요즘 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모드 땜에 스크린 녹화 시 흰색으로 나왔지만, 요구사항대로 구현 완료하였다~!
'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 |