관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] 4. ReactorKit - `TaskEdit 구현`, 화면전환, 데이터 전달 본문

Architecture (swift)/ReactorKit

[iOS - swift] 4. ReactorKit - `TaskEdit 구현`, 화면전환, 데이터 전달

jake-kim 2021. 12. 3. 02:23

1. ReactorKit - 개념

2. ReactorKit - 테스트 방법 (Storyboard 사용, IBOutlet 테스트 방법)

3. ReactorKit - `TaskList 구현`, 템플릿 (template), 비동기 처리 transform(mutation:)

4. ReactorKit - `TaskEdit 구현`, 화면전환, 데이터 전달

 

* ReactorKit과 RxDataSources 사용 방법은 이곳 참고

 

ReactorKit 구현 방향

  1. View, Reactor 생성
  2. View의 storyboard에 UI 생성, IBOutlet 입력
  3. Reactor의 Action 정의, Action에 해당하는 Mutation, State 정의
  4. Reactor에서 필요한 service 정의
  5. Reactor의 mutate, reduce 정의

TaskEdit 구현

  • 화면전환 TaskList -> TaskEdit
    • TaskEditReactor는 생성자로 taskService를 가지고 있고, 이 값은 TaskList에서 가지고 있는 인스턴스를 주입
      // TaskEditReactor.swift
      
      init(initialState: State, taskService: TaskService) {
          self.initialState = initialState
          self.taskService = taskService
      }​
    • TaskList의 Action에 TaskEdit으로 보내는 Action 추가
      enum Action {
          case didInitBinding
          case didSelect(IndexPath)
          case didTapAddButton // <-
      }​
    • TaskList의 Mutation와, State에 present관련 값 추가
      • Mutation의 setIsPresentEditTask(Bool): 
      • State의 isPresentEditTask: 
enum Mutation {
    case setSections([TaskListSection])
    case updateSectionItem(IndexPath, TaskListSection.Item)
    case insertSectionItem(IndexPath, TaskListSection.Item)
    case setIsPresentEditTask(Bool) // <-
}

struct State {
    var sections: [TaskListSection]
    /// Bool 값은 영구적이므로, sections값이 변경되면 isPresentEditTask도 방출되면서 isPresentEditTask 옵저버가 계속 실행
    /// Mutation에서 concat으로 true > false로 바꾸어주는 로직 필요
    var isPresentEditTask: Bool = false
}
  • didTapAddButton이 오면, concat으로 presentEditTask를 true로 바꾸었다가, false로 변경
    • true로 계속 되어있으면 sections값들이 바뀌어도 ViewController에 같이 emit되므로 false로 변경 필요
func mutate(action: Action) -> Observable<Mutation> {
    var newMutation: Observable<Mutation>
    
    switch action {
    case .didInitBinding:
        newMutation = getRefreshMutation()
    case .didSelect(let indexPath):
        newMutation = setCheckMarkMutation(indexPath)
    case .didTapAddButton:
        newMutation = getPresentEditTaskMutation() // <-
    }
    
    return newMutation
}

...

private func getPresentEditTaskMutation() -> Observable<Mutation> {
    return Observable.concat([
        Observable.just(.setIsPresentEditTask(true)),
        Observable.just(.setIsPresentEditTask(false)) // <- 
    ])
}

...

func reduce(state: State, mutation: Mutation) -> State {
    var newState = state
    
    switch mutation {
    case .setSections(let sections):
        newState.sections = sections
        
    case .updateSectionItem(let indexPath, let item):
        newState.sections[indexPath.section].items[indexPath.item] = item
        
    case .insertSectionItem(_, let item):
        newState.sections[0].items.insert(item, at: 0)
        
    case .setIsPresentEditTask(let isPresent): // <-
        newState.isPresentEditTask = isPresent
        
    }
    
    return newState
}
  • 화면전환, 데이터 전달
    • TaskListViewController에서는 isPresentEditTask를 구독하고 있다가, 이벤트 발생시 화면전환
    • 화면전환시 필요한 TaskEditReactor 인스턴스는 TaskListReactor에서 정의
// TaskListReactor.swift

func getTaskEditReactorForCreatingTask() -> TaskEditReactor {
    
    return TaskEditReactor(initialState: TaskEditReactor.State(title: ""), taskService: taskService)
}


// TaskListViewController.swift

private func bindState(_ reactor: TaskListReactor) {
    reactor.state
        .map { $0.isPresentEditTask }
        .distinctUntilChanged()
        .filter { $0 }
        .map { _ in reactor.getTaskEditReactorForCreatingTask() } // <-
        .bind(onNext: presentTaskEditViewController)
        .disposed(by: disposeBag)
}

private func presentTaskEditViewController(reactor: TaskEditReactor) {
    let storyboard = UIStoryboard(name: "TaskEdit", bundle: nil)
    let viewController = storyboard.instantiateViewController(withIdentifier: TaskEditViewController.className) as! TaskEditViewController
    viewController.reactor = reactor
    present(viewController, animated: true)
}

cf) ReactorKit을 이용한 샘플 앱

- ReactorKit 샘플 앱 - RxDataSources을 이용한 Section, Item 모델 구현 패턴 (with 동적 사이즈 셀)

 

* 전체 코드: https://github.com/JK0369/ExTaskUsingReactorKit/tree/Router

Comments