🌱 RxSwift 성별 토글 버튼만들기

2022. 2. 24. 01:46iOS/반응형 Rx | Combine

이 글을 쓰게 된 이유

새싹 프로젝트에서 성별을 선택하고 회원가입을 하는 화면이 있었다. Rx로 단순 토글 버튼을 만드는 것은 쉬웠지만, 연관된 토글 버튼을 만드는 것은 나에게 조금 힘든 도전이었다.

예를 들어 남자를 선택했을 때, 여자가 선택되어 있으면, 여자를 해제하고 남자를 선택해야하고, 남자를 한 번더 선택하면 선택해제가 되게 해야했다. 말로는 쉬운데 이게 참 구현하기 힘들었다.

 

과정

처음 Rxswift로 토글 버튼을 만드는 법을 검색해보니 .scan으로 이전 상태를 이용해서 간단히 만들 수 있었다.

//Controller
Input(button.rx.tap)

//ViewModel
Output(buttonTap) {
        buttonTap.
        scan(false) { lastState, _ in
        return !lastState
       }
	.bind(target)
	.disposed(by: disposeBag)
}

토글 버튼 하나라면 쉬웠을 테지만, 서로에게 영향을 주는 토클 버튼 2개라서 힘들었다. 그래서 남자를 선택하면 여자의 상태값에는 무조건 false를 줬다.

// Controller
Input(button.rx.tap)

Output.maleState
	.asDriver(onErrorJustReturn: false)
    .drive(mainView.femaleButton.rx.isSelected)
    .disposed(by: disposeBag)

// ViewModel
Output(buttonTap) {
	
    // 스트림 생성
    let maleResult = buttonTap.
        scan(false) { lastState, _ in
        return !lastState
       }
       .share(replay: 1, scope: .whileConnected)
    
    // 남자버튼의 isSelected
    _ = maleResult
	.bind(maleTarget)
	.disposed(by: disposeBag)
    
    // 여자버튼의 isSelected
    _ = maleResult
	.map({ _ in
    	return false
    })
    .bind(femaleTarget)
    .disposed(by: disposeBag)
    
    // ...
}

이렇게 하니 문제가 발생했다. .scan에서 값을 누적하면서 이전 값을 통해서 toggle을 판별하는데 다른 스트림에서 변경해줘서 toggle값이 꼬여버렸다.

해서 .scan을 버리고 relay를 하나두어서 성별 버튼울 누르면 성별 값을 relay에 저장하고, relay에서 각각의 성별 output을 구독하게 만들었다.

func transform(input: Input) -> Output {
        
        // Output Subject
        let maleSubject = BehaviorSubject<Bool>(value: false)
        let femaleSubject = BehaviorSubject<Bool>(value: false)
		
        // 남자 버튼 클릭 시 gender로 값 전달
        input.maletap
            .map({ _ in
                switch self.gender.value {
                case .none, .female:
                    return .male
                case .male:
                    return .none
                }
            })
            .share(replay: 1, scope: .whileConnected)
            .asDriver(onErrorJustReturn: .none)
            .drive(gender)
            .disposed(by: disposeBag)
		
        // 여자 버튼 클릭 시 gender로 값 전달
        input.femaletap
            .map({ _ in
                switch self.gender.value {
                case .none, .male:
                    return .female
                case .female:
                    return .none
                }
            })
            .share(replay: 1, scope: .whileConnected)
            .asDriver(onErrorJustReturn: .none)
            .drive(gender)
            .disposed(by: disposeBag)
		
        // 젠더 값으로 Output값 결정
        gender.bind { selectedGender in
            switch selectedGender {
            case .male:
                maleSubject.onNext(true)
                femaleSubject.onNext(false)
            case .female:
                maleSubject.onNext(false)
                femaleSubject.onNext(true)
            case .none:
                maleSubject.onNext(false)
                femaleSubject.onNext(false)
            }
            debugPrint(selectedGender)
        }.disposed(by: disposeBag)

        return Output(maleState: maleSubject, femaleState: femaleSubject, sceneTransition: input.tap)
    }

의문점 및 결론

.share를 썼는데 여기서 의미가 있는지 없는지 모르겠다. input tap 스트림을 gender가 공유를 하는지 아닌지 알아봐야겠다.

 

다음글 예고

다음글은 여기서 사용된 Rxswift Input / Output 패턴이나 iOS15의 Button Configuration에 대해서 써봐야겠다. 버튼 색상 변경을 configurationHandler를 이용하여 코드를 줄였다.