κ³°νκΉλ RxSwift κ°μ μ€ step1 μ MVVM + TableView λ‘ λ°κΏλ³Έ μμ ν¬μ€ν μμ View Controller μμμ λ°μ΄ν°λ°μΈλ©, TableViewCell μμ μ΄λ―Έμ§ μ²λ¦¬λ₯Ό λ°λ‘ μ 리ν κΈμ λλ€!
1 ListViewController / 2 DetailViewController / 3 MemberCell
1. ListViewController - λ©€λ² λ°μ΄ν°λ₯Ό 리μ€νΈ(TableView) λ‘ λ³΄μ¬μ£Όλ νλ©΄
- ViewController λ viewModel μ κ°κ³ , μ΄λ₯Ό ν΅ν΄ λ°μ΄ν°λ₯Ό μ²λ¦¬ν©λλ€ !
- ViewController μμ μ κ²½μ¨μΌνλ μμ μ λ°μ΄ν°κ° λ³κ²½λμμ λ, UI μ λ°μ΄νΈλ₯Ό μ΄λ»κ² ν΄μΌν μ§? μ λν μμ μ λλ€.
- setBinding λ©μλμ λͺ¨μλ λ΄μ©μ΄ ν΅μ¬μ λλ€ β¨
π setBinding λ©μλ
1. TableView μ λ°μ΄ν° 보μ¬μ£ΌκΈ°
viewModel.members -- (bind) --> tableView.rx.items(cellIdentifier: cellType:)
UITableViewDataSource νλ‘ν μ½μμ νμ λ©μλλ₯Ό ꡬνν΄ λ°μ΄ν°λ₯Ό νννλ κ²κ³Ό λμΌνλ€.
2. activity Indicator μ λλ©μ΄μ λνλ΄κΈ°
viewModel.members -- map {$0.isEmpty} -- (subscribe) --> setVisibleWithAnimation(v: s:)
Behavior Subject μ΄κΈ° λλ¬Έμ ꡬλ μ νμλ§μ λΉ λ°μ΄ν°κ° λ°μμμ§λλ€. μ± μ€νμ μλμΌλ‘ indicator κ° λνλλλ‘ νκ³ , λ°μ΄ν°κ° λ°μμμ‘μ λ indicator κ° μ¬λΌμ§λλ‘ νκΈ° μν΄ map operator λ₯Ό μΆκ°νμ΅λλ€.
3. TableView Cell μ ν μ, Detail View Controller λ‘ μ΄λνκΈ°
tableView.rx.modelSelected -- (subscribe) --> presentDetail(member)
TableView μ Cell μ μ ννμ λ, μ€νλ λμλ€μ λ³΄ν΅ UITableViewDelegate νν λ‘μ½μ didSelectRowAt λ©μλμ ꡬνν©λλ€. RxCocoa μμ μ 곡λλ tableView.rx.itemSelected νΉμ modelSelected λ₯Ό μ΄μ©νλ©΄ μμ λ©μλλ₯Ό ꡬνν¨κ³Ό λμΌν©λλ€!
- tableView.rx.itemSelected : μ νλ Cell μμΉμ IndexPath λ₯Ό μ λ¬νλ€.
- tableView.rx.modelSelected : μ νλ Cell μμΉμ Model μ μ λ¬νλ€.
* μ°Έκ³ *
* viewModel.members : BehaviorSubject, κΈ°λ³Έ κ° νΉμ μ΄μ κ°μ μ λ¬λ°λ Subject.
* observe(on:) : λ€μ observable μ΄ μνλ μ°λ λλ₯Ό μ§μ νλ€. UI μμ
μ MainSchedular.instance μμ.
* dispose(by: disposeBag) : ViewController κ° μ¬λΌμ§ λ, disposable λ€λ ν¨κ» λ©λͺ¨λ¦¬μμ ν΄μ νκΈ° μν¨.
* bind : RxCocoa μμ μ 곡νλ λ©μλ, subscribe λ‘ κ°μ μ§μ λμ
νλ κ² λμ bind λ‘ Outlet λ³μμ μ°κ²°κ°λ₯νλ€.
* κΈμ΄ λ무 κΈΈμ΄μ Έμ μ 체 μ½λλ λ§ν¬λ‘ μ°κ²°ν κ²μ
class ViewController: UIViewController {
@IBOutlet var timerLabel: UILabel!
@IBOutlet var tableView: UITableView!
@IBOutlet var activityIndicator: UIActivityIndicatorView!
let viewModel = MemberViewModel.shared
let disposeBag = DisposeBag()
let cellId = "MemberCell"
override func viewDidLoad() {
super.viewDidLoad()
setTimer()
setBinding()
}
private func setBinding() {
// tableView datasource -- λ°μ΄ν° κ°μ, μ
μ λ€μ΄κ° λ΄μ©
viewModel.members
.observe(on: MainScheduler.instance)
.filter { !$0.isEmpty }
.bind(to: tableView.rx.items(cellIdentifier: cellId, cellType: MemberCell.self)) { index, item, cell in
cell.updateUI(member: item)
}
.disposed(by: disposeBag)
// activityIndicator
viewModel.members
.observe(on: MainScheduler.instance)
.map { $0.isEmpty }
.subscribe(onNext: { [weak self] visible in
self?.setVisibleWithAnimation(self?.activityIndicator, visible)
})
.disposed(by: disposeBag)
// tableView delegate
// tableView.rx.itemSelected -> indexPath
tableView.rx.modelSelected(Member.self)
.observe(on: MainScheduler.instance)
.subscribe(onNext: { [weak self] member in
self?.presentDetail(of: member)
})
.disposed(by: disposeBag)
}
// Other methods ...
}
μ¬κΈ°μ, bind + observe(on: Main) μ μ‘°ν© λμ drive λ₯Ό μ¬μ©ν μλ μμ΅λλ€.
bind μ drive λͺ¨λ λ΄λΆμ μΌλ‘ subscribe λ₯Ό μ°κ³ μμΌλ©°, drive λ 무쑰건 MainThread μμ λμνλ€.
μ΄μ λλΆμ΄ asDriver(onErrorJustReturn:) λ±μ μ΄μ©νλ©΄ μλ¬κ° λ¬μ λ, μ²λ¦¬ν κΈ°λ³Έκ°μ μ€μ ν μ μλ€.
λν Subject λμ λμ΄μ§μ§ μλ νμ
μ Relay λΌκ³ νλ€. μλ¬κ° λκ±°λ completed λμ§ μκ³ , νμ onNext λ§ μκΈ° λλ¬Έμ onNext κ° μλ accept λ‘ νννλ€.
* UI μμ
μ νμ UI Thread (= Main) μμ λμκ°μΌ νκ³ , μλ¬κ° λλ€κ³ μ°κ²°μ΄ λμ΄μ§λ©΄ μλλ νΉμ§μ΄ μλ€.
* RxCocoa κ΄λ ¨λμ΄μλ μ°¨μ°¨ λ°λ‘ μ 리νκ³ , νμνλ€λ©΄ μ½λλ₯Ό μμ ν΄λ³΄κ² μ΅λλ€! π₯Ί
μ 리νμλ©΄, UI μ κ΄λ ¨λ μ½λλ Relay + drive μ μ‘°ν©μΌλ‘ νΈνκ³ μμ νκ² μ¬μ©ν μ μλ€.
λ¨μν κ°μ μ¬μ©νκ±°λ μ μ₯νκΈ° μν΄μλ Subject + bind/ subscribe μ μ‘°ν©μ μ΄μ©νλ€.
2. DetailViewController
- DetailViewController μμλ λμΌν ViewModel μ μ΄μ©νμ΅λλ€.
- ListVC μμ TableView μ μ μ΄ ν΄λ¦λμμ λ, μ νλ model μ μ λ¬λ°μ ViewModel λ΄μ selectedMember λ³μμ κ°μΌλ‘ ν λΉν©λλ€!
- DetailVC μμλ viewModel.selectedMember λ₯Ό μ΄μ©ν΄ UI λ₯Ό μ λ°μ΄νΈν©λλ€.
- String -> UILabel.text λ‘μ λ³νμ κ°λ¨νμ§λ§, avatar μ΄λ―Έμ§μ κ²½μ° url μ ν΅ν΄ μ΄λ―Έμ§λ‘ λ³νν΄μ€μΌν©λλ€.
π‘ DetailVC μμ μ΄λ―Έμ§ λ€μ΄λ‘λ μμ
Observable<Data> -- (bind) --> avatarImage.rx.image
JSON data λ₯Ό λ€μ΄λ°μ λ μ΄μ©ν APIManger.fetchData(url:) λ©μλλ₯Ό ν΅ν΄, μ΄λ―Έμ§λ₯Ό λΉλκΈ°λ‘ λ€μ΄λ‘λ νλ μμ λ μνν΄μ€λλ€. λ©μλλ₯Ό ν΅ν΄ μ λ¬λ°μ Observable μ λν΄ subscribe (bind) λ₯Ό ν΄μ£Όμ΄ μ΄λ―Έμ§λ₯Ό λ³κ²½ν΄μ£Όλ κ²μ λλ€!
class MemberDetailViewController: UIViewController {
// UILabels ...
@IBOutlet weak var avatarImage: UIImageView!
let viewModel = MemberViewModel.shared
var disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
// UILabel.text μ²λ¦¬
setImage(url: member.avatar) // binding
}
func setImage(url: String) {
let orginalImageURL = url.replacingOccurrences(of: "size=50x50&", with: "")
// Observable<Data> -> UIImage(data: )
APIManager.fetchData(url: orginalImageURL)
.observe(on: MainScheduler.instance)
.map { UIImage(data: $0)! }
.bind(to: avatarImage.rx.image)
.disposed(by: disposeBag)
}
}
3. MemberCell
- Cell μμ UI μ κ΄λ ¨λ, μ΄λ―Έμ§ λ€μ΄λ‘λ μμ λ±μ DetailVC μ λΉμ·νμ§λ§, Cell μ μ¬μ¬μ©λλ νΉμ±μ΄ μκΈ° λλ¬Έμ μ΄λ₯Ό κ³ λ €ν΄μ£Όμ΄μΌν©λλ€!
- TableView μμ Cell μ νλ²μ 보μ¬μ§λ κ°μλ§νΌ init λκ³ , μ€ν¬λ‘€ λ λμλ μ΄μ μ μμ±λλ Cell μ΄ μ¬μ¬μ©λ©λλ€. κ·Έλλ§λ€ νΈμΆλλ λ©μλκ° prepareForReuse() μΈ κ²μ΄κ΅¬μ!
- cell λ§λ€ μ΄λ―Έμ§λ₯Ό λ€μ΄λ‘λ λ°κ³ , μ΄λ₯Ό disposeBag μ λ΄κ³ μλ μμ μ DetailVC μ λμΌνλ°μ. cell μ΄ μ¬μ¬μ©λ λλ§λ€ DisposeBag μ μ¬ν λΉν΄μ£Όμ΄ disposables μ λ²λ €μ£Όλ μμ μ ν΄μ€λλ€. λ§μ½ DisposeBag μ μ¬ν λΉνμ§ μλλ€λ©΄, μ¬μ¬μ©λλ μ λ€μμ subscribe(bind) μ΄ν λ§λ€μ΄μ§ disposables λ₯Ό μ λΆ λ€ κ°μ§κ³ μκ² λμ΄ λ©λͺ¨λ¦¬μ λ¨μμμκ²λλ€ π΅π«
- κ·Έλ¦¬κ³ , cell μ΄ μ¬μ¬μ©λκΈ° λλ¬Έμ image κ° λ€μ΄λ‘λλκΈ° μ μλ μ΄μ cell μ image λ₯Ό 보μ¬μ€λλ€. μ΄λ₯Ό λ°©μ§νκΈ° μν΄μ image = nil μΌλ‘ ν λΉνμ΅λλ€! (μλ μ΄λ―Έμ§ μ°Έκ³ )
class MemberCell: UITableViewCell {
// IBOutlets
var disposeBag = DisposeBag()
override func prepareForReuse() {
super.prepareForReuse()
// cell reuse μμ μ΄κΈ°νλλλ‘ μμ
νκΈ°
avatarImage.image = nil
disposeBag = DisposeBag()
}
// Other methods ...
}
μ°Έκ³ λ§ν¬
- κ³°νκΉλ κ°μ [RxSwift, 4μκ° μμ λΉ λ₯΄κ² μ΅ν μ€λ¬΄μ μ¬μ©νκΈ°]
- RxCocoa λ₯Ό μ΄μ©ν UITableView μ DataSource & Delegate
- [RxSwift] bind, subscribe, drive
π νΉμ λΆμ‘±νκ±°λ μλͺ»λ λ΄μ©μ΄ μμΌλ©΄ λκΈλ‘ μλ €μ£ΌμΈμ π§
'π iOS > RxSwift' μΉ΄ν κ³ λ¦¬μ λ€λ₯Έ κΈ
[iOS/RxSwift] RxDataSource - UITableView, UICollectionView (2) | 2021.10.17 |
---|---|
[iOS] MVVM & RxSwift μ€ν°λ W2 - TableView λ§λ€μ΄λ³΄κΈ° (0) | 2021.07.15 |
[iOS] RxSwift - Subject vs Observable (0) | 2021.07.14 |
[iOS] RxSwift - Observables μμ±κ³Ό ꡬλ , μλͺ μ£ΌκΈ°, operator (0) | 2021.07.11 |
[iOS] MVVM & RxSwift μ€ν°λ W1 - Observable μλͺ μ£ΌκΈ°μ μ°λ λκ΄λ¦¬ (0) | 2021.07.11 |