티스토리 뷰

반응형

 

 

안녕하세요. 오늘은 최근 React native로 앱 개발을 하며 마주했던 이슈를 파헤쳤던 과정을 회고와 함께 말씀드리고자 합니다.

사실 정확히 말하면 이슈라고 할 수 없는 'IOS에서의 의도적인 행동'이었지만, 그냥 이슈라고 칭하겠습니다.

 

참고로 해당 이슈는 React-native단에서 해결할 수 있는 것이 아니라는 결론이 나서 결국에는 수동으로 이 과정을 구현하게 되었습니다. >> 수동으로 패스워드 타입 구현하기

 

하지만 이 과정에서 배운 점들이 많아 글을 쓰지 않을 수가 없었습니다...!! (누군가는 해결했을지도 모르겠네요.. 그렇다면 꼭 댓글을 남겨주세요.)

 

 


Issue explained

IOS에서

Input type ==='password' 일 때 값을 재 입력하게 되면 비밀번호 값이 유지되지 않습니다. IOS에서는 이 속성을 secureTextEntry라고 합니다. 해당 속성이 true이면 password type 이게 되면서 값들이 Bullet points로 바뀌는 것이죠.

 

사실 이는 IOS 자체적으로 막은 기능입니다. 개인적으로 Apple에서 그렇게 설정을 했다면 보안상 다 이유가 있는 법..이라는 생각이 드는데요.. 이 분의 의견을 보니 나름의 이유가 있는 듯합니다. 

 

위에서 녹화된 화면을 보시듯 비밀번호를 입력하면 마지막 문자가 잠시 동안 노출이 됩니다. 유저가 다시 편집을 할 때 입력한 내용을 정확하게 기억해야 하며 이는 잘못된 보안 데이터를 지속적으로 전송하는 행위가 됩니다. 사실상 유저가 다시 편집을 하는 경우는 자주 있는 일이 아니기 때문에 편집 시점에서는 모든 값들을 삭제한다고 하네요. (이 분의 개인적인 의견이기는 합니다.)

 


나름의 이유야 있겠지만 저는 클라이언트분이 비밀번호이더라도 값이 리셋되지 않기를 원하시기도 했고, 저 또한 기존에 비밀번호가 리셋되는 것에 불편함을 느꼈던 경험이 있었기 때문에 해당 이슈에 관해 파헤치기 시작했습니다. 

 

 

열심히 찾아본 결과 해당 이슈는 지난 몇 년에 걸쳐 여러 번 등록이 되었습니다. 

 

 

 

StackOfFlow에도 관련 키워드로 검색을 하시다 보면 여러 개가 나오기는 합니다. 좀 오래되긴 했지만요.

 

 


 

Solution for Swift

Swfit에서 해결하는 방법은 꽤 간단해 보였습니다. UITextField를 커스텀 클래스로 만들어서 become​First​Responder()를 재정의하여 원하는 액션을 추가하는 것이죠.  

 

class PasswordTextField: UITextField {

    override var isSecureTextEntry: Bool {
        didSet {
            if isFirstResponder {
                _ = becomeFirstResponder()
                //MARK:- Do something what you want
            }
        }
    }

    override func becomeFirstResponder() -> Bool {

        let success = super.becomeFirstResponder()
        if isSecureTextEntry, let text = self.text {
            self.text?.removeAll()
            insertText(text)
        }
         return success
    }
}

 

 


 

How to apply Swift code with React-native

 

위에서 본 솔루션 코드를 React-native에서 적용할 수 있을까요? 해당 코드를 실행하기 위해서는 React-native에서 UITextField를 상속받고 오버 라이딩을 할 수 있어야겠습니다.

 

저도 해당 부분에 대해 잘 몰라서 방법이 있는지 찾아보기도 했는데요.

찾아본 결과 React-native는 native 컴포넌트를 바인딩해놓은 거라 상속이 된다 하더라도 변하는 것은 제한적이랍니다.

 

