섹션마다 타입이 다른 셀로 disclosure collection view 만들기!
디자인을 받아보고선 이건 대체 어케 만드는걸까… 한참을 고민했다…
애플에서 제공한 Modern CollectionView 프로젝트에서 봤던 기억이 났고…
해당 프로젝트를 열어 코드를 뜯어보며 만들어나갔다…
UICollectionViewListCell의 UICellAccessory 설정하기
현재 진행하는 프로젝트에 따라 내부에는 커스텀 셀, 타이틀에 해당하는 셀은 UICollectionViewListCell을 활용하기로 한다.
UICollectionViewListCell의 악세사리 옵션 중 OutlineDisclosureOptions
을 이용하여 타이틀 셀을 접었다 펴기를 사용할 수 있도록 설정한다.
// 부모 셀 (타이틀)
let titleCell = UICollectionView.CellRegistration<UICollectionViewListCell, WorkspaceItem> { cell, indexPath, itemIdentifier in
var contentConfiguration = cell.defaultContentConfiguration()
contentConfiguration.text = itemIdentifier.title
contentConfiguration.textProperties.font = Font.title2.fontStyle
cell.contentConfiguration = contentConfiguration
let disclosureOptions = UICellAccessory.OutlineDisclosureOptions(style: .header, tintColor: Constants.Color.black)
cell.accessories = [.outlineDisclosure(options: disclosureOptions)]
}
// 자식 셀로 사용할 것
let channelCell = UICollectionView.CellRegistration<WorkspaceCollectionViewCell, Channel> { cell, indexPath, itemIdentifier in
cell.titleLabel.text = itemIdentifier.name
cell.imageView.image = .hashTagThin
}
하나의 섹션 내부에 하나의 부모(타이틀)셀이 존재, 나머지 셀은 타이틀 내부 자식 셀로 들어가는 구조!
모델 생성
하나의 컬렉션뷰에 섹션별로 다른 타입의 데이터를 가지고 있도록 설정해야하기 때문에 복잡한 설정이 필요하다…
struct WorkspaceItem: Hashable {
let id = UUID()
var title: String
var subItems: [WorkspaceItem] // channel, dm, newfriend
var item: AnyHashable?
}
컬렉션뷰 셀에 들어갈 아이템 구조체를 생성한다. 모든 셀에 이 구조체를 공통으로 사용할 것이다.
타이틀 셀 안에 자식 셀이 있는 구조로 만들어야 하기 때문에 WorkspaceItem 배열을 프로퍼티로 생성한다.
어떤 데이터가 타이틀이며 어떤 데이터가 자식 셀인지 구분하는 방식은 subItems에 값이 있는지 여부로 판단한다.
item의 타입을 AnyHashable로 설정하여 Hashable 타입을 갖는 구조체가 아이템으로 들어갈 수 있도록 설정한다.
자식 셀로 들어갈 데이터를 생성한다.
let channelItem = [
WorkspaceItem(title: "", subItems: [], item: Channel(name: "일반")),
WorkspaceItem(title: "", subItems: [], item: Channel(name: "일반"))
]
let dmItem = [
WorkspaceItem(title: "", subItems: [], item: DM(name: "jiyeon12")),
WorkspaceItem(title: "", subItems: [], item: DM(name: "jiyeon23"))
]
let newFriend = [
WorkspaceItem(title: "", subItems: [], item: NewFriend(title: "팀원 추가"))
]
각 Channel, DM, NewFriend 구조체는 Hashable을 채택하는 구조체이다.
let channelSection = WorkspaceItem(title: "채널", subItems: channelItem)
let dmSection = WorkspaceItem(title: "다이렉트 메세지", subItems: dmItem)
부모 셀로 들어갈 아이템을 생성하여 subItems에 자식 데이터를 넣어준다.
NSDiffableDataSourceSectionSnapshot
단일 섹션에 대한 데이터를 캡술화 하는 기능을 가지고 있다.
아웃라인 스타일을 사용할 때 필요한 계층적 데이터 모델링을 허용하는 기능을 한다.
쉽게 말하자면… 하나의 섹션 내에서 부모 셀 내부에 자식 셀로 들어갈 아이템을 넣어주는 것이다.
private func updateSnapShot(section: WorkspaceType, item: [WorkspaceItem]) {
let snapshot = initialSnapshot(items: item)
switch section {
case .channel:
mainView.dataSource.apply(snapshot, to: .channel, animatingDifferences: false)
case .dm:
mainView.dataSource.apply(snapshot, to: .dm, animatingDifferences: false)
case .newFriend:
mainView.dataSource.apply(snapshot, to: .newFriend, animatingDifferences: false)
}
}
func initialSnapshot(items: [WorkspaceItem]) -> NSDiffableDataSourceSectionSnapshot<WorkspaceItem> {
var snapshot = NSDiffableDataSourceSectionSnapshot<WorkspaceItem>()
snapshot.append(items, to: nil)
for item in items where !item.subItems.isEmpty{
snapshot.append(item.subItems, to: item) // subitem을 item 내부로 넣어주기
}
return snapshot
}
initialSnapshot 메서드에서 하나의 섹션에 대한 스냅샷을 처리한다.
subitem 값이 존재하면 부모 스냅샷에 자식으로 아이템을 추가하는 구조이다.
채널(”채널1”, “채널2”) 이러한 구조로 들어가는 것!
initialSnaphot에서 처리한 스냅샷을 각 섹션 별로 apply하면 된다.
NSDiffableDataSourceSnapshot
DatasourceSnapshot을 통해 섹션의 순서를 정해준다.
순서를 정해주지 않으면 아이템이 apply가 완료되는 순서대로 들어가기 때문에 먼저 섹션 설정을 해준다.
private func sectionSnapShot() {
var snapshot = NSDiffableDataSourceSnapshot<WorkspaceType, WorkspaceItem>()
snapshot.appendSections([.channel, .dm, .newFriend])
mainView.dataSource.apply(snapshot)
}
데이터에 따라 다른 셀을 사용하기
DiffableDatasource 설정 시 subitem의 유무에 따라 셀을 다르게 설정한다.
var dataSource: UICollectionViewDiffableDataSource<WorkspaceType, WorkspaceItem>!
dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView, cellProvider: { collectionView, indexPath, itemIdentifier in
if itemIdentifier.subItems.count == 0 { // 자식 셀
if let channel = itemIdentifier.item as? Channel {
return collectionView.dequeueConfiguredReusableCell(using: channelCell, for: indexPath, item: channel)
}
else if let dm = itemIdentifier.item as? DM {
return collectionView.dequeueConfiguredReusableCell(using: dmCell, for: indexPath, item: dm)
}
else if let newFirend = itemIdentifier.item as? NewFriend {
return collectionView.dequeueConfiguredReusableCell(using: newFriendCell, for: indexPath, item: newFirend)
}
else {
return UICollectionViewCell()
}
} else { // 부모 셀
let cell = collectionView.dequeueConfiguredReusableCell(using: titleCell, for: indexPath, item: itemIdentifier)
return cell
}
})
섹션 별로 각 타입이 다르기 때문에 itemIdentifier로 넘어오는 item을 타입캐스팅 하여 나누어 처리해준다.
아이템 개수에 따라 토글 초기 설정
아이템의 개수가 1개일 때는 접고, 1개보다 많을 경우에만 펴놓고싶다!
func initialSnapshot(items: [WorkspaceItem]) -> NSDiffableDataSourceSectionSnapshot<WorkspaceItem> {
var snapshot = NSDiffableDataSourceSectionSnapshot<WorkspaceItem>()
snapshot.append(items, to: nil)
for item in items where !item.subItems.isEmpty{
snapshot.append(item.subItems, to: item)
if item.subItems.count > 1 {
snapshot.expand(items) // 펼치기
}
}
return snapshot
}
snapshot.expand를 통해 snapshot 적용 시 펼치기 설정을 하였다.!
참고
'iOS > 🗂️ 내 코드 기록하기' 카테고리의 다른 글
[RxSwift] Rx 를 사용하여시스템 권한 요청받기 (카메라, 앨범, 알림) (0) | 2024.11.06 |
---|---|
[iOS/Swift] 인앱에서 앱스토어 리뷰 팝업 띄우기 (0) | 2024.10.24 |
[iOS/SwiftUI] Text 더보기 버튼 만들기 - ViewThatFits (1) | 2024.04.19 |