// // ViewController.swift // Test // // Created by Gunter Kwon on 9/27/24. // import UIKit class ViewController: UIViewController { @IBOutlet weak var collectionView: UICollectionView! struct Section: Hashable { let id: String var title: String } struct Item: Hashable { let id: String let title: String } var dataSource: UICollectionViewDiffableDataSource! var sections: [Section] = [ Section( id: "1", title: "Fruits" ), Section( id: "2", title: "Vegetables" ) ] var itemsInSection: [Section: [Item]] = [:] var sectionLayoutsCache: [UUID: NSCollectionLayoutSection] = [:] override func viewDidLoad() { super.viewDidLoad() configureCollectionView() configureDataSource() initializeData() applySnapshot(animatingDifferences: false) } func configureCollectionView() { collectionView.collectionViewLayout = createLayout() collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "MyCell") collectionView.register( UICollectionReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "HeaderView" ) } func createLayout() -> UICollectionViewLayout { let layout = UICollectionViewCompositionalLayout { [weak self] (sectionIndex, environment) -> NSCollectionLayoutSection? in guard let self = self else { return nil } let section = self.sections[sectionIndex] let sectionID = section.id debugPrint("gunter \(#function) \(sectionID)") let sectionLayout: NSCollectionLayoutSection = { let itemSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(50) ) let item = NSCollectionLayoutItem(layoutSize: itemSize) let groupSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(50) ) let group = NSCollectionLayoutGroup.horizontal( layoutSize: groupSize, subitems: [item] ) let sectionLayout = NSCollectionLayoutSection(group: group) let headerSize = NSCollectionLayoutSize( widthDimension: .fractionalWidth(1.0), heightDimension: .estimated(44) ) let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem( layoutSize: headerSize, elementKind: UICollectionView.elementKindSectionHeader, alignment: .top ) sectionLayout.boundarySupplementaryItems = [sectionHeader] return sectionLayout }() return sectionLayout } return layout } func configureDataSource() { dataSource = UICollectionViewDiffableDataSource( collectionView: collectionView, cellProvider: { (collectionView, indexPath, item) -> UICollectionViewCell? in let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MyCell", for: indexPath) cell.contentView.backgroundColor = .systemGray5 cell.layer.cornerRadius = 8 let tag = 100 if let label = cell.contentView.viewWithTag(tag) as? UILabel { label.text = item.title } else { let label = UILabel(frame: cell.contentView.bounds) label.tag = tag label.text = item.title label.textAlignment = .center cell.contentView.addSubview(label) } return cell } ) dataSource.supplementaryViewProvider = { (collectionView, kind, indexPath) in guard kind == UICollectionView.elementKindSectionHeader else { return nil } let headerView = collectionView.dequeueReusableSupplementaryView( ofKind: kind, withReuseIdentifier: "HeaderView", for: indexPath ) let tag = 200 if let label = headerView.viewWithTag(tag) as? UILabel { let section = self.sections[indexPath.section] label.text = section.title } else { let label = UILabel(frame: headerView.bounds) label.tag = tag label.textAlignment = .left label.font = UIFont.boldSystemFont(ofSize: 20) let section = self.sections[indexPath.section] label.text = section.title headerView.addSubview(label) } return headerView } } func initializeData() { // 각 섹션에 아이템 초기화 let fruits = [ Item(id: "1",title: "Apple"), Item(id: "2", title: "Banana"), Item(id: "3", title: "Cherry") ] let vegetables = [ Item(id: "4", title: "Carrot"), Item(id: "5", title: "Lettuce"), Item(id: "6", title: "Tomato") ] itemsInSection[sections[0]] = fruits itemsInSection[sections[1]] = vegetables } func applySnapshot(animatingDifferences: Bool = true) { var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections(sections) for section in sections { let items = itemsInSection[section] ?? [] snapshot.appendItems(items, toSection: section) } dataSource.apply(snapshot, animatingDifferences: animatingDifferences) } @IBAction func addSection(_ sender: Any) { let newSection = Section(id: UUID().uuidString, title: "Section \(sections.count + 1)") sections.append(newSection) itemsInSection[newSection] = [] applySnapshot() } @IBAction func removeSection(_ sender: Any) { guard !sections.isEmpty else { return } let removedSection = sections.removeLast() itemsInSection[removedSection] = nil applySnapshot() } @IBAction func addItem(_ sender: Any) { guard !sections.isEmpty else { return } let randomSection = sections.randomElement()! let newItem = Item(id: UUID().uuidString, title: "New Item \(Int.random(in: 1...100))") itemsInSection[randomSection]?.append(newItem) applySnapshot() } @IBAction func removeItem(_ sender: Any) { guard !sections.isEmpty else { return } let randomSection = sections.randomElement()! guard var items = itemsInSection[randomSection], !items.isEmpty else { return } items.removeLast() itemsInSection[randomSection] = items applySnapshot() } }