Skip to content

Latest commit

ย 

History

History
341 lines (270 loc) ยท 13.6 KB

220314_MVVM,_Rx,_changed,_coordinator,_observable.md

File metadata and controls

341 lines (270 loc) ยท 13.6 KB

220314 MVVM, Rx, changed, coordinator, observable

TIL (Today I Learned)

3์›” 14์ผ (์›”)

ํ•™์Šต ๋‚ด์šฉ

  • MVVM ํ™œ๋™ํ•™์Šต
  • RxCocoa - changed
  • escaping closure๋ฅผ RxSwift๋กœ ๋ฆฌํŒฉํ† ๋งํ•ด๋ณด๊ธฐ
  • Coordinator ํŒจํ„ด์ด๋ž€?

ย 

๊ณ ๋ฏผํ•œ ์  / ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

[3์›” 14์ผ ํ€ด์ฆˆ ์˜ค๋‹ต๋…ธํŠธ]

1์ดˆ๋‹น 120ํ”„๋ ˆ์ž„์œผ๋กœ ์‹คํ–‰๋˜๋Š” ํ”„๋กœ์„ธ์Šค์ด๋ฉฐ Constranints, Layout, Display์˜ 3๊ฐœ Phase๋กœ ๊ตฌ์„ฑ๋˜๋Š” ๊ฒƒ์€?

  • Render Loop

๊ฐ์ฒด์ง€ํ–ฅ ํ”„๋กœ๊ทธ๋ž˜๋ฐ OOP์˜ 4๋Œ€ ํŠน์ง•

  • ์บก์Šํ™”, ์ƒ์†, ๋‹คํ˜•์„ฑ, ์ถ”์ƒํ™”

Render Loop์˜ ๋‹ค์Œ update Cycle์—์„œ Constraints์— ๋Œ€ํ•œ ๊ฐฑ์‹ ์ด ์ผ๊ด„์ ์œผ๋กœ ์ผ์–ด๋‚˜๋„๋ก ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ์˜ˆ์•ฝํ•˜๋Š” ๋ฉ”์„œ๋“œ๋Š”?

  • setNeedsUpdateConstraints
    • ๋‹ค์Œ ์—…๋ฐ์ดํŠธ ์‚ฌ์ดํด์—์„œ ์ œ์•ฝ์กฐ๊ฑด์— ๋Œ€ํ•œ ๊ฐฑ์‹ ์ด ์ผ๊ด„์ ์œผ๋กœ ์ผ์–ด๋‚˜๋„๋ก ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ์˜ˆ์•ฝํ•œ๋‹ค

์‹ค์ œ๋กœ ์—…๋ฐ์ดํŠธ ์‹คํ–‰(์žฌ์ •์˜ํ•  ๋ฟ ์ง์ ‘ ํ˜ธ์ถœํ•˜๋ฉด ์•ˆ๋œ๋‹ค!)

  • updateConstraints

๋‹ค์Œ ์—…๋ฐ์ดํŠธ ์ฃผ๊ธฐ์— ์—…๋ฐ์ดํŠธ๊ฐ€ ํ•„์š”ํ•œ ๊ฒƒ์œผ๋กœ ๋ช…์‹œ์ ์œผ๋กœ ํ‘œ์‹œ

  • setNeedsUpdateConstraints

์—…๋ฐ์ดํŠธ๊ฐ€ ํ•„์š”ํ•˜๋ฉด ์ฆ‰์‹œ ์—…๋ฐ์ดํŠธ

  • updateConstraintsIfNeeded

ํ˜„์žฌ ๋งŽ์ด ์‚ฌ์šฉ๋˜๊ณ ์žˆ๋Š” HTTP์˜ ๋ฒ„์ „์€?

  • 1.1 ๋ฒ„์ „
    • 1997๋…„ 1์›”์— ์ตœ์ดˆ ๊ฐœ๋ฐœ๋œ ํ‘œ์ค€ ํ”„๋กœํ† ์ฝœ์ด๋ฉฐ ํ˜„์žฌ๋„ ๋งŽ์ด ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค.

