UI Binding

๐ŸŽ›๏ธ RxCocoa UI Binding โ€” From Events to Declarative Updates

"โ€œํƒ€๊นƒ-์•ก์…˜์€ ์•ˆ๋…•, ์ด์ œ๋Š” Rx์™€ ํ•จ๊ป˜!โ€

RxCocoa๋Š” UIKit ์ปดํฌ๋„ŒํŠธ์— Reactive extensions๋ฅผ ์ œ๊ณตํ•ด ํ„ฐ์น˜ยทํ…์ŠคํŠธยท์Šคํฌ๋กค ๋“ฑ ์ด๋ฒคํŠธ ์ŠคํŠธ๋ฆผ์„ ์‰ฝ๊ฒŒ ๊ตฌ๋…ํ•˜๊ณ , ์†์„ฑ์„ ๋ฐ”์ธ๋”ฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๋ฌธ์„œ์—์„œ๋Š” ๊ฐ€์žฅ ๋งŽ์ด ์“ฐ์ด๋Š” UIButton.rx.tap, UITextField.rx.text, UITableView.rx.items ๋ฐ”์ธ๋”ฉ ํŒจํ„ด์„ ์ •๋ฆฌํ•ฉ๋‹ˆ๋‹ค.


1๏ธโƒฃ UIButton โ€” Reactive Tap

loginButton.rx.tap
    .throttle(.milliseconds(500), scheduler: MainScheduler.instance)
    .bind(to: viewModel.loginTap)
    .disposed(by: bag)
  • rx.tap = Observable<Void>

  • Tap debounce/throttle๋กœ ์ค‘๋ณต ํด๋ฆญ ๋ฐฉ์ง€

  • withUnretained(self) ํ™œ์šฉ ์˜ˆ:

    button.rx.tap
        .withUnretained(self)
        .subscribe(onNext: { vc, _ in vc.dismiss(animated:true) })

2๏ธโƒฃ UITextField & UILabel โ€” Twoโ€‘way Binding

// View โ†’ ViewModel
emailField.rx.text.orEmpty
    .bind(to: viewModel.email)
    .disposed(by: bag)

// ViewModel โ†’ View
viewModel.email
    .bind(to: emailField.rx.text)
    .disposed(by: bag)
  • .orEmpty : Optional String โ†’ String ๋ณ€ํ™˜

  • RxCocoa์—๋Š” ๋‚ด์žฅ๋œ ์–‘๋ฐฉํ–ฅ ๋ฐ”์ธ๋”ฉ ๋„์šฐ๋ฏธ๊ฐ€ ์—†์œผ๋ฏ€๋กœ, ๋ณต์žกํ•œ ๊ฒฝ์šฐ์—๋Š” BindRelay ๋˜๋Š” ์‚ฌ์šฉ์ž ์ •์˜ ์—ฐ์‚ฐ์ž๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•˜์„ธ์š”.


3๏ธโƒฃ UITableView โ€” items(cellIdentifier:)

A. Simple Static Cell

dataObservable
    .bind(to: tableView.rx.items(cellIdentifier: "Cell")) { index, model, cell in
        cell.textLabel?.text = model.title
    }
    .disposed(by: bag)

B. Sectioned Table (RxDataSources)

let dataSource = RxTableViewSectionedReloadDataSource<SectionModel<String, Item>>(configureCell: ...)

sectionsObservable
    .bind(to: tableView.rx.items(dataSource: dataSource))
    .disposed(by: bag)
  • RxDataSources๋Š” ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์ธ์‹ํ•˜๋Š” animated ์ฒ˜๋ฆฌ ๋“œ๋ผ์ด๋ฒ„๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.


4๏ธโƒฃ CollectionView Quick Note

items.bind(to: collectionView.rx.items(cellIdentifier: "Cell", cellType: MyCell.self)) { row, item, cell in
    cell.configure(item)
}.disposed(by: bag)

5๏ธโƒฃ ControlProperty & Driver

Feature

ControlEvent

ControlProperty

Driver

Source

UI ์ด๋ฒคํŠธ (tap)

UI ์†์„ฑ (text)

Any Observable

Error

never

never

never

Scheduler

Main

Main

Main

UI Binding Output์€ Driver ๊ถŒ์žฅ (Main thread, shareReplay(1))


6๏ธโƒฃ Memory & Thread Safety

  • RxCocoa๋Š” ์ž๋™์œผ๋กœ MainScheduler์—์„œ ๊ตฌ๋…ํ•ฉ๋‹ˆ๋‹ค.

  • ๋ฐ”์ธ๋”ฉ ํ™•์žฅ ํ•จ์ˆ˜(bind(to:))๋Š” ๋Œ€์ƒ ๊ฐ์ฒด๊ฐ€ ํ•ด์ œ๋  ๋•Œ ์ž๋™์œผ๋กœ dispose๋ฉ๋‹ˆ๋‹ค.


7๏ธโƒฃ Mini Quiz

  1. rx.tap์„ Driver๋กœ ๋ณ€ํ™˜ํ•˜๋ ค๋ฉด?

  2. tableView.rx.modelSelected์˜ ๋ฉ”๋ชจ๋ฆฌ ์ฃผ์˜์ ?

  3. rx.text๊ฐ€ Optional์ธ ์ด์œ ๋Š”?

Answers
  1. button.rx.tap.asDriver() โ€” ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— onErrorJustReturn(())์€ ๋ถˆํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

  2. ์„ ํƒ ํ๋ฆ„(select stream)์€ ์…€์„ ์œ ์ง€(retain)ํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ์‚ฌ์šฉ ํ›„ .bind(onDisposed:) ๋˜๋Š” withUnretained๋ฅผ ์‚ฌ์šฉํ•ด ์ˆœํ™˜ ์ฐธ์กฐ๋ฅผ ๋ฐฉ์ง€ํ•˜์„ธ์š”.

  3. UITextField์˜ ํ…์ŠคํŠธ๋Š” placeholder๊ฐ€ ํ‘œ์‹œ๋  ๋•Œ nil์ผ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, .orEmpty๋ฅผ ์‚ฌ์šฉํ•ด ๋น„์˜ต์…”๋„ ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ํ•˜์„ธ์š”.


๋‹ค์Œ โ–ถ๏ธ gesture_keyboard.md ๋กœ ์ด๋™ํ•ด Gesture Recognizer & Keyboard reactive ํŒจํ„ด์„ ๋ฐฐ์›Œ๋ด…์‹œ๋‹ค. ๐Ÿš€

Last updated