Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementing Modern Collection Views 애플 개발자 문서 2편 #160

Closed
Youngminah opened this issue Apr 27, 2022 · 1 comment
Closed

Comments

@Youngminah
Copy link
Owner

Youngminah commented Apr 27, 2022

MountainsView

Simulator Screen Recording - iPhone 13 - 2022-04-27 at 13 34 14

func configureDataSource() {
    
    let cellRegistration = UICollectionView.CellRegistration
    <LabelCell, MountainsController.Mountain> { (cell, indexPath, mountain) in
        // Populate the cell with our item description.
        cell.label.text = mountain.name
    }
    
    dataSource = UICollectionViewDiffableDataSource<Section, MountainsController.Mountain>(collectionView: mountainsCollectionView) {
        (collectionView: UICollectionView, indexPath: IndexPath, identifier: MountainsController.Mountain) -> UICollectionViewCell? in
        // Return the cell.
        return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: identifier)
    }
}
  • UICollectionViewDiffableDataSource
  • 생성자 @escaping 클로져로 cell을 등록해주고
  • cell configure도 해줌.
func performQuery(with filter: String?) {
    let mountains = mountainsController.filteredMountains(with: filter).sorted { $0.name < $1.name }

    var snapshot = NSDiffableDataSourceSnapshot<Section, MountainsController.Mountain>()
    snapshot.appendSections([.main])
    snapshot.appendItems(mountains)
    dataSource.apply(snapshot, animatingDifferences: true)
}
  • 스냅샷은 초기에 찍어두고 업뎃마다 바꿔주기
class MountainsController {

    struct Mountain: Hashable {
        let name: String
        let height: Int
        let identifier = UUID()
        func hash(into hasher: inout Hasher) {
            hasher.combine(identifier)
        }
        static func == (lhs: Mountain, rhs: Mountain) -> Bool {
            return lhs.identifier == rhs.identifier
        }
        func contains(_ filter: String?) -> Bool {
            guard let filterText = filter else { return true }
            if filterText.isEmpty { return true }
            let lowercasedFilter = filterText.lowercased()
            return name.lowercased().contains(lowercasedFilter)
        }
    }
    func filteredMountains(with filter: String?=nil, limit: Int?=nil) -> [Mountain] {
        let filtered = mountains.filter { $0.contains(filter) }
        if let limit = limit {
            return Array(filtered.prefix(through: limit))
        } else {
            return filtered
        }
    }
    private lazy var mountains: [Mountain] = {
        return generateMountains()
    }()
}

extension MountainsController {
    private func generateMountains() -> [Mountain] {
        let components = mountainsRawData.components(separatedBy: CharacterSet.newlines)
        var mountains = [Mountain]()
        for line in components {
            let mountainComponents = line.components(separatedBy: ",")
            let name = mountainComponents[0]
            let height = Int(mountainComponents[1])
            mountains.append(Mountain(name: name, height: height!))
        }
        return mountains
    }
}
  • Mountain은 struct 타입임
  • swift에서 enum은 자동적으로 Hashable이지만,
  • 구조체는 그렇지 못함.
  • 그래서 Hashable 프로토콜 채택.
  • Hashable해야 DiffableDatasource이용할 수 있음.


WiFiSettingsView

Simulator Screen Recording - iPhone 13 - 2022-04-27 at 13 58 36

func configureDataSource() {
    wifiController = WiFiController { [weak self] (controller: WiFiController) in
        guard let self = self else { return }
        self.updateUI()
    }

    self.dataSource = UITableViewDiffableDataSource
        <Section, Item>(tableView: tableView) { [weak self]
            (tableView: UITableView, indexPath: IndexPath, item: Item) -> UITableViewCell? in
        guard let self = self, let wifiController = self.wifiController else { return nil }

        let cell = tableView.dequeueReusableCell(
            withIdentifier: WiFiSettingsViewController.reuseIdentifier,
            for: indexPath)
        
        var content = cell.defaultContentConfiguration()
        // network cell
        if item.isNetwork {
            content.text = item.title
            cell.accessoryType = .detailDisclosureButton
            cell.accessoryView = nil

        // configuration cells
        } else if item.isConfig {
            content.text = item.title
            if item.type == .wifiEnabled {
                let enableWifiSwitch = UISwitch()
                enableWifiSwitch.isOn = wifiController.wifiEnabled
                enableWifiSwitch.addTarget(self, action: #selector(self.toggleWifi(_:)), for: .touchUpInside)
                cell.accessoryView = enableWifiSwitch
            } else {
                cell.accessoryView = nil
                cell.accessoryType = .detailDisclosureButton
            }
        } else {
            fatalError("Unknown item type!")
        }
        cell.contentConfiguration = content
        return cell
    }
    self.dataSource.defaultRowAnimation = .fade

    wifiController.scanForNetworks = true
}


@Youngminah
Copy link
Owner Author

Youngminah commented Apr 27, 2022

Advances in UI Data Sources

WWDC Advances in UI Data Sources



문제점

  • UI 와 data간의 싱크가 안맞아서 생기는 문제점들이 있엇음
  • reloadData를 해주기 전에 데이터를 요구하면 에러가 뜸.


Snapshots

  • 더이상 indexPath를 이용하지 않는다.
  • indexPath를 접근할 수 없어서 생기는 문제점 사라짐.
  • 유니크한 section과 item의 identifier들을 가지고 접근함.


예제

아래 사진을 현재의 Snapshot 모습이라 하자

image



아래 사진 처럼 새로운 스냅샷으로 바꾼다고 했을때, 일어나는 일

image

  • 현재의 스냅샷을 알고 있고,
  • 바뀔 스냅샷의 모습도 알고 있음.
  • 따라서 자연스럽게 애니메이션 또 한 가능하다
  • 이것이 핵심 개념


Diffable Data Source

  • UICollectionViewDiffableDataSource : iOS or tvOS
  • UITableViewDiffableDataSource : iOS or tvOS
  • NSCollectionViewDiffableDataSource : Mac

  • NSDiffableDataSourceSnapshot : 모든 곳에서 쓰이는 Shapshot 구조체

NSDiffableDataSourceSnapshot

  • Snapshot을 위한 구조체로써
  • NSDiffableDataSourceSnapshot<SectionIdentifierType, ItemIdentifierType> where SectionIdentifierType : Hashable, ItemIdentifierType : Hashable 구조체
  • 각각의 SectionIdentifierTypeItemIdentifierType
  • Hashable 프로토콜을 따라야 하며 고유 아이디로 식별해야하기 때문에 고유 아이디가 있어야한다고
  • 개발자 문서 important에 써있음.
  • 그렇기 때문에 section은 주로 enum으로 생성하므로
  • 기본으로 hashable타입이라 따로 프로토콜을 채택하지 않아도 되지만,
  • item이 struct 타입형일 경우 hashable프로토콜을 채택하고 고유 아이디 값을 주어야한다.
  • datasource에 apply 함수에 인자로 넣기만 하면 끝!
  • snapshot에서는 indexpath 인자를 찾아 볼 수 없다.
  • indexpath기반이 아님을 알 수 있음.
    • image
    • identifier 기반임을 나타냄.


Snapshot을 생성하는 보편적 2가지 방법

image



Performance에 관한 얘기

  • 빠름.
  • 백그라운드 큐에서 apply함수를 안전하게 호출.
  • 예상하건데 hash 기반으로 이루어져있어서 indexpath를 이용할 때 보다
  • 훨씬 빠를 것으로 예상함.


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant