iOS/RxSwift

[RxSwift] UITextField에 Rx 적용하기

728x90

요즘 rx 공부를 하면서 알바집 코드에 rx를 적용해보려고 한다!

취업전 코드라 엄청난 레거시지만 차츰 mvc -> rx+mvvm 구조로 변경해볼생각이다 ㅎㅎ

 

오늘 적용해볼 화면은 "비밀번호 입력" 화면이다.

 

📌 요구 사항

  • 비밀번호 입력 textfield
    • 6자리 이상✅
  • 비밀번호 확인 textfield
    • 비밀번호 입력창 텍스트와 일치할 경우 ✅
    • 일치하지 않을 경우 에러메시지 show
    • 텍스트 빈값이면 에러메시지 hidden
  • 위 조건들의 모두 참일때 다음 버튼 활성화 ➡️

📌 구현

viewModel

import RxSwift
import RxCocoa

struct RegisterPasswordViewModel {
    // 비밀번호 텍스트
    let pwdText = BehaviorRelay<String>(value: "")

    // 비밀번호 확인 텍스트
    let pwdCkText = BehaviorRelay<String>(value: "")
    
    // 비밀번호 관련 상태값
    struct PwdState {
        var pwdCheck: Bool = false
        var pwdCkCheck: Bool = false
        var nextBtnCheck: Bool = false
        var errorMsgHidden: Bool = true
    }
    
    func checkPwd() -> Driver<PwdState> {
        let output = Observable.combineLatest(pwdText, pwdCkText) {
            var state = PwdState()
            
            if($0.count >= 6) { // 비밀번호 6자 이상
                state.pwdCheck = true
                if $0 == $1 { // 비밀번호 일치
                    state.pwdCkCheck = true
                    state.nextBtnCheck = true
                }else{
                    state.errorMsgHidden = false
                }
            }
            
            if $1 == "" {
                state.errorMsgHidden = true
            }
            
            return state
        }
        .asDriver(onErrorJustReturn: PwdState())
        return output
    }
}

pwdText, pwdCkText 값은 viewController textfield의 텍스트가 바인딩될 변수이다.

PwdState은 viewController에 넘겨줄 비밀번호 상태값들이다.

checkPwd 함수는 pwdText, pwdCkText를 combineLatest로 조합하여 text 변경시마다 호출되며 PwdState을 viewController에 전달해 UI 변경을 돕는다.

 

Subject가 아니라 Relay를 사용한 이유

Subject는 .completed, .error의 이벤트가 발생하면 subscribe가 종료되는 반면, Relay는 .completed, .error를 발생하지 않고 Dispose되기 전까지 계속 작동하기 때문에 UI Event에서 사용하기 적절하다고 판단했다.

➕ Relay는 Subject를 Wrapping하는 형태로 구성된다고 한다.

 

✨ BehaviorRelay를 사용한 이유

처음에 PublishRelay로 코드를 작성하였으나, 초깃값을 지정할 수 없어서 checkPwd 함수의 

combineLatest 시 모든 observable이 세팅 되기 전까지 해당 코드가 실행되지 않아 초깃값을 지정할 수 있는 BehaviorRelay를 사용했다.

 

✨Driver를 사용한 이유

Observable은 기본적으로 main thread에서 동작하지 않고 background thread에서 동작해서 메인 스레드에서 작동하려면 따로 코드를 추가해줘야 한다. 하지만 Driver는 main thread에서 관찰, 구독하기 때문에 UI에 이벤트에 적합하다고 판단했다.

➕ Observable를 asDriver()를 통해 driver로 변환 가능하다.

➕ Driver처럼 UI 처리에 특화된 옵저버블을 Traits이라고 한다.

 

Relay로 이벤트를 발생시키고, asDriver를 통해서 이벤트를 구독

 

viewController

// 비밀번호
passwordTextField.rx.text.orEmpty
    .bind(to: viewModel.pwdText)
    .disposed(by: disposeBag)

passwordTextField.rx.controlEvent([.editingDidBegin])
    .asDriver()
    .drive(onNext: { [weak self] in
        guard let self = self else { return }
        self.passwordTextField.borderColor = .mainYellow
    })
    .disposed(by: disposeBag)

passwordTextField.rx.controlEvent([.editingDidEnd])
    .asDriver()
    .drive(onNext: { [weak self] in
        guard let self = self else { return }
        self.passwordTextField.borderColor = .lightGray
    })
    .disposed(by: disposeBag)

// 비밀번호 확인
passwordCkTextField.rx.text.orEmpty
    .bind(to: viewModel.pwdCkText)
    .disposed(by: disposeBag)

passwordCkTextField.rx.controlEvent([.editingDidBegin])
    .asDriver()
    .drive(onNext: { [weak self] in
        guard let self = self else { return }
        if self.errorLabel.isHidden{
            self.passwordCkTextField.borderColor = .mainYellow
        }else{
            self.passwordCkTextField.borderColor = .red
        }
    })
    .disposed(by: disposeBag)

passwordTextField, passwordCkTextField를 viewModel의 text상태값에 바인딩해주었다.

controlEvent인 .editingDidBegin와 editingDidEnd를 활용하여 textfield border color를 변경해주었다.

 

// 비밀번호 검사
viewModel.checkPwd()
    .drive(onNext: { [weak self] in
        guard let self = self else { return }
        self.checkImage1.image = $0.pwdCheck ? #imageLiteral(resourceName: "icCheckedCorrect") : #imageLiteral(resourceName: "icCheckedNormal")
        self.checkImage2.image = $0.pwdCkCheck ? #imageLiteral(resourceName: "icCheckedCorrect") : #imageLiteral(resourceName: "icCheckedNormal")
        self.btnNextEnable($0.nextBtnCheck)
        self.errorLabel.isHidden = $0.errorMsgHidden
        if $0.errorMsgHidden{
            if self.passwordCkTextField.isFirstResponder{
                self.passwordCkTextField.borderColor = .mainYellow
            }else{
                self.passwordCkTextField.borderColor = .lightGray
            }
        }else{
            self.passwordCkTextField.borderColor = .red
        }
    })
    .disposed(by: disposeBag)

viewModel checkPwd 함수에서 전달받은 결과값들을 이용해 UI를 업데이트 해주는 코드를 작성하였다.

 

📌 실행 영상

 

uitextfield secure모드 땜에 스크린 녹화 시 흰색으로 나왔지만, 요구사항대로 구현 완료하였다~!

728x90

'iOS > RxSwift' 카테고리의 다른 글

[RxSwift] Network 통신하기  (0) 2022.11.15
[RxSwift] ReactorKit 알아보기  (0) 2022.10.30
[RxSwift] RxCocoa란?  (0) 2022.10.23
[RxSwift] Combining Operator 알아보기  (0) 2022.10.23
[RxSwift] Transforming Operator 알아보기  (0) 2022.10.23