apple/UIKit & ReactiveX

RxSwift ch 18. Table & Collection views

lgvv 2022. 1. 18. 16:15

✅ 이번 글은 RxSwift ch18을 기반으로 작성되었습니다.

 

✅ 목차

1️⃣ BaseView 만들어보기 

2️⃣ modelSelected 사용법

3️⃣ Mutiple Cell Types

 

✅ BaseView 만들어보기 

개발을 하다 보니 같은 UI도 여러개의 뷰에서 사용하는 경우가 있어. 하지만 이전에는 이 방법을 몰라서 그냥 복사 붙여넣기로 사용했었는데, 이번에 개발하다보니 BaseView를 두어 사용하면 좋겠다고 생각해서 만들어 보았어.

 

🟠 BaseView

import UIKit
import RxCocoa
import RxSwift
import SnapKit
import Then

struct BaseView {
    var tableView = UITableView().then {
        $0.backgroundColor = .blue
        $0.rowHeight = 60
    }
    
    var multipleBtn = UIButton().then {
        $0.setTitle("multipleBtn", for: .normal)
        $0.backgroundColor = .green
    }
    
    func setBaseConstraints(view: UIView) {
        tableView.snp.makeConstraints {
            $0.edges.equalTo(view.safeAreaLayoutGuide)
        }
        multipleBtn.snp.makeConstraints {
            $0.leading.bottom.trailing.equalTo(view.safeAreaLayoutGuide)
        }
    }
    
    func setAddviews(view: UIView) {
        [tableView, multipleBtn].forEach{ view.addSubview($0) }
    }
}

이런식으로 BaseView를 설정해 주었어. 

그럼 어떻게 사용하느냐? 

 

✅ modelSelected의 사용

🟠 BasicTableViewController.swift 

class BasicTableViewController: UIViewController {
    
    let bv1 = BaseView()
    var bag = DisposeBag()
    
    var actioneBtn = UIButton().then {
        $0.setTitle("actioneBtn", for: .normal)
        $0.backgroundColor = .magenta
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        bv1.setAddviews(view: view)
        [actioneBtn].forEach { view.addSubview($0) }
        
        bv1.setBaseConstraints(view: view)
        actioneBtn.snp.makeConstraints {
            $0.leading.trailing.equalToSuperview()
            $0.bottom.equalTo(bv1.multipleBtn.snp.top)
        }
        
        bv1.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
        
        bindTableView()
    }
    
    private func bindTableView() {
        let cities = Observable.of(["Lisbon", "Copenhagen", "London", "Madrid", "Vienna"])
        
        
        // MARK: Basic Table View
        cities
            .bind(to: bv1.tableView.rx.items) {
                (tableView: UITableView, index: Int, element: String) in
                
                let cell = UITableViewCell(style: .default, reuseIdentifier: "cell")
                cell.textLabel?.text = element
                return cell
            }
            .disposed(by: bag)
        
        // modelSelected(_:)는 model object(cell에 의해 보여지는 것)을 emit 한다.
        bv1.tableView.rx.modelSelected(String.self)
            .bind { model in
                print("\(model) was selected")
            }
            .disposed(by: bag)
        
        // modelDeleted(_:)는 항목 삭제시 이벤트 발생 - tableView:commitEditingStyle:forRowAtIndexPath
        bv1.tableView.rx.modelDeleted(String.self)
            .bind { model in
                print("\(model) was modelDeleted")
            }
            .disposed(by: bag)
        
        // itemDeleted는 항목 삭제시 이벤트 발생
        bv1.tableView.rx.itemDeleted
            .bind { index in
                print("\(index.row) was itemDeleted")
            }
            .disposed(by: bag)
        
        // 악세서리버튼 탭하면 이벤트 발생
        bv1.tableView.rx.itemAccessoryButtonTapped
            .bind { index in
                print("\(index.row) was itemAccessoryButtonTapped")
            }
            .disposed(by: bag)
        
        bv1.multipleBtn.rx.tap
            .bind { self.navigationToMultiple() }
            .disposed(by: bag)
        
        actioneBtn.rx.tap
            .bind { self.navigationToAction() }
            .disposed(by: bag)
            
    }
}

