apple/UIKit & ReactiveX

[ReactorKit] ReactorKit 공부하기 #3 RxTodo 따라잡기 (1)

lgvv 2022. 9. 7. 21:08

ReactorKit 공부하기 #3 RxTodo 따라잡기 (1)

 

 

아무튼 ReactorKit RxTodo 예제 고고

 

이번시간에 하려고 한 것 

 

RxTodo의 Create만 구현하기!

 

아래 폴더로 구성 Service 부분은 현재 예제를 구현하기에 그렇게 필요하지 않아서 패스!

사용한폴더 구조

 

 

🌿 구현 🌿

 

이제 리액터 사용법이 이해가 되기 시작했움!

 

리액터 정리!!

ViewController에서 map을 통해 원하는 Action으로 변경해서 reactor에 전달

reactor에서는 mutate함수를 통해서 이전 상태를 받아서 다음 상태를 반환!

reduce는 해당 로직처리!

그리고 변경된 값은 viewController에서 map을 통해 reactor.state를 가져다가 사용하기!

👉 ViewController

 

//
//  TaskListViewController.swift
//  AppleCollectionView
//
//  Created by Hamlit Jason on 2022/09/07.
//

import UIKit
import ReactorKit

final class TaskListViewController: UIViewController, View {
    var disposeBag = DisposeBag()
    let reactor: TaskListViewReactor
    
    // MARK: - Views
    let addButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: nil, action: nil)
    let tableView = UITableView()
    
    // MARK: - Initialize
    init(reactor: TaskListViewReactor) {
        self.reactor = reactor
        super.init(nibName: nil, bundle: nil)
        self.navigationItem.leftBarButtonItem = self.editButtonItem
        self.navigationItem.rightBarButtonItem = self.addButtonItem
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    // MARK: - Life Cycle
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupTableView()
        bind(reactor: reactor)
        // MARK: - 초기 테이블 뷰 로딩을 위해서
        reactor.action.onNext(TaskListViewReactor.Action.refresh)
    }
    
    func bind(reactor: TaskListViewReactor) {
        // MARK: - Action
        
        self.addButtonItem.rx.tap
            .map { TaskListViewReactor.Action.create } // 1. create액션으로 바꿔서
            .bind(to: reactor.action) // 2. 액션에 bind
            .disposed(by: disposeBag)
                
        // MARK: - State
        
        reactor.state
            .map { _ in reactor.currentState.tasks }
            .bind(to: tableView.rx.items(
                cellIdentifier: TaskCell.identifier,
                cellType: TaskCell.self)
            ) { index, item, cell in
                cell.configureCell(with: item)
            }
            .disposed(by: disposeBag)
    }
}

extension TaskListViewController {
    func setupTableView() {
        view.addSubview(tableView)
        
        tableView.register(
            TaskCell.self,
            forCellReuseIdentifier: TaskCell.identifier
        )
        
        tableView.snp.makeConstraints {
            $0.edges.equalTo(0)
        }
    }
}

여기서 viewDidLoad에 reactor를 한번 보내주는데, 그래야 테이블 뷰 그릴 수 있음!

사실 extension + Rx를 활용하면 viewDidLoad마저도 rx로 바꿀 수 있어서 그렇게 해도 된다.

근데 개인적으로는 LifeCycle과 관련한 코드는 rx로 묶으면 뭔가 보기가 불편해져서 그냥 한눈에 보이라고 저렇게 두는 편!

 

 

👉 Reactor

//
//  TaskListViewReactor.swift
//  AppleCollectionView
//
//  Created by Hamlit Jason on 2022/09/07.
//

import Foundation
import ReactorKit

class TaskListViewReactor: Reactor {
    var initialState = State()
    
    // MARK: - User의 행동을 정의
    enum Action {
        case refresh // 테이블 뷰 리로드
        case create // 테이블 아이템 생성
    }
    
    // MARK: - Action을 받았을 때 변화
    enum Mutation {
        case refreshItem // 테이블뷰 리로드
        case insertItem // 테이블 뷰에 아이템 추가
    }
    
    // MARK: - 변화를 받은 후의 상태를 기록
    struct State {
        // 현재 상태!
        var tasks = [Task]()
    }
    
    // MARK: - 이전 상태와 처리 단위를 받아서 다음 상태를 반환
    func mutate(action: Action) -> Observable<Mutation> {
        switch action {
        case .refresh:
            return Observable.just(.refreshItem)
        case .create:
            return Observable.just(.insertItem)
        }
    }
    
    // MARK: - 변화에 맞게끔 값을 설정해!
    func reduce(state: State, mutation: Mutation) -> State {
        var newState = state
        switch mutation {
        case .refreshItem:
            newState.tasks = Task.readTask()
        case .insertItem:
            Task.createTask()
            newState.tasks = Task.readTask()
        }
        
        return newState
    }
}

 

👉 cell

//
//  TaskCell.swift
//  AppleCollectionView
//
//  Created by Hamlit Jason on 2022/09/07.
//

import UIKit

class TaskCell: UITableViewCell {
    
    // MARK: - Views
    var contentLabel = UILabel()
    
    // MARK: - Initialize
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        setupContentLabel(contentLabel)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func configureCell(with item: Task) {
        self.contentLabel.text = item.content
    }
}

extension TaskCell {
    private func setupContentLabel(_ sender: UILabel) {
        contentView.addSubview(sender)
        
        sender.font = UIFont.preferredFont(forTextStyle: .body, compatibleWith: .current)
        sender.textColor = .systemTeal
        
        sender.snp.makeConstraints {
            $0.centerY.equalToSuperview()
            $0.leading.equalToSuperview().offset(15)
        }
    }
}