[RxSwift] AirPortClone: Navigation Logic

Junyoung Park·2022년 12월 24일
0

RxSwift

목록 보기
18/25
post-thumbnail

#6 Navigation and Passing Data between ViewControllers - RxSwift MVVM Coordinator iOS App

AirPortClone: Navigation Logic

구현 목표

  • 네비게이션 로직 구현

구현 태스크

  • 선택 셀을 통한 뷰 모델 라우팅
  • Coordinator 연결
  • Coordinator을 통한 네비게이션 컨트롤러 내 뷰 푸시

핵심 코드

private func setViewModel() {
        viewModel = viewModelBuilder((
            searchText: searchBarController.searchBar.rx.text.orEmpty.asDriver(),
            citySelect: tableView.rx.modelSelected(CityViewModel.self).asDriver()
        ))
    }
  • 검색 뷰 컨트롤러에서 modelSelected라는 rx에 연결된 프로퍼티를 통해 현재 선택된 도시 뷰 모델을 asDriver로 변환한 뒤 전달
  • 즉 현재 뷰 모델(SearchViewModel)이 가지고 있는 citySelect는 특정 셀을 선택할 때마다 새롭게 업데이트
typealias Input = (
        searchText: Driver<String>,
        citySelect: Driver<CityViewModel>
    )
  • 검색 뷰 모델이 따르고 있는 프로토콜이 받아들이는 인풋의 종류에 citySelect가 추가됨에 따라 검색 뷰 컨트롤러의 뷰 빌더를 통해 전달 가능
private typealias RoutingAction = (citySelectedRelay: PublishRelay<Set<AirportModel>>, ())
    private let routingAction: RoutingAction = (citySelectedRelay: PublishRelay(), ())
    typealias Routing = (citySelected: Driver<Set<AirportModel>>, ())
    lazy var router = (citySelected: routingAction.citySelectedRelay.asDriver(onErrorDriveWith: .empty()), ())
  • 검색 뷰 모델 내의 변수를 통해 전달받은 위의 인풋 중 citySelect 값을 통해 만들어줄 중간 단계 라우팅
  • PublishRelay, Driver를 통해 각각 값이 전달/보관/UI를 그리기 위한 구동 단계를 형성
  • 실제 라우팅 과정이 일어나는 PublishRelay는 밖에 보이지 않게 가리고, UI로 연결되는 Driver는 노출
			input
            .citySelect
            .map({ $0.city })
            .withLatestFrom(state.airports.asDriver()) {( city: $0, airports: $1 )}
            .map { city, airports in
                airports.filter({ $0.city == city })
            }
            .map({ [routingAction] in
                routingAction.citySelectedRelay.accept($0)
            })
            .drive()
            .disposed(by: disposeBag)
  • 뷰 모델이 인풋의 선택된 도시 citySelectDriver 형태로 이미 뷰 모델이 가지고 있는 다른 데이터(공항 데이터)와 연결, 필터링을 통해 결과적으로 데이터 바인딩이 일어나는 라우팅 액션의 citySelectedRelay에 값을 전달
override func start() {
        let searchVC = SearchViewController()
        let service = AirportService.shared
        searchVC.viewModelBuilder = { [disposeBag] in
            var viewModel = SearchViewModel(input: $0, airportService: service)
            viewModel
                .router
                .citySelected
                .map { [weak self] models in
                    self?.showAirports(usingModel: models)
                }
                .drive()
                .disposed(by: disposeBag)
            return viewModel
        }
        navigationController.pushViewController(searchVC, animated: true)
    }
  • SearchCoordinatorstart 함수 내에서도 라우터를 계속 관찰, PublishRelay()를 통해 받아오는 값을 models로 연결해 showAirports 함수에 연결
private func showAirports(usingModel models: Set<AirportModel>) {
        let airportsCoordinator = AirportsCoordinator(navigationController: navigationController)
        add(coordinator: airportsCoordinator)
        airportsCoordinator.start()
    }
  • 즉 어떤 데이터를 선택한지에 따라 models에 해당 데이터가 들어온 뒤 주어진 뷰에 따른 Coordinator를 추가하고, 해당 Coordinator를 구동하는 기본 로직은 상동
import UIKit

class AirportsCoordinator: BaseCoordinator {
    private let navigationController: UINavigationController
    
    init(navigationController: UINavigationController) {
        self.navigationController = navigationController
    }
    
    override func start() {
        let vc = AirportsViewController()
        self.navigationController.pushViewController(vc, animated: true)
    }
}
  • 현 시점에서는 건네받은 네비게이션 뷰 컨트롤러에 디테일 뷰를 푸시하는 것으로 마무리.

구현 화면

네비게이션 이동 과정을 자체를 주관하는 Coordinator 패턴의 동작대로, 뷰를 로드하고 삭제하는 과정을 위임하고 있다. 아직까지 그 '힘'이라고 해야 하나, 장점을 명백하게 느끼지는 못하겠지만, 일단은 다채롭다.

profile
JUST DO IT

0개의 댓글