์ž์‹ ํด๋ž˜์Šค์—์„œ ํ”„๋กœํผํ‹ฐ๋ฅผ override ํ•˜๋ ค๊ณ ํ•˜๋Š”๋ฐ, ์—๋Ÿฌ๋‚˜์ง€ ์•Š๋Š” ๊ตฌ๋ฌธ์€?

์žฌ๊ท€ํ•จ์ˆ˜๋ฅผ ์ฃผ์˜ํ•ด์„œ ์‚ฌ์šฉํ•˜์ง€ ๋ชปํ•˜๋ฉด ์Šคํƒ ์˜ค๋ฒ„ ํ”Œ๋กœ์šฐ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

  • ํ•จ์ˆ˜๋Š” ์Šคํƒ์˜์—ญ์—์„œ ์‹คํ–‰๋˜๋Š”๋ฐ ์žฌ๊ท€ํ•จ์ˆ˜๋Š” ์ž๊ธฐ์ž์‹ ์„ ๋ฐ˜๋ณต์ ์œผ๋กœ ํ˜ธ์ถœํ•˜๊ฒŒ ๋œ๋‹ค. ๋”ฐ๋ผ์„œ ๋ฐ˜๋ณต์ ์ธ ํ•จ์ˆ˜ ํ˜ธ์ถœ๋กœ ์ธํ•œ ์Šคํƒ ์˜ค๋ฒ„ ํ”Œ๋กœ์šฐ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

async await ๊ตฌ๋ฌธ์„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•

  1. async
  2. throws
  3. try
  4. await


[์›จ๋”์™€ ํ•จ๊ป˜ํ•˜๋Š” MVVM ์‹ค์Šต!]

  • ViewModel์˜ ๊ฒฝ์šฐ UIKit์„ import ํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•˜๋‹ค.
import Foundation

class YagomViewModel {
    enum Color {
        case red
        case blue
    }
    private(set) var currentBackgroundColor: Color? {
        didSet {
            listener?()
        }
    }
    private(set) var data: [String] = [] {
        didSet {
            reloadData?()
        }
    }
    
    private var listener: (() -> Void)?
    private var reloadData: (() -> Void)?

    
    func bind(_ closure: @escaping () -> Void) {
        listener = closure
    }
    
    func reloadDataBind(_ closure: @escaping () -> Void) {
        reloadData = closure
    }
    
    func didTapRedButton() {
        changedColor(color: .red)
    }
    
    func didTapblueButton() {
        changedColor(color: .blue)
    }
    
    func didTapApiButton() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            self.data.append(contentsOf: ["์•ผ", "๊ณฐ", "์•„", "์นด", "๋ฐ", "๋ฏธ"])
        }
    }
    
    private func changedColor(color: Color) {
        currentBackgroundColor = color
    }
}
  • View๋Š” ViewModel์—๊ฒŒ ์ด๋ฒคํŠธ๋งŒ์„ ์ „๋‹ฌํ•œ๋‹ค.
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var redButton: UIButton!
    @IBOutlet weak var blueButton: UIButton!
    @IBOutlet weak var apiButton: UIButton!
    @IBOutlet private weak var tableView: UITableView!
    
    var viewModel = YagomViewModel()
    override func viewDidLoad() {
        super.viewDidLoad()
        setUpBindings()
        tableView.dataSource = self
    }
    
    // ๋ทฐ๋ชจ๋ธ๊ณผ ๋ทฐ๋ฅผ ๋ฐ”์ธ๋”ฉ
    private func setUpBindings() {
        viewModel.bind { [weak self] in
            self?.view.backgroundColor = self?.viewModel.currentBackgroundColor == .red ? .systemRed : .systemBlue
        }
        viewModel.reloadDataBind { [weak self] in
            self?.tableView.reloadData()
        }
    }

    // ๋ทฐ๋ชจ๋ธ์—๊ฒŒ ์ด๋ฒคํŠธ๋งŒ ์ „๋‹ฌ~
    @IBAction func didTapRedButton(_ sender: UIButton) {
        viewModel.didTapRedButton()
    }

    @IBAction func didTapblueButton(_ sender: UIButton) {
        viewModel.didTapblueButton()
    }
    
    @IBAction func didTapApiButton(_ sender: UIButton) {
        viewModel.didTapApiButton()
    }
}

