ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [iOS] UITextField를 RxDelegateProxy를 이용하여 사용해보자
    스위프트 2023. 7. 29. 13:27

    TextField에서 엔터를 쳤을 때 동작을 하고 싶은데..

    생각보다 RxSwift에서 UITextField에 대한 지원이 아쉬웠습니다.

    // RxCocoa에 있는 UITextField+Rx
    
    extension Reactive where Base: UITextField {
        /// Reactive wrapper for `text` property.
        public var text: ControlProperty<String?> {
            value
        }
        
        /// Reactive wrapper for `text` property.
        public var value: ControlProperty<String?> {
            return base.rx.controlPropertyWithDefaultEvents(
                getter: { textField in
                    textField.text
                },
                setter: { textField, value in
                    // This check is important because setting text value always clears control state
                    // including marked text selection which is important for proper input
                    // when IME input method is used.
                    if textField.text != value {
                        textField.text = value
                    }
                }
            )
        }
        
        /// Bindable sink for `attributedText` property.
        public var attributedText: ControlProperty<NSAttributedString?> {
            return base.rx.controlPropertyWithDefaultEvents(
                getter: { textField in
                    textField.attributedText
                },
                setter: { textField, value in
                    // This check is important because setting text value always clears control state
                    // including marked text selection which is important for proper input
                    // when IME input method is used.
                    if textField.attributedText != value {
                        textField.attributedText = value
                    }
                }
            )
        }
    }

    UITextFieldDelegate를 이용한 동작


    extension SomeViewController: UITextFieldDelegate {
    
        func textFieldShouldReturn(_ textField: UITextField) -> Bool {
            if textField == nameTextField {
                // Do something
            } else if textField == urlTextField {
                // Do something
            } else if textField == descTextField {
                // Do something
            }
            return true
        }
        
    }

    Delegate를 이용해서 구현하면 저런 식으로 구현할 수 있습니다.

    TextField가 하나만 존재하면 괜찮지만 개수가 늘어나면 동일하게 if-else (또는 switch) 가 늘어납니다.

    뭔가 아쉽습니다. 😢

    RxDelegateProxy를 활용하자


    Delegate에 있는 동작을 RxDelegateProxy로 추가할 수 있습니다.

    extension UITextField: HasDelegate {
        public typealias Delegate = UITextFieldDelegate
    }

    먼저 UITextField가 HasDelegate를 준수하도록 만듭니다.

    open class RxTextFieldDelegateProxy: DelegateProxy<UITextField, UITextFieldDelegate>, DelegateProxyType {
        
        public weak private(set) var textField: UITextField?
        
        public init(textField: ParentObject) {
            self.textField = textField
            super.init(parentObject: textField, delegateProxy: RxTextFieldDelegateProxy.self)
        }
        
        public static func registerKnownImplementations() {
            self.register { RxTextFieldDelegateProxy(textField: $0) }
        }
        
        private var _returnKeyDidTapPublishSubject: PublishSubject<()>?
        
        internal var returnKeyDidTapPublishSubject: PublishSubject<()> {
            if let subject = _returnKeyDidTapPublishSubject {
                return subject
            }
            let subject = PublishSubject<()>()
            _returnKeyDidTapPublishSubject = subject
            return subject
        }
        
        deinit {
            if let subject = _returnKeyDidTapPublishSubject {
                subject.on(.completed)
            }
        }
        
    }

    다음으로 DelegateProxy 를 준수하여 필요한 동작을 추가합니다.

    그리고 textFieldShouldReturn 를 활용하기 위한 PublishSubject returnKeyDidTapPublishSubject 를 추가합니다.

    extension RxTextFieldDelegateProxy: UITextFieldDelegate {
        
        public func textFieldShouldReturn(_ textField: UITextField) -> Bool {
            if let subject = _returnKeyDidTapPublishSubject {
                subject.on(.next(()))
            }
            return self._forwardToDelegate?.textFieldShouldReturn(textField) ?? true
        }
        
    }

    추가한 PublishSubject textFieldShouldReturn 이 호출될 때 onNext를 보냅니다.

    public extension Reactive where Base: UITextField {
        
        var returnKeyDidTap: ControlEvent<Void> {
            let source = RxTextFieldDelegateProxy.proxy(for: base).returnKeyDidTapPublishSubject
            return ControlEvent(events: source)
            
        }
        
    }

    마지막으로 ControlEvent를 만들어 사용해 줍시다.

    활용


    nameTextField.rx.returnKeyDidTap
        .withUnretained(self.urlTextField)
        .subscribe(onNext: { nextTextField, _ in
            nextTextField.becomeFirstResponder()
        })
        .disposed(by: disposeBag)
    
    urlTextField.rx.returnKeyDidTap
        .withUnretained(self.descTextField)
        .subscribe(onNext: { nextTextField, _ in
            nextTextField.becomeFirstResponder()
        })
        .disposed(by: disposeBag)

    이런 식으로 사용하면 다음 TextField에 포커싱이 가도록 구현할 수 있습니다.

     

    UITextField+Rx 예시 영상

     

Designed by Tistory.