이번 게시글에서는 가장 최신 기능 조합인
Diffable Data Source(데이터 관리)와
List Configuration(컬렉션뷰 레이아웃)을
사용하여 컬렉션뷰를 만들어보자
Diffable Data Source
: UICollectionViewDataSource를 상속받은 UICollectionViewDiffableDataSource
2019년 iOS 13 WWDC19 참고 (advances in UI data sources)
기존과 달라진 점
- indexpath 안씀 -> itemIdentifier로 대체
- cellForRow 안씀
- numberOfCell 안씀
- reloadData 안씀 -> apply() : 변경되는 데이터 양의 상관없이 백그라운드의 스레드에서 연산처리
- 데이터는 각각의 고유한 모델 사용(Hashable을 채택한 model 사용)
다음의 기능을 코드로 구현해보자.
1. 간단한 리스트를 컬렉션뷰로 나타내기
2. 서치바에 글자 입력 후 엔터 시, 컬렉션뷰에 추가해서 보여주기
3. 셀 클릭 시, 어럴트 창 렌더링하기
class DiffableCollectionViewController: UIViewController {
@IBOutlet weak var collectionView: UICollectionView!
@IBOutlet weak var searchBar: UISearchBar!
// 생략
// var cellRegistration: UICollectionView.CellRegistration<UICollectionViewListCell, String>!
// <Int, String> = <섹션 정보, 모델 타입>
private var dataSource: UICollectionViewDiffableDataSource<Int, String>!
var list = ["이순신", "김좌진", "맥아더", "노르망디"]
override func viewDidLoad() {
super.viewDidLoad()
searchBar.delegate = self
collectionView.collectionViewLayout = createLayout()
collectionView.delegate = self
configureDataSource()
}
}
extension DiffableCollectionViewController {
private func createLayout() -> UICollectionViewLayout {
var configuration = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
let layout = UICollectionViewCompositionalLayout.list(using: configuration)
return layout
}
private func configureDataSource() {
let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, String>(handler: { cell, indexPath, itemIdentifier in
var contentConfig = UIListContentConfiguration.valueCell()
contentConfig.text = itemIdentifier
contentConfig.secondaryText = "\(itemIdentifier.count)"
cell.contentConfiguration = contentConfig
var backgroundConfig = UIBackgroundConfiguration.listPlainCell()
backgroundConfig.strokeWidth = 2
backgroundConfig.strokeColor = .brown
cell.backgroundConfiguration = backgroundConfig
})
// collectionView.dataSource = self
// cellForItem, numberOfItems 대체
dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView, cellProvider: { collectionView, indexPath, itemIdentifier in
let cell = collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemIdentifier)
return cell
})
// 1. 초기 리스트 보여주기
// Initial Snapshot
var snapshot = NSDiffableDataSourceSnapshot<Int, String>()
snapshot.appendSections([0])
snapshot.appendItems(list)
dataSource.apply(snapshot)
}
}
extension DiffableCollectionViewController: UICollectionViewDelegate {
// 3. 셀 클릭 시, 어럴트 창 렌더링하기
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let item = dataSource.itemIdentifier(for: indexPath) else { return }
let alert = UIAlertController(title: item, message: "클릭!", preferredStyle: .alert)
let ok = UIAlertAction(title: "확인", style: .default)
alert.addAction(ok)
present(alert, animated: true)
}
}
extension DiffableCollectionViewController: UISearchBarDelegate {
// 2. 서치바에 단어 입력 후 엔터 시, 컬렉션뷰에 추가하기
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
var snapshot = dataSource.snapshot()
snapshot.appendItems([searchBar.text!])
dataSource.apply(snapshot, animatingDifferences: true)
}
}
1. 초기 UICollectionViewLayout와 CellRegistration 설정은 이전과 동일 (Extension 활용하여 메서드 추가)
- 다만, 이 과정에서 cellRegistration을 전역변수로 지정할 필요가 없어져서 생략
- 전역변수를 지우면 메서드 내부의 cellRegistration에서 타입 오류 발생
- let cellRegistration: UICollectionView.CellRegistration<UICollectionViewListCell, String>
- 또는 위 코드처럼 해결
2. dataSource 전역변수 선언 후, Extension 내부 메서드를 통해 cellForRow/numberOfItems 매서드를 대체
- UICollectionViewDiffableDataSource 타입 할당 (개발 팁: 클로져 매개변수 부분에서 Enter)
3. Snapshot을 통한 데이터 관리
- 우선, 기존의 snapshot을 가져오고,
- snapshot에 저장된 데이터를 변경하고,
- apply하기 -> 여기에서 기존의 snapshot과 비교를 통해 레이아웃에 적용
주의사항
- 스토리보드의 Scene 상위 아이콘 목록에서 iOS 14 버전 이상에서 Search Display Controller를 사용하려면 복잡하므로 삭제하고 진행
- didSelect 같은 매서드는 UICollectionViewDelegate로부터 여전히 사용해야함
- Snapshot 기능을 사용할 때, 모델이 담긴 리스트는 변경이 되지 않음
마지막으로, 이전 게시물에서 구현했던
UICollectionViewDataSource + List Configuration 코드를
Diffable Data Source + List Configuration 코드로 개선해보자. (편의상 viewDidLoad에 모두 구현)
struct User: Hashable {
let id = UUID().uuidString
let name: String
let age: Int
}
class TestCollectionViewController: UICollectionViewController {
var list: [User] = [
User(name: "태이슨", age: 25),
User(name: "태이슨", age: 25),
User(name: "제이슨", age: 33),
User(name: "구스타보", age: 5),
]
var cellRegistration: UICollectionView.CellRegistration<UICollectionViewListCell, User>!
var dataSource: UICollectionViewDiffableDataSource<Int, User>!
override func viewDidLoad() {
super.viewDidLoad()
var configuration = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
configuration.showsSeparators = false
configuration.backgroundColor = .brown
let layout = UICollectionViewCompositionalLayout.list(using: configuration)
collectionView.collectionViewLayout = createLayout()
cellRegistration = UICollectionView.CellRegistration { cell, indexPath, itemIdentifier in
// UIContentConfiguration 프로토콜 => Cell ContentView
// contentConfiguration 채택 => label/img 관리
var content = UIListContentConfiguration.valueCell() //cell.defaultContentConfiguration()
content.text = itemIdentifier.name
content.textProperties.color = .red
content.secondaryText = "\(itemIdentifier.age)살"
content.prefersSideBySideTextAndSecondaryText = false
content.textToSecondaryTextVerticalPadding = 20
content.image = itemIdentifier.age < 8 ? UIImage(systemName: "person.fill") : UIImage(systemName: "star")
content.imageProperties.tintColor = .yellow
cell.contentConfiguration = content
// backgroundConfiguration 채택 => 그림자/배경 관리
var backgroundConfig = UIBackgroundConfiguration.listPlainCell()
backgroundConfig.backgroundColor = .lightGray
backgroundConfig.cornerRadius = 10
backgroundConfig.strokeWidth = 2
backgroundConfig.strokeColor = .systemPink
cell.backgroundConfiguration = backgroundConfig
}
dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView, cellProvider: { collectionView, indexPath, itemIdentifier in
let cell = collectionView.dequeueConfiguredReusableCell(using: self.cellRegistration, for: indexPath, item: itemIdentifier)
return cell
})
// Initial Snapshot
var snapshot = NSDiffableDataSourceSnapshot<Int, User>()
snapshot.appendSections([0])
snapshot.appendItems(list)
dataSource.apply(snapshot)
}
// 생략
// override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// return list.count
// }
// override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
// let item = list[indexPath.item]
// let cell = collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: item)
// return cell
// }
}
1. numberOfItems와 cellForItemAt 메서드 삭제
2. 사용하는 User 모델(Struct)의 Hashable 프로토콜 채택
- Class로 Hashable을 채택하면 Equatable까지 채택해야함 -> 복잡해짐 -> 보통 Struct로 구현
- UUID 속성은 초기화를 통해 애플이 고유한 값을 제공 -> 각 모델의 Hashable 프로토콜 충족
- name과 age 속성이 겹쳐도 UUID로 인해 오류가 나지 않음
[Xcode] 새로운 방법으로 컬렉션뷰 만들기(2) - UICollectionViewDataSource + List Configuration 코드 개선
'Xcode 개발' 카테고리의 다른 글
[Xcode] MVVM 패턴 (0) | 2022.11.04 |
---|---|
[Xcode] 새로운 방법으로 컬렉션뷰 만들기(2) - UICollectionViewDataSource + List Configuration 코드 개선 (0) | 2022.11.01 |
[Xcode] 새로운 방법으로 컬렉션뷰 만들기(1) - UICollectionViewDataSource + List Configuration (0) | 2022.10.31 |