extension ViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell") else {
            return UITableViewCell()
        }
        
        cell.textLabel?.text = viewModel.data[indexPath.row]
        return cell
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return viewModel.data.count
    }
}
  • ๋‚ด ์ž…๋ง›๋Œ€๋กœ ์„ค๊ณ„ํ•ด๋ณธ MVVM ์ฝ”๋“œ...
  • ์›จ๋”๋Š” Delegate ํŒจํ„ด์œผ๋กœ ๊ตฌํ˜„ํ•ด์ฃผ์…จ์ง€๋งŒ ๋‚˜๋Š” Observable์ด ํŽธํ•ด์„œ Observable ํ˜•ํƒœ๋กœ ๊ตฌํ˜„ํ•ด๋ณด์•˜๋‹ค.
  • ๋ฐ์ดํ„ฐ๋ฅผ ๊ด€์ฐฐํ•˜๋ฉด์„œ, ๋ณ€ํ™”ํ•  ๋•Œ๋งˆ๋‹ค ํŠน์ • ํด๋กœ์ €๋ฅผ ์‹คํ–‰ํ•ด์ค„ ํ”„๋กœํผํ‹ฐ ์˜ต์ €๋ฒ„(currentBackgroundColor, data)๋ฅผ ์ƒ์„ฑํ•˜๊ณ , ํ•ด๋‹น ํด๋กœ์ €์— ๊ฐ’์„ ํ• ๋‹นํ•ด์ค„ ๋ฉ”์†Œ๋“œ(bind, reloadDataBind)๋ฅผ ๋งŒ๋“ค์–ด์ฃผ์—ˆ๋‹ค.
  • ViewController์—์„œ ViewModel์„ ์ƒ์„ฑํ•˜๊ณ , ๋ฐ”์ธ๋”ฉ ์ž‘์—…์„ ํ•ด์ฃผ์—ˆ๋‹ค.
    • ์ปฌ๋Ÿฌ๊ฐ€ ๋ฐ”๋€” ๊ฒฝ์šฐ ๋ฐฐ๊ฒฝ์ƒ‰์„ ๋ฐ”๊พธ๋„๋ก ํ•˜๊ณ , ๋ฐฐ์—ด์ด ๋ณ€ํ™”ํ•  ๋•Œ๋งˆ๋‹ค reloadData() ๋ฉ”์†Œ๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋„๋ก ๋ฐ”์ธ๋”ฉ ํ•ด์ฃผ์—ˆ๋‹ค.
  • ์ดํ›„ ๊ฐ ๋ฒ„ํŠผ๋งˆ๋‹ค ViewModel์—๊ฒŒ ์ด๋ฒคํŠธ๋ฅผ ์ „๋‹ฌํ•˜๋„๋ก ViewModel์˜ ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ด์ฃผ์—ˆ๋‹ค.
  • TableView๋Š” ViewModel์˜ data๋ผ๋Š” ๋ฐฐ์—ด๋กœ ๊ตฌ์„ฑํ•ด์ฃผ์—ˆ๋‹ค.
  • ์ดํ›„ ์‹คํ–‰ํ•˜๋ฉด ๊ฐ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅผ๋•Œ, ViewModel์—๊ฒŒ ์ด๋ฒคํŠธ๊ฐ€ ์ „๋‹ฌ๋˜๊ณ , ViewModel์—์„œ๋Š” ๊ฐ’์„ ๋ณ€๊ฒฝํ•œ๋‹ค. ๋ณ€๊ฒฝ๋˜๋ฉด didSet์— ๋“ฑ๋ก๋˜์–ด์žˆ๋Š” ํด๋กœ์ €๊ฐ€ ์‹คํ–‰๋œ๋‹ค. ํ•ด๋‹น ํด๋กœ์ €๋Š” ViewController์—์„œ ๋ฐ”์ธ๋”ฉ ์ฒ˜๋ฆฌํ•ด์ค€ ์ž‘์—…๋“ค์ด๋‹ค.

