꿈돌이랜드

SeeMeet 개발후기 - 2, Rx 도입 본문

Programming/개발 후기

SeeMeet 개발후기 - 2, Rx 도입

loinsir 2023. 5. 1. 22:25
반응형

씨밋을 개발하면서 가장 애먹은 부분 중 하나는 약속 신청 화면들이었다.

언뜻? 보면 별거 아닌 화면 같다. 그냥 UISearchBar 이용하고, UITextField, UITextView 이용해서 입력창을 구현하고 각 입력창에 모든 값이 입력되어 있으면 다음 버튼을 활성화 시키고 색을 변환시키면 될테니까.

하지만 저 상단의 검색창을 구현하는 일이 만만치 않았다.

...

위처럼, 입력 도중에 연관 검색어 기능 처럼 본인의 친구 목록에서 지금까지 입력한 글자에 매치되는 친구목록을 보여줘야 하며, 그중 하나를 선택할 경우 칩셋(토큰)의 형태로 검색창에 박혀야 했다. 그리고 칩셋의 X는 버튼으로 작동해서, 누를 경우 해당 이름은 삭제되어야 한다.

처음엔 위 검색창을 어떤식으로 구현할 지에 대해 팀원들과 논의가 많았었다. 결국 데모데이까지 정확한 결론을 내리지 못하고 구현도 못하였고, 임시로 3개까지의 칩들만을 보여주기로 합의하였고 데모데이를 마쳤었다. 데모데이 이후에는 출시를 위해 정확한 구현을 해야 하므로 고민을 하게되었다.

어떻게 구현할까? 여러 시도를 해보았다.

1. UISearchToken

https://developer.apple.com/documentation/uikit/uisearchtoken

UISearchToken은 UISearchTextField와 함께 사용되어, 복잡한 검색문을 편집하는 것을 이해하기 쉽게 하는 UI이다. 우리 앱의 UI 기능과 상당히 유사하기도 해서 이것을 상속시켜 커스텀 하면 되겠구나 싶었다. 그러나 그럴 수 없었다.

UIKit에서, UISearchToken의 생성자는 public으로 접근제어자를 선언해놓았다. 이는 모듈 외부에서 접근은 허용하지만, 재정의(오버라이드)는 막아두었다는 의미이다. 그래서 구현하기 어려웠다.

2. UICollectionView

주변지인에게 물어보았는데 컬렉션 뷰를 고려해보라고 했었다. 사실 약간의 생각은 있었다. 만약 여러개 칩셋이 쌓이면 좌우로 스크롤링이 되어야하기 때문이다. 결론부터 말하자면 이것은 정답이었다. 하지만 컬렉션 뷰에 얽힐 여러 UI와 이벤트 요소들을 전부 delegate메서드에 구현하게 되면 엄청난 길이의 코드가 생성될 것으로 예견되었다. 그래서 RxSwift를 이시점에 도입하기로 했다. RxSwift의 조합 연산자들을 사용하여 여러 이벤트 스트림들을 엮어서 선언적으로 구현하면 좋을듯 했다.

enum SearchTokenType {
    case selectedFriendToken(data: FriendsData) // 선택하여 추가한 친구 이름 토큰
    case inputFieldToken // 항상 마지막에 위치하는, 입력창이 있는 토큰
}

우선 이런식으로 칩셋의 종류 2가지를 구별했다.

var friendDataToSet: FriendsData?
private var friendDataList: [FriendsData] = []

private lazy var selectedFriendsListRelay: BehaviorRelay<[SearchTokenType]> = { [weak self] in
    if let friendDataToSet = self?.friendDataToSet {
        return BehaviorRelay<[SearchTokenType]>(value: [.selectedFriendToken(data: friendDataToSet), .inputFieldToken]) // 항상 마지막은 입력 필드 셀
    } else {
        return BehaviorRelay<[SearchTokenType]>(value: [.inputFieldToken]) // 항상 마지막은 입력 필드 셀
    }
}()

private lazy var filteredFriendsRelay = BehaviorRelay<[FriendsData]>(value: friendDataList)

그리고 나서 위와 같이 표시할 칩셋들의 데이터들을 relay로 선언해놓았는데, Relay는 Subject, Observable과는 달리 .complete, .error 이벤트를 발생시키지 않기 때문에 Dispose되기 전까지 UI에 표시될 데이터를 다루는데 적합하다고 여겨 이렇게 선언했다.

private func setupSelectedCollectionView() {
	selectedFriendsRelay
    .bind(to: selectedCollectionView.rx.items) { (collectionView, item, element) in
    	switch element {
        case .selectedFriendToken(let friendsData):
        ...
        case .inputFieldToken:
        ...
}

그리고 해당 relay에서 방출되는 element를 분기해 처리했다.

결론

collectionview와 enum 타입, RxRelay를 사용해 해당 토큰 서치뷰를 구현했다. 아쉬운 점은 프로젝트 아키텍처가 MVC로 되어있어서 모든 비즈니스 로직이 VC에 있어서 VC 코드가 500줄이 넘는다. (심지어 코드 기반 UI) 유지보수가 굉장히 어려워보인다. 다음에 프로젝트를 할때는 MVVM으로 설계해서 Relay 처리등을 뷰모델로 분리시키면 좋을 것 같다.


Uploaded by N2T

반응형