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)
}
}
}