์›จ๋” QnA

  • MVVM์„ ์ž˜~์“ฐ๋ฉด ์ข‹๋‹ค.
  • ์‹ ์ž…๋“ค ๋Œ€๋ถ€๋ถ„ MVVM์„ ์ œ๋Œ€๋กœ ์“ฐ์ง€ ๋ชปํ•œ๋‹ค. MVVM์„ ์™œ์“ฐ๋Š”์ง€ ์•Œ์•„๋ณด๊ณ  ์ด์œ ๋ฅผ ๊ฐ–๊ณ  ํ™œ์šฉํ•˜์ž.
  • ๊ทธ๋ฆฌ๊ณ  ViewModel์ด ViewModel์Šค๋Ÿฝ๊ฒŒ ์˜ฌ๋ฐ”๋ฅธ ์—ญํ• ์„ ํ•˜๊ณ ์žˆ๋Š”์ง€ ์ค‘์š”ํ•˜๋‹ค.
  • ํ…Œ์ŠคํŠธ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋„๋ก ์—ญํ• ์„ ์ž˜ ๋ถ„๋ฆฌํ•˜๋Š” ๊ฒƒ๋„ ์ค‘์š”
    • ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜...๋‚ด์šฉ

๋Š๋‚€์ 

  • ๋„ˆ๋ฌด ์–ด๋ ต์ง€๋งŒ... ๊ณ„์† ๋ฐ˜๋ณตํ•˜๋‹ค๋ณด๋ฉด ์–ธ์  ๊ฐ€ ๊นจ๋‹ฌ์Œ์ด ์˜ค๊ฒ ์ง€...?
  • ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜๋Š” ์•„์ง๋„ ์–ด๋ ค์›Œ...

[Coordinator ํŒจํ„ด]

Coordinator๋ž€?

  • ํ•˜๋‚˜ ์ด์ƒ์˜ ๋ทฐ ์ปจํŠธ๋กค๋Ÿฌ๋“ค์—๊ฒŒ ์ง€์‹œ๋ฅผ ๋‚ด๋ฆฌ๋Š” ๊ฐ์ฒด์ด๋ฉฐ, ์—ฌ๊ธฐ์„œ ๋งํ•˜๋Š” ์ง€์‹œ๋Š” View์˜ ํŠธ๋žœ์ง€์…˜์„ ์˜๋ฏธํ•œ๋‹ค.
  • ์ฆ‰, Coordinator๋Š” ์•ฑ ์ „๋ฐ˜์— ์žˆ์–ด ํ™”๋ฉด ์ „ํ™˜ ๋ฐ ๊ณ„์ธต์— ๋Œ€ํ•œ ํ๋ฆ„์„ ์ œ์–ดํ•˜๋Š” ์—ญํ• ์„ ํ•œ๋‹ค.

์ˆ˜ํ–‰๊ธฐ๋Šฅ

  • ํ™”๋ฉด ์ „ํ™˜์— ํ•„์š”ํ•œ ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ(ViewController, ViewModel ...)
  • ์ƒ์„ฑํ•œ ์ธ์Šคํ„ด์Šค์˜ ์ข…์†์„ฑ ์ฃผ์ž…(DI)
  • ์ƒ์„ฑ๋œ ViewController์˜ ํ™”๋ฉด ์ „ํ™˜

์™œ ์‚ฌ์šฉํ• ๊นŒ?

  • ViewController๊ฐ€ ๋‹ด๋‹นํ•˜๋˜ ํ™”๋ฉด ์ „ํ™˜ ์ฑ…์ž„์„ Coordinator๊ฐ€ ๋‹ด๋‹นํ•˜๊ฒŒ๋˜๋ฉด์„œ, ํ™”๋ฉด์ „ํ™˜ ์‹œ ViewController์—์„œ ์‚ฌ์šฉํ•  ViewModel์„ ํ•จ๊ป˜ ์ฃผ์ž…ํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค.
  • ๋˜ํ•œ ํ™”๋ฉด ์ „ํ™˜์— ๋Œ€ํ•œ ์ฝ”๋“œ๋ฅผ ๋”ฐ๋กœ ๊ด€๋ฆฌํ•˜๊ฒŒ ๋˜๋ฉด์„œ ์žฌ์‚ฌ์šฉ๊ณผ ์œ ์ง€๋ณด์ˆ˜๋ฅผ ํŽธํ•˜๊ฒŒ ๋งŒ๋“ค์–ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ์ฃผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค.
  • ์ •๋ฆฌํ•˜์ž๋ฉด Coordinator๋Š” ํ™”๋ฉด ์ „ํ™˜ ์ œ์–ด ๋‹ด๋‹น๊ณผ ์˜์กด์„ฑ ์ฃผ์ž…์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ด์ฃผ๋Š” ํ—ˆ๋ธŒ๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋  ๊ฒƒ ๊ฐ™๋‹ค.

