apple/UIKit & ReactiveX

[ReactorKit] ReactorKit 공부하기 #4 RxTodo 따라잡기 (2)

lgvv 2022. 9. 8. 02:19

ReactorKit 공부하기 #4 RxTodo 따라잡기 (2)

 

이번에는 지난 시간 create를 했다면, delete를 진행해 볼 예정.

 

해당 파일

AppleCollectionView.zip
2.73MB

 

삭제하기 할 예정

 

🌿 구현 코드 🌿

어려웠던 점: Action의 경우에는 정의하기가 그리 어렵지 않았으나, Mutation을 어떻게 줘야할 지 생각을 많이했었음.

 

 

//
//  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.editButtonItem.rx.tap
            .map { TaskListViewReactor.Action.toggleEditing }
            .bind(to: reactor.action)
            .disposed(by: disposeBag)
        
        self.addButtonItem.rx.tap
            .map { TaskListViewReactor.Action.create } // 1. create액션으로 바꿔서
            .bind(to: reactor.action) // 2. 액션에 bind
            .disposed(by: disposeBag)
        
        self.tableView.rx.itemDeleted
            .map(TaskListViewReactor.Action.deleteTask)
            .bind(to: reactor.action)
            .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)
        
        reactor.state
            .map { $0.isEditing }
            .withUnretained(self)
            .bind { owner, isEditing in
                owner.navigationItem.leftBarButtonItem?.title = isEditing
                ? "✅완료하기✅"
                : "🔮수정하기🔮"
                owner.navigationItem.leftBarButtonItem?.style = isEditing
                ? .done
                : .plain
                owner.tableView.setEditing(isEditing, animated: true)
            }
            .disposed(by: disposeBag)
    }
}

extension TaskListViewController {
    func setupTableView() {
        view.addSubview(tableView)
        tableView.delegate = self
        
        tableView.register(
            TaskCell.self,
            forCellReuseIdentifier: TaskCell.identifier
        )
        
        tableView.snp.makeConstraints {
            $0.edges.equalTo(0)
        }
    }
}
extension TaskListViewController: UITableViewDelegate {
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
    }
}

 

//
//  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 toggleEditing // 토글 에디팅
        case deleteTask(IndexPath) // task 삭제
        case refresh // 테이블 뷰 리로드
        case create // 테이블 아이템 생성
    }
    
    // MARK: - Action을 받았을 때 변화
    enum Mutation {
        case toggleEditing // leftBarItem 토글버튼
        case refreshItem // 테이블뷰 리로드
        case insertItem // 테이블 뷰에 아이템 추가
        case deleteItem(IndexPath) // 테이블 뷰에 아이템 삭제
    }
    
    // MARK: - 변화를 받은 후의 상태를 기록
    struct State {
        var isEditing: Bool = false
        var tasks = [Task]() // 현재 상태!
    }
    
    // MARK: - 이전 상태와 처리 단위를 받아서 다음 상태를 반환
    func mutate(action: Action) -> Observable<Mutation> {
        switch action {
        case .toggleEditing:
            return Observable.just(.toggleEditing)
        case .refresh:
            return Observable.just(.refreshItem)
        case .create:
            return Observable.just(.insertItem)
        case .deleteTask(let indexPath):
            return .just(.deleteItem(indexPath))
        }
    }
    
    // MARK: - 변화에 맞게끔 값을 설정해!
    func reduce(state: State, mutation: Mutation) -> State {
        var newState = state
        switch mutation {
        case .toggleEditing:
            newState.isEditing = !newState.isEditing
        case .refreshItem:
            newState.tasks = Task.readTask()
        case .insertItem:
            Task.createTask()
            newState.tasks = Task.readTask()
        case .deleteItem(let indexPath):
            let task = Task.readTask()[indexPath.row]
            Task.deleteTask(task: task, indexPath: indexPath)
            newState.tasks = Task.readTask()
        }
        return newState
    }
}
//
//  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)
        }
    }
}