๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐ŸŽ iOS/Architecture

[RIBs] Memo ์‚ญ์ œ,์ˆ˜์ • ๊ตฌํ˜„ํ•ด๋ณด๊ธฐ

by Fomagran ๐Ÿ’ป 2021. 8. 26.
728x90
๋ฐ˜์‘ํ˜•

์•ˆ๋…•ํ•˜์„ธ์š” Foma๐Ÿ’ป ์ž…๋‹ˆ๋‹ค!

 

์ €๋ฒˆ ์‹œ๊ฐ„์— Memo๊นŒ์ง€ ์ด๋™ํ•ด์„œ ํ™”๋ฉด์„ ๋ณด์—ฌ์ฃผ๋Š” ๊ฒƒ๊นŒ์ง€ ๊ตฌํ˜„ํ–ˆ์—ˆ๋Š”๋ฐ์š”.

 

์˜ค๋Š˜์€ Memoํ™”๋ฉด์—์„œ ์ผ์–ด๋‚˜๋Š” ์‚ญ์ œ,์ˆ˜์ •์„ ๊ตฌํ˜„ํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

 

๋ฐ”๋กœ ์‹œ์ž‘ํ• ๊ฒŒ์š”~


MemoViewController.storyboard

 

์Šคํ† ๋ฆฌ๋ณด๋“œ ํ™”๋ฉด ๊ตฌ์„ฑ์€ ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

 

๋ทฐ์ปจํŠธ๋กค๋Ÿฌ์— ํ…Œ์ด๋ธ”๋ทฐ๋ฅผ ๋„ฃ์–ด์ฃผ๊ณ 

 

ํ…Œ์ด๋ธ”๋ทฐ ์…€์€ MemoTableViewCell์„ ๋”ฐ๋กœ ๋งŒ๋“ค์–ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

 

ํ…Œ์ด๋ธ”๋ทฐ์…€์€ ์ •๋ณด๋ฅผ ํ‘œ์‹œํ•ด์ค„ Label,์‚ญ์ œ๋ฒ„ํŠผ.๋”ํ•˜๊ธฐ๋ฒ„ํŠผ์œผ๋กœ ๊ตฌ์„ฑ๋˜์–ด์žˆ์Šต๋‹ˆ๋‹ค.

 


Memo

 

๋ฉ”๋ชจ๋Š” ๊ฐ„๋‹จํ•˜๊ฒŒ ์ˆซ์ž๋งŒ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ๊ฐ์ฒด๋กœ ๋งŒ๋“ค์–ด์ค๋‹ˆ๋‹ค.

 

struct Memo {
    let number:Int
}

MemoData

 

์›๋ž˜ ์„œ๋ฒ„์™€ ํ†ต์‹ ์„ ํ•ด์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€์•ผํ•˜์ง€๋งŒ...๊ท€์ฐฎ์œผ๋‹ˆ๊น .. ์ „์—ญ์œผ๋กœ ์„ ์–ธํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฏธ๋ฆฌ ์„ธํŒ…ํ•ด๋†“๊ฒ ์Šต๋‹ˆ๋‹ค.

 

var MEMO_DATA:[Memo] = [
    Memo(number: 1),
    Memo(number: 2),
    Memo(number: 3),
    Memo(number: 4),
    Memo(number: 5),
    Memo(number: 6),
    Memo(number: 7),
    Memo(number: 8),
    Memo(number: 9),
    Memo(number: 10),
    Memo(number: 11),
    Memo(number: 12),
    Memo(number: 13),
    Memo(number: 14),
    Memo(number: 15),
    Memo(number: 16),
    Memo(number: 17),
    Memo(number: 18),
    Memo(number: 19),
    Memo(number: 20),
]

MemoTableViewCell

 

์Šคํ† ๋ฆฌ๋ณด๋“œ์—์„œ ๋ฒ„ํŠผ๊ณผ ๋ ˆ์ด๋ธ”์„ ์—ฐ๊ฒฐํ•ด์ฃผ๊ณ 

 

RxCocoa,RxSwift๋ฅผ import ํ•ด์ค๋‹ˆ๋‹ค.

 

๊ทธ๋ฆฌ๊ณค ์‚ญ์ œ๋ฒ„ํŠผ๊ณผ ๋”ํ•˜๊ธฐ๋ฒ„ํŠผ์„ ๋ˆŒ๋ €์„ ๋•Œ์˜ ์•ก์…˜์„ ์˜ต์ €๋ฒ„๋ธ”๋กœ ๋งŒ๋“ค์–ด์ค๋‹ˆ๋‹ค.

 

import UIKit
import RxSwift
import RxCocoa

class MemoTableViewCell: UITableViewCell {
    
    @IBOutlet weak var plusButton: UIButton!
    @IBOutlet weak var deleteButton: UIButton!
    @IBOutlet weak var title: UILabel!
    
