순진이의 하루/study정리

21. UITableViewDiffableDataSource

순진이 2023. 1. 3. 20:42

AViewModel과 UITableVIewDiffableDataSource 활용하기

※ 스토리보드로 진행

 

ViewController

import UIKit

class ViewController: UIViewController {
    
    @IBOutlet weak var tableView: UITableView!
    private let viewModel = ViewModel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.delegate = self
        viewModel.makeTableView(tableView: tableView)
    }
}

ViewModel

- custom cell일 경우, register해주는 작업도 필요

- 반드시 append 해주는 작업을 해야 dataSource로 쓸 수 있음

import UIKit
import Combine

class ViewModel {

    private var dataSource: UITableViewDiffableDataSource<SectionClass, Int>!
    //<Int, Int> -> Section과 row라고 생각해주면 됨, hasable한 값이 들어가야 함
    
    let sectionClasses = [SectionClass(name: "1"), SectionClass(name: "2")]
    var numbers = [1, 2, 3]
    var newNumbers = [4, 5, 6]
    
    func addNewDate() {
        updateSnapshot2(items: numbers + newNumbers, section: sectionClasses[1])
    }
    
    private func updateSnapshot(items: [Int], section: SectionClass) {
        var snapshot = NSDiffableDataSourceSnapshot<SectionClass, Int>()
        snapshot.appendSections(sectionClasses)
        snapshot.appendItems(items, toSection: section)
        dataSource.apply(snapshot)   
    }
    
    func makeTableView(tableView: UITableView) {      
        let nib = UINib(nibName: CustomTableViewCell.identifier, bundle: nil)
        tableView.register(nib, forCellReuseIdentifier: CustomTableViewCell.identifier)
        dataSource = .init(tableView: tableView, cellProvider: { tableView, indexPath, itemIdentifier in
            guard let cell = tableView.dequeueReusableCell(withIdentifier: CustomTableViewCell.identifier, for: indexPath) as? CustomTableViewCell else { fatalError() }
            cell.configure(text: String(itemIdentifier))
            return cell
        })
        updateSnapshot(items: numbers, section: sectionClasses[0])
    }
}

struct SectionClass: Hashable {
    let name: String
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(name)
    }
}

 

0. UITableViewDiffableDataSource 선언

@preconcurrency @MainActor class UITableViewDiffableDataSource<SectionIdentifierType, ItemIdentifierType> : NSObject where SectionIdentifierType : Hashable, SectionIdentifierType : Sendable, ItemIdentifierType : Hashable, ItemIdentifierType : Sendable

- UITableViewDiffableDataSource<> 안에 들어있는 type은 hasable 해야 함

- UITableViewDiffableDataSource<A, B>인 경우 -> A가 section, B가 Item이라고 생각하면 됨.

 

1. Section과 Item을 추가

1-1. Section을 추가하는 방법 - enum로 만들기

- enum 자체가 hasable하기 때문에 매우 유용

enum Section: CaseIterable {
    case first
    case second
}

- UITableViewDiffableDataSource<>안의 타입을 맞춰줘야 함 (여기서는 enum이므로 Section 타입)

//선언부
private var dataSource: UITableViewDiffableDataSource<Section, Int>!

//사용부
private func updateSnapshot(items: [Int], section: Section) {
    var snapshot = NSDiffableDataSourceSnapshot<Section, Int>()
    snapshot.appendSections(Section.allCases)
    snapshot.appendItems([1, 2, 3], toSection: Section.first)
    snapshot.appendItems([4, 5, 6], toSection: Section.second)
    dataSource.apply(snapshot)
}

1-2. Section을 추가하는 방법 - struct로 만들기(현재 코드)

- struct는 hasable 하지 않기 때문에 hash 함수를 구현해줘야 하

struct SectionClass: Hashable {
    let name: String
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(name)
    }
}
//선언부
private var dataSource: UITableViewDiffableDataSource<SectionClass, Int>!
    
let sectionClasses = [SectionClass(name: "1"), SectionClass(name: "2")]
    
//구현부
private func updateSnapshot2(items: [Int], section: SectionClass) {
    var snapshot = NSDiffableDataSourceSnapshot<SectionClass, Int>()
    snapshot.appendSections(sectionClasses)
    snapshot.appendItems(items, toSection: section)
    dataSource.apply(snapshot)
}

 

2. dataSource 연결

- cellProvider를 클로저로 전달할 수도 있음

dataSource = .init(tableView: tableView, cellProvider: { tableView, indexPath, itemIdentifier in
   guard let cell = tableView.dequeueReusableCell(withIdentifier: CustomTableViewCell.identifier, for: indexPath) as? CustomTableViewCell else { fatalError() }
   cell.configure(text: String(itemIdentifier))
   return cell
})

 


CustomCell

- nib 파일 통해 label 하나 넣고, 연결해놓기

import UIKit

class CustomTableViewCell: UITableViewCell {
    
    static let identifier = String(describing: CustomTableViewCell.self)

    @IBOutlet weak var label: UILabel!
    
    override func awakeFromNib() {
        super.awakeFromNib()
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
    }
    
    func configure(text: String) {
        label.text = text
    }
}


UITableViewDelegate

- UITableViewDiffableDataSource쓸 때, Delegate는 평소랑 똑같이 써주면 된다! 

import UIKit

class ViewController: UIViewController {
    
    @IBOutlet weak var tableView: UITableView!
    private let viewModel = ViewModel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.delegate = self
        viewModel.setupTableView(tableView: tableView)
    }
}

extension ViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        viewModel.addNewDate()
    }
}

//ViewModel.swift
func addNewDate() {
    updateSnapshot2(items: numbers + newNumbers, section: sectionClasses[1])
}