๋Š๋‚€์ 

  • ์•„ ์ผ๋‹จ ๋งŒ๋“ค์–ด๋ณด๊ธด ํ–ˆ๋Š”๋ฐ... ์ œ๋Œ€๋กœ ๋งŒ๋“ ๊ฑด์ง€ ๋ชจ๋ฅด๊ฒ ๋‹ค... ์™ค์ผ€ ์–ด๋ ต์ง€
  • ๊ทธ๋ž˜๋„ ๋งŒ๋“ค๊ณ ๋‚˜๋‹ˆ๊นŒ ํ™”๋ฉด์ „ํ™˜์„ viewModel ๋‹จ์—์„œ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์–ด ๊ฐœ์‰ฌ์›Œ์ง.

[UIAlertController๋ฅผ Rx์Šค๋Ÿฝ๊ฒŒ ๋ฆฌํŒฉํ† ๋ง ํ•ด๋ณด๊ธฐ]

func showActionSheet(
    sourceView: UIView,
    titles: (String, String),
    topHandler: @escaping (UIAlertAction) -> Void,
    bottomHandler: @escaping (UIAlertAction) -> Void
) {
    let topAction = UIAlertAction(title: "Move to \(titles.0)", style: .default, handler: topHandler)
    let bottomAction = UIAlertAction(title: "Move to \(titles.1)", style: .default, handler: bottomHandler)
    let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
    alert.addAction(topAction)
    alert.addAction(bottomAction)
    if let popoverController = alert.popoverPresentationController {
        popoverController.sourceView = sourceView
        let rect = CGRect(x: .zero, y: .zero, width: sourceView.bounds.width, height: sourceView.bounds.height / 2)
        popoverController.sourceRect = rect
        popoverController.permittedArrowDirections = [.up, .down]
    }
    navigationController.topViewController?.present(alert, animated: true)
}
  • ๋ผ์ด์–ธํ•œํƒœ ์ฝ”๋“œ๋ฆฌ๋ทฐ ๋ฐ›๊ณ ๋‚œ ํ›„ escaping ํด๋กœ์ €๋งŒ ๋ณด๋ฉด... '์•„ ์˜ต์ €๋ฒ„๋ธ” ์“ธ ์ˆ˜ ์žˆ์„ ๊ฑฐ ๊ฐ™์€๋ฐ?' ๋ผ๋Š” ์ƒ๊ฐ์— ๋น ์ง„๋‹ค.
  • ์˜ค๋Š˜๋„ ์–ด๊น€์—†์ด ์˜ต์ €๋ฒ„๋ธ”์„ ์“ธ ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™์•„์„œ ์ฐพ์•„๋ณด๋‹ˆ๊นŒ... ์˜ˆ์ œ์ฝ”๋“œ๋“ค์ด ๋งŽ๊ธธ๋ž˜ ๋„์ „ํ•ด๋ณด์•˜๋‹ค.
  • ๋”ฐ๋ผ์„œ ์œ„ ์ฝ”๋“œ๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ์ˆ˜์ •ํ•ด๋ณด์•˜๋‹ค.
enum ActionType: CaseIterable {
    case top
    case bottom
}