    override func prepareForReuse() {
        disposeBag = DisposeBag()
    }
    
    var disposeBag:DisposeBag = DisposeBag()
    
    var deleteTap : Observable<Void>{
        return self.deleteButton.rx.tap.asObservable()
    }
    
    var plusTap : Observable<Void>{
        return self.plusButton.rx.tap.asObservable()
    }
}

MemoViewController

 

์•„๋ž˜์™€ ๊ฐ™์ด 4๊ฐœ ๋ชจ๋“ˆ์„ import ํ•ด์ค๋‹ˆ๋‹ค.

 

import RIBs
import UIKit
import RxCocoa
import RxSwift

 

PresentableListener์— ์ •๋ณด๋ฅผ ๊ฐ€์ง€๋Š” memos ๋ฉ”๋ชจ๋ทฐ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ์ผ์–ด๋‚  ์•ก์…˜์ธ deleteMemo,plusMemo๋ฅผ ๋ฏธ๋ฆฌ ์ •์˜ํ•ด์ค๋‹ˆ๋‹ค.

 

protocol MemoPresentableListener: AnyObject {
    var memos: BehaviorRelay<[Memo]> { get }
    func deleteMemo(_ index:Int)
    func plusMemo(_ index: Int)
}

 

๊ฐ€์žฅ ๋จผ์ € ํ…Œ์ด๋ธ”๋ทฐ๋ฅผ ์—ฐ๊ฒฐํ•ด์ฃผ๊ณ 

 

ํ…Œ์ด๋ธ”๋ทฐ์™€ ๋ฐ”์ธ๋”ฉ์„ ํ•ด์ค„ bindํ•จ์ˆ˜๋ฅผ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.

 

bind()

 

listener๋ฅผ ํ†ตํ•ด์„œ memos ์•ˆ์— ์žˆ๋Š” ์ •๋ณด๋ฅผ ํ…Œ์ด๋ธ”๋ทฐ์™€ ๋ฐ”์ธ๋”ฉํ•ฉ๋‹ˆ๋‹ค.

 

cell์•ˆ์— ์žˆ๋Š” deleteTap๊ณผ plusTap์„ ๊ตฌ๋…ํ•˜์—ฌ ์•ก์…˜์— ๋Œ€ํ•œ ํ–‰๋™์„ PresentableListener ๋ฏธ๋ฆฌ ์ •์˜ํ•ด๋‘์—ˆ๋˜ deleteMemo์™€ 

 

plusMemo๊ฐ€ ์‹คํ–‰๋˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

 

final class MemoViewController: UIViewController, MemoPresentable {
       
    @IBOutlet weak var table: UITableView!
    
    weak var listener: MemoPresentableListener?
    let disposeBag:DisposeBag = DisposeBag()
    
    static func instantiate() -> Self {
        return Storyboard.MemoViewController.instantiate(self)
    }
    
    override func viewDidLoad() {
        configure()
        bind()
    }
    
    private func configure() {
        self.title = "Memo"
    }
    
    private func bind() {
        listener?.memos.bind(to: table.rx.items(cellIdentifier: "MemoTableViewCell"))  { [weak self] (index, memo, cell) in
            if let cell = cell as? MemoTableViewCell {
                cell.number.text = "\(memo.number)"
                cell.deleteTap
                    .subscribe(onNext:{
                        self?.listener?.deleteMemo(index)
                    })
                    .disposed(by: cell.disposeBag)
                
                cell.plusTap
                    .subscribe(onNext:{
                        self?.listener?.plusMemo(index)
                    })
                    .disposed(by: cell.disposeBag)
            }
        }.disposed(by: disposeBag)
    }
}

MemoInteractor

 

์ด์ œ ๋ทฐ์ปจํ† ๋กค๋Ÿฌ์—์„œ ์ •์˜ํ•˜๊ณ  ์‹คํ–‰ํ•œ memos,deleteMemo,plusMemo์— ๋Œ€ํ•œ ๋กœ์ง์„ ๊ตฌํ˜„ํ•ด์ค˜์•ผ๊ฒ ์ฃ ?

 

๋น„์ง€๋‹ˆ์Šค ๋กœ์ง์„ ๋‹ด๋‹นํ•˜๋Š” ์ธํ„ฐ๋ž™ํ„ฐ๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค.

 

๊ฐ€์žฅ ๋จผ์ € memos๋ฅผ BehaviorRelay<[Memo]> ๋กœ ๋งŒ๋“ค์–ด์ค๋‹ˆ๋‹ค. (BehaviorRelay๋ฅผ ๋ชจ๋ฅด์‹œ๋Š” ๋ถ„๋“ค์€ ์—ฌ๊ธฐ ์—์„œ ๋ด์ฃผ์„ธ์š”!)

 