그래서 제가 Swfit에서 코드를 바꾸더라도 React-native에서 모듈을 새로 만들어버리면 반영이 안 되었던 것이었어요 :( 삽질 삽질!

 

 


 

Fix native code in node-modules

:  Instantly make and keep fixes to npm dependencies

어떻게 해야 native 언어를 React-native에 반영할 수 있을까?라는 고민으로 React-native 4년 차이신 동료분께 가서 제 상황을 말씀드렸더니 한 라이브러리를 소개해주시더군요.

 

바로 patch-package라는 라이브러리인데요, 이 라이브러리는 npm 패키지 의존성은 그대로 유지하면서, 변경한 npm 패키지의 내용을 버전 관리 대상으로 간편하게 만들어 줍니다.

 

이는 node-modules안의 파일들을 커스텀한 상태가 배포 상태에서도 지속되도록 수정사항을 기억해뒀다가 배포 시 node-modules 위에 덮어 씌워주게 되는 방식입니다.

 

즉, node_modules에 수정한 사항이 git으로 관리되고 어떠한 실행 환경에서도 적용되도록 해줍니다. 해당 라이브러리에 대한 자세한 설명은 포스팅을 따로 분리하였습니다. 궁금하신 분은 여기를 클릭해주세요!

 

아무튼 저는 stack of flow에서 솔루션으로 올라온 코드를 해당 경로 안에 적용시켰습니다.

 

 

Before

// node_modules/react-native/   
// Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.m


- (BOOL)textField:(__unused UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
  NSString *newText =
    [_backedTextInputView.textInputDelegate textInputShouldChangeText:string inRange:range];

  if (newText == nil) {
    return NO;
  }

  if ([newText isEqualToString:string]) {
    _textDidChangeIsComing = YES;
    return YES;
  }

  NSMutableAttributedString *attributedString = [_backedTextInputView.attributedText mutableCopy];
  [attributedString replaceCharactersInRange:range withString:newText];
  [_backedTextInputView setAttributedText:[attributedString copy]];

  // Setting selection to the end of the replaced text.
  UITextPosition *position =
    [_backedTextInputView positionFromPosition:_backedTextInputView.beginningOfDocument
                                        offset:(range.location + newText.length)];
  [_backedTextInputView setSelectedTextRange:[_backedTextInputView textRangeFromPosition:position toPosition:position]
                              notifyDelegate:YES];

  [self textFieldDidChange];

  return NO;
}

 

 

After

// node_modules/react-native/   
// Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.m


- (BOOL)textField:(__unused UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
    //Setting the new text.
    NSString *updatedString = [textField.text stringByReplacingCharactersInRange:range withString:string];
    textField.text = updatedString;

    //Setting the cursor at the right place
    NSRange selectedRange = NSMakeRange(range.location + string.length, 0);
    UITextPosition* from = [textField positionFromPosition:textField.beginningOfDocument offset:selectedRange.location];
    UITextPosition* to = [textField positionFromPosition:from offset:selectedRange.length];
    textField.selectedTextRange = [textField textRangeFromPosition:from toPosition:to];

    //Sending an action
    [textField sendActionsForControlEvents:UIControlEventEditingChanged];

    return NO;
}

 

변경한 코드로 실행시키면 IOS에서 비밀번호 편집 시 리셋되는 현상은 사라집니다. 하지만 여기에는 치명적인 결함이 있었습니다.

 

제가 수정한 코드는 모든 UITextField에서 shouldChangeCharactersInRange라는 함수가 발동이 되면 실행되게 됩니다. 그래서 한글의 경우에는 해당 코드가 password type이 아닌 input에도 적용이 되어 자모음이 분리되는 현상이 발생하게 됩니다. 

ㄱ ㅗ ㅁ ㄷ ㅗ ㄹ ㅇ ㅣ 

이런 식으로요.

 

native 언어를 잘 안다면 파고들어 조건을 나누면 해결이 됐을 수도 있겠지만, 정말 아무리 찾고 봐도 어떤 식으로 분리를 해야 될지 모르겠더군요.. 또 해당 부분을 커스텀하는 것 자체에 대한 안정성 이슈가 고려되기도 했고요. 

 

궁극적으로 해결은 하지 못했지만 해당 부분에 관련하여 meta Reactive-native github에 Issue 등록을 했습니다. 기대하는 답변이 아니더라도 해당 props라도 만들어 주셨으면 좋겠는 마음에 추신도 달았습니다.

 

답변이 잘 달릴까요? 궁금하신 분은 여기로.. > Issue 보기  

 

 

 


 

Implement password types manually

 

수동적으로 비밀번호 타입을 구현하는 것에 대한 시도도 해보았습니다. TextInput에서 제공하는 props인

onChangeText / onKeyPress / onSelectionChange를 재밌게 활용해서 구현에 성공하였습니다. 

 

>> 수동으로 패스워드 타입 구현하기

 

 


 

Conclusion 

reason for Issue Closed

 

아직 제 이슈에는 답글이 달리지 않았지만 그동안 달린 이슈들이 closed 되면서 내린 답변이 있었습니다. 

이는  '패스워드가 편집 시점에 리셋되는 현상은 IOS의 의도적인 behavior이다.'라는

native언어에서 작동하고 있는 것을 React-native 범위에서 수정하는 것을 고려하라는 의견이었는데요.

I'm going to close this issue as "works as intended". In general if something works a particular way in iOS native development it seems reasonable to consider it out of scope for RN to fix it.

 

또 해당 prop을 선택적으로 추가할 수는 있지만 또 다른 prop을 추가하는 것을 원하지 않는다는 의견도 있었습니다. 

"We could optionally override this behavior with a prop, but I am not sure we want to add another prop to the TextInput component."

 

제가 단 이슈에도 비슷하게 답변이 달릴 듯합니다. 궁금한 점은 보안상 문제가 되지 않는다면 추가하지 않을 이유도 없는 것 같은데 IOS에서 해당 value를 리셋시키는 정확한 이유가 궁금해집니다.

 

 

 

 

 

반응형