extension UIViewController {
    func navigationToMultiple() {
        let vc = MultipleTableViewController()
//        self.present(vc, animated: true, completion: nil)
        navigationController?.pushViewController(vc, animated: true)
    }
    
    func navigationToAction() {
        let vc = ActionViewController()
//        self.present(vc, animated: true, completion: nil)
        navigationController?.pushViewController(vc, animated: true)
    }
}

이렇게 사용할 수 있어. 이렇게 사용하니, 여러번 반복해서 사용할 수 있어서 정말 좋아..!

 

🎖modelSelected(_:)

이 코드는 셀 안의 model의 값을 방출해주는 코드야. 

선택한 cell의 index를 알고 싶다? -> itemSelected

선택한 cell의 값을 알고 싶다? -> modelSelected

 

modelDeleted의 코드를 추가하면 테이블 뷰에서 삭제할 수 있는 옵션이 자동으로 만들어진다!

 

✅ Mutiple Cell Types

BaseView를 만들어 둔 이유가 여기에 있어. 

다른 코드에서 재사용하기 위해서지~!

import UIKit
import RxCocoa
import RxSwift
import SnapKit
import Then

class MultipleTableViewController: UIViewController {
    
    let bv2 = BaseView()
    var bag = DisposeBag()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        bv2.setAddviews(view: view)
        bv2.setBaseConstraints(view: view)
        bv2.tableView.register(TextCell.self, forCellReuseIdentifier: "titleCell")
        bv2.tableView.register(ImageCell.self, forCellReuseIdentifier: "pairOfImageCell")
        bv2.multipleBtn.isHidden = true
        BindTableView()
    }
    
    private func BindTableView() {
        let observable = Observable<[MyModel]>.just([
            .text("Paris"),
            .pairOfImgae(
                UIImage(systemName: "plus")!,
                UIImage(systemName: "minus")!
            ),
            .text("Seoul"),
            .pairOfImgae(
                UIImage(systemName: "plus")!,
                UIImage(systemName: "minus")!
            )
        ])
        
        observable.bind(to: bv2.tableView.rx.items) {
            (tableView: UITableView, index: Int, element: MyModel) in
            let indexPath = IndexPath(item: index, section: 0)
            
            switch element {
            case .text(let title):
                let cell = self.bv2.tableView.dequeueReusableCell(withIdentifier: "titleCell", for: indexPath) as! TextCell
                cell.titleLabel.text = title
                return cell
            case let .pairOfImgae(firstimage, secondimage):
                let cell = self.bv2.tableView.dequeueReusableCell(withIdentifier: "pairOfImageCell", for: indexPath) as! ImageCell
                cell.leftImage.image = firstimage
                cell.rightImage.image = secondimage
                return cell
            }
        }
        .disposed(by: bag)
    }
}

enum MyModel {
    case text(String)
    case pairOfImgae(UIImage, UIImage)
}

class TextCell: UITableViewCell {
    var titleLabel = UILabel().then { _ in }
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        contentView.backgroundColor = .orange
        [titleLabel].forEach {
            contentView.addSubview($0)
        }
        setContstraints()
        
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func setContstraints() {
        titleLabel.snp.makeConstraints {
            $0.edges.equalToSuperview()
        }
    }
}

class ImageCell: UITableViewCell {
    var leftImage = UIImageView().then { _ in }
    var rightImage = UIImageView().then { _ in }
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        [leftImage, rightImage].forEach {
            contentView.addSubview($0!)
        }
        setContstraints()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func setContstraints() {
        leftImage.snp.makeConstraints {
            $0.top.leading.bottom.equalToSuperview()
            $0.trailing.equalTo(contentView.snp.centerX)
        }
        
        rightImage.snp.makeConstraints {
            $0.top.trailing.bottom.equalToSuperview()
            $0.leading.equalTo(contentView.snp.centerX)
        }
    }
}

 

(좌) BasicTableViewController (우) MultipleTableViewController