func showActionSheet(sourceView: UIView, titles: [String]) -> Observable<ProjectState> {
    return Observable.create { observer in
        let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
        ActionType.allCases.enumerated().forEach { index, _ in
            let action = UIAlertAction(title: "Move to \(titles[index])", style: .default) { _ in
                observer.onNext(ProjectState(rawValue: titles[index]) ?? ProjectState.todo)
                observer.onCompleted()
            }
            alert.addAction(action)
        }
        if let popoverController = alert.popoverPresentationController {
            popoverController.sourceView = sourceView
            let rect = CGRect(
                x: .zero,
                y: .zero,
                width: sourceView.bounds.width,
                height: sourceView.bounds.height / 2
            )
            popoverController.sourceRect = rect
            popoverController.permittedArrowDirections = [.up, .down]
        }
        self.navigationController.topViewController?.present(alert, animated: true)

        return Disposables.create {
            alert.dismiss(animated: true, completion: nil)
        }
    }
}
  • ๋ญ๊ฐ€ ๋งŽ์ด ๋ฐ”๋€ ๊ฒƒ ๊ฐ™์ง€๋งŒ... ๋ณ„๊ฑฐ์—†๋‹ค.
  • ActionType์ด๋ผ๋Š” enum์„ ๋งŒ๋“ค๊ณ  ํ•ด๋‹น ์ผ€์ด์Šค๋ฅผ ๋ฐ˜๋ณตํ•˜๋ฉด์„œ ํ•ธ๋“ค๋Ÿฌ ๋‚ด๋ถ€์— onNext๋กœ ProjectState๋ผ๋Š” ๋ฐ์ดํ„ฐ์™€ ํ•จ๊ป˜ ์ด๋ฒคํŠธ๋ฅผ ์ „๋‹ฌํ•ด์ค€๋‹ค.
  • ๋‚˜๋จธ์ง€๋Š” iPad๋ฅผ ์œ„ํ•œ popover ์„ค์ •...
showActionSheet(sourceView: cell, titles: project.status.excluded)
    .subscribe(onNext: { state in
        self.useCase.changedState(project, state: state)
    }).disposed(by: disposeBag)
  • ์‚ฌ์šฉํ•  ๋•Œ(๊ตฌ๋…)๋Š” onNext๋กœ ์ „๋‹ฌ๋ฐ›์€ state๊ฐ’์œผ๋กœ project์˜ ์ƒํƒœ๊ฐ’์„ ๋ฐ”๊ฟ”์ฃผ๋Š” ์ž‘์—…์„ ํ•ด์ฃผ์—ˆ๋‹ค.
  • ์ด๋•Œ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ sourceView๋ฅผ ๋„˜๊ฒจ์ฃผ๋Š” ์ด์œ ๋Š” popover๋ฅผ ๋„์šธ ์œ„์น˜๋ฅผ ์žก๊ธฐ ์œ„ํ•จ์ธ๋ฐ... ViewModel์— UIKit์„ importํ•ด์•ผํ•ด์„œ ๋ชน์‹œ ๋ถˆํŽธํ•˜๋‹ค..
  • ์ด๋ถ€๋ถ„์€ ๊ณ ๋ฏผํ•ด๋ณด์•˜์ง€๋งŒ ์ข‹์€ ๋ฐฉ๋ฒ•์ด ๋– ์˜ค๋ฅด์ง€๊ฐ€ ์•Š์•„์„œ ๊ฐœ์„ ํ•˜์ง€ ๋ชปํ–ˆ๋‹ค.

[UI์˜ value๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ์„ ๋•Œ๋งŒ ์ด๋ฒคํŠธ ๋ฐ›๊ธฐ]