๊ทธ๋ฆฌ๊ณค ๋ฉ”๋ชจ๋ฐ์ดํ„ฐ๋ฅผ ์˜ต์ €๋ฒ„๋ธ”ํ•˜๊ฒŒ ๋ฐ›์•„์˜ค๊ฒŒ๋” ๋งŒ๋“ค์–ด์ค๋‹ˆ๋‹ค.

 

๊ทธ๋ฆฌ๊ณค getMemoData๋ฅผ memos๋ž‘ ๋ฐ”์ธ๋”ฉ์„ ํ•ด์ฃผ์–ด memos๊ฐ€ memoData๋ฅผ ๊ฐ–๊ฒŒํ•ฉ๋‹ˆ๋‹ค.

 

.disposeOnDeactive ๋ฉ”์†Œ๋“œ๋ฅผ ์ด์šฉํ•ด์„œ ์ธํ„ฐ๋ž™ํ„ฐ๊ฐ€ ๋น„ํ™œ์„ฑํ™”๋˜๋ฉด dispose๋˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

 

๋ทฐ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ๋ฆฌ์Šค๋„ˆ์˜ memos ๋ฐ์ดํ„ฐ์™€ ํ…Œ์ด๋ธ”๋ทฐ๋ฅผ ๋ฐ”์ธ๋”ฉ ํ•ด์ฃผ์—ˆ์ฃ ?

 

์ธํ„ฐ๋ž™ํ„ฐ์—์„œ memos ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฐ”๋€” ๋•Œ๋งˆ๋‹ค ํ…Œ์ด๋ธ”๋ทฐ์˜ ์ •๋ณด๊ฐ€ ์ž๋™์œผ๋กœ ๊ฐฑ์‹ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

 

final class MemoInteractor: PresentableInteractor<MemoPresentable>, MemoInteractable {

    weak var router: MemoRouting?
    weak var listener: MemoListener?
    var memos: BehaviorRelay<[Memo]> = BehaviorRelay.init(value: [])
	
    ...
    override func didBecomeActive() {
        super.didBecomeActive()
        getMemoData()
            .bind(to: memos)
            .disposeOnDeactivate(interactor: self)
    }
    
    func getMemoData() -> Observable<[Memo]> {
        return Observable<[Memo]>.create { observer in
            observer.onNext(MEMO_DATA)
            return Disposables.create()
        }
    }
}

 

๋ทฐ์ปจํŠธ๋กค๋Ÿฌ์˜ MemoPresentableListener์— ๋ฏธ๋ฆฌ ํ•„์š”ํ•œ ์•ก์…˜๋“ค์„ ์ •์˜ํ•ด๋‘์—ˆ์ฃ ?

 

์ธํ„ฐ๋ž™ํ„ฐ๊ฐ€ MemoPresentableListener ์ฑ„ํƒํ•˜์—ฌ ์ผ์–ด๋‚œ ์•ก์…˜์— ๋Œ€ํ•œ ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

 

deleteMemo๋Š” memos์˜ ํ•ด๋‹น index๋ฒˆ์งธ ๋ฉ”๋ชจ๊ฐ€ ์‚ญ์ œ๋˜๊ณ  memos์— ์ „๋‹ฌํ•˜๋„๋ก ์ž‘์„ฑํ•˜์˜€๊ณ 

 

plusMemo๋Š” memos์˜ ํ•ด๋‹น index๋ฒˆ์งธ ์ˆซ์ž๊ฐ€ +1 ๋˜๋„๋ก ๋งŒ๋“ค๊ณ  memos์— ์ „๋‹ฌํ•˜๋„๋ก ์ž‘์„ฑํ•˜์˜€์Šต๋‹ˆ๋‹ค.

 

// MARK: MemosPresentableListener
extension MemoInteractor: MemoPresentableListener {
    
    func deleteMemo(_ index:Int) {
        var newMemos = memos.value
        newMemos.remove(at: index)
        memos.accept(newMemos)
    }
    
    func plusMemo(_ index: Int) {
        var newMemos = memos.value
        newMemos[index] = Memo(number:newMemos[index].number + 1)
        memos.accept(newMemos)
    }
}

์‹คํ–‰ํ™”๋ฉด

 

๋ฉ”๋ชจ์—์„œ ๋”ํ•˜๊ธฐ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ์ˆซ์ž๊ฐ€ +1์”ฉ ์ฆ๊ฐ€ํ•˜๊ณ  ์‚ญ์ œ๋ฅผ ๋ˆ„๋ฅด๋ฉด ํ•ด๋‹น ๋ฉ”๋ชจ๊ฐ€ ์‚ญ์ œ๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

 

 

728x90
๋ฐ˜์‘ํ˜•

๋Œ“๊ธ€