let input = DetailViewModel.Input(
    didTapRightBarButton: rightBarButton.rx.tap.asObservable(),
    didTapLeftBarButton: leftBarButton.rx.tap.asObservable(),
    didChangeTitleText: titleTextField.rx.text.asObservable(),
    didChangeDatePicker: datePicker.rx.date.asObservable(),
    didChangeDescription: descriptionTextView.rx.text.asObservable())
    didChangeTitleText: titleTextField.rx.text.changed.asObservable(),
    didChangeDatePicker: datePicker.rx.date.changed.asObservable(),
    didChangeDescription: descriptionTextView.rx.text.changed.asObservable()
)
  • ์ฒ˜์Œ์—” ์œ„์™€ ๊ฐ™์ด ๋‹จ์ˆœํ•˜๊ฒŒ input์„ ๋งŒ๋“ค์–ด์ฃผ์—ˆ๋Š”๋ฐ...
  • ์ด๋ ‡๊ฒŒ ๋งŒ๋“ค๋‹ค๋ณด๋‹ˆ TextField์˜ ๊ฒฝ์šฐ ๊ฐ’์„ ์ˆ˜์ •ํ•˜์ง€ ์•Š๊ณ  tapํ•ด์„œ ํ™œ์„ฑํ™”๋งŒ ํ•ด๋„ ์ด๋ฒคํŠธ๋ฅผ ์ „๋‹ฌ๋ฐ›๋Š” ๊ฒƒ์„ ํ™•์ธํ–ˆ๋‹ค.
    • ์ด๋Ÿฌ๋ฉด ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๊ณ  modal์„ ๋‹ซ์•„๋„, ์ด๋ฒคํŠธ๋ฅผ ๋ฐ›๊ณ  ๊ฐ’์ด ์ˆ˜์ •๋œ ๊ฒƒ ๋งˆ๋ƒฅ ๋นˆ๋ฌธ์ž์—ด์ด ๋“ค์–ด์™€์„œ ๊ธฐ์กด ๋ฐ์ดํ„ฐ๊ฐ€ ์‚ฌ๋ผ์ง€๋Š”... ๋ฒ„๊ทธ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.
    • ์•„๋ฌด๊ฒƒ๋„ ์•ˆํ•ด๋„.. Modal๋งŒ ๋„์šฐ๊ณ  ๋‹ซ์•„๋„.. ๋นˆ๋ฌธ์ž์—ด ์ด๋ฒคํŠธ๋ฅผ ๋ฐ›์•„์„œ ๋ฐ์ดํ„ฐ๊ฐ€ ์ง€์›Œ์ง€๋Š”....๐Ÿฅฒ
  • ๊ตฌ๊ธ€๋ง์„ ํ•ด๋ณด๋‹ˆ changed๋ผ๋Š” ControlProperty๋ฅผ ์ฐพ๊ฒŒ ๋˜์—ˆ๊ณ , ์•„๋ž˜์™€ ๊ฐ™์ด ๊ฐ’์ด ๋ณ€๊ฒฝ๋ ๋•Œ ๋งˆ๋‹ค ์ด๋ฒคํŠธ๋ฅผ ์ „๋‹ฌํ•˜๋Š” ์˜ต์ €๋ฒ„๋ธ”๋กœ ๋ณ€๊ฒฝํ•ด์ฃผ์—ˆ๋‹ค
let input = DetailViewModel.Input(
    didTapRightBarButton: rightBarButton.rx.tap.asObservable(),
    didTapLeftBarButton: leftBarButton.rx.tap.asObservable(),
    didChangeTitleText: titleTextField.rx.text.changed.asObservable(), // changed
    didChangeDatePicker: datePicker.rx.date.changed.asObservable(), // changed
    didChangeDescription: descriptionTextView.rx.text.changed.asObservable() // changed
)
  • ๊ทธ๋ฆฌ๊ณ  output์„ ์„ค์ •ํ•ด์ค„๋•Œ ๋…ผ์˜ต์…”๋„ ํƒ€์ž…์œผ๋กœ ์„ค์ •ํ•ด์ฃผ์—ˆ๋Š”๋ฐ, ์˜ต์…”๋„ ํƒ€์ž…์œผ๋กœ ๋ฐ”๊ฟ”์ฃผ๊ณ , nil์ผ ๊ฒฝ์šฐ ๊ธฐ์กด ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•ด์„œ, ๊ฐ’์ด ์ž„์˜๋กœ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋„๋ก ์ฒ˜๋ฆฌํ•ด์ฃผ์—ˆ๋‹ค.
  • ์ด๋ ‡๊ฒŒ ํ•˜๋‹ˆ๊นŒ ๊ฐ’์„ ์ˆ˜์ •ํ•˜์ง€ ์•Š์œผ๋ฉด ์ •์ƒ์ ์œผ๋กœ ์ˆ˜์ •๋˜์ง€ ์•Š์•˜๊ณ , ํ•ด๋‹น ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.