Code Block language| class OutlineViewController: UIViewController { |
| |
| enum Section { |
| case main |
| } |
| |
| class OutlineItem: Hashable { |
| let title: String |
| let subitems: [OutlineItem] |
| let outlineViewController: UIViewController.Type? |
| |
| init(title: String, |
| viewController: UIViewController.Type? = nil, |
| subitems: [OutlineItem] = []) { |
| self.title = title |
| self.subitems = subitems |
| self.outlineViewController = viewController |
| } |
| func hash(into hasher: inout Hasher) { |
| hasher.combine(identifier) |
| } |
| static func == (lhs: OutlineItem, rhs: OutlineItem) -> Bool { |
| return lhs.identifier == rhs.identifier |
| } |
| private let identifier = UUID() |
| } |
| |
| var dataSource: UICollectionViewDiffableDataSource<Section, OutlineItem>! = nil |
| var outlineCollectionView: UICollectionView! = nil |
| |
| override func viewDidLoad() { |
| super.viewDidLoad() |
| navigationItem.title = "Modern Collection Views" |
| configureCollectionView() |
| configureDataSource() |
| |
| navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .trash, target: self, action: #selector(removeCells)) |
| } |
| |
| @objc func removeCells() { |
| |
| guard let item = menuItems.first(where: { $0.title == "Outlines" }) else { |
| return |
| } |
| |
| let items = [item] + item.subitems |
| |
| var snapshot = dataSource.snapshot() |
| snapshot.deleteItems(items) |
| |
| dataSource.apply(snapshot, animatingDifferences: true) |
| } |
| |
| private lazy var menuItems: [OutlineItem] = { |
| return [ |
| OutlineItem(title: "Lists", subitems: [ |
| OutlineItem(title: "Simple List", viewController: SimpleListViewController.self), |
| OutlineItem(title: "Reorderable List", viewController: ReorderableListViewController.self), |
| OutlineItem(title: "List Appearances", viewController: ListAppearancesViewController.self), |
| OutlineItem(title: "List with Custom Cells", viewController: CustomCellListViewController.self) |
| ]), |
| OutlineItem(title: "Outlines", subitems: [ |
| OutlineItem(title: "Emoji Explorer", viewController: EmojiExplorerViewController.self), |
| OutlineItem(title: "Emoji Explorer - List", viewController: EmojiExplorerListViewController.self) |
| ]), |
| OutlineItem(title: "Cell Configurations", subitems: [ |
| OutlineItem(title: "Custom Configurations", viewController: CustomConfigurationViewController.self) |
| ]) |
| ] |
| }() |
| |
| } |
|
| extension OutlineViewController { |
| |
| func configureCollectionView() { |
| let collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: generateLayout()) |
| view.addSubview(collectionView) |
| collectionView.autoresizingMask = [.flexibleHeight, .flexibleWidth] |
| collectionView.backgroundColor = .systemGroupedBackground |
| self.outlineCollectionView = collectionView |
| collectionView.delegate = self |
| } |
| |
| func configureDataSource() { |
| |
| let containerCellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, OutlineItem> { (cell, indexPath, menuItem) in |
| var contentConfiguration = cell.defaultContentConfiguration() |
| contentConfiguration.text = menuItem.title |
| contentConfiguration.textProperties.font = .preferredFont(forTextStyle: .headline) |
| cell.contentConfiguration = contentConfiguration |
| |
| let disclosureOptions = UICellAccessory.OutlineDisclosureOptions(style: .header) |
| cell.accessories = [.outlineDisclosure(options: disclosureOptions)] |
| cell.backgroundConfiguration = UIBackgroundConfiguration.clear() |
| } |
| |
| let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, OutlineItem> { cell, indexPath, menuItem in |
| var contentConfiguration = cell.defaultContentConfiguration() |
| contentConfiguration.text = menuItem.title |
| cell.contentConfiguration = contentConfiguration |
| cell.backgroundConfiguration = UIBackgroundConfiguration.clear() |
| } |
| |
| dataSource = UICollectionViewDiffableDataSource<Section, OutlineItem>(collectionView: outlineCollectionView) { |
| (collectionView: UICollectionView, indexPath: IndexPath, item: OutlineItem) -> UICollectionViewCell? in |
| if item.subitems.isEmpty { |
| return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: item) |
| } else { |
| return collectionView.dequeueConfiguredReusableCell(using: containerCellRegistration, for: indexPath, item: item) |
| } |
| } |
| |
| let snapshot = initialSnapshot() |
| self.dataSource.apply(snapshot, to: .main, animatingDifferences: false) |
| } |
| |
| func generateLayout() -> UICollectionViewLayout { |
| let listConfiguration = UICollectionLayoutListConfiguration(appearance: .sidebar) |
| let layout = UICollectionViewCompositionalLayout.list(using: listConfiguration) |
| return layout |
| } |
| |
| func initialSnapshot() -> NSDiffableDataSourceSectionSnapshot<OutlineItem> { |
| var snapshot = NSDiffableDataSourceSectionSnapshot<OutlineItem>() |
| |
| func addItems(_ menuItems: [OutlineItem], to parent: OutlineItem?) { |
| snapshot.append(menuItems, to: parent) |
| for menuItem in menuItems where !menuItem.subitems.isEmpty { |
| addItems(menuItem.subitems, to: menuItem) |
| } |
| } |
| |
| addItems(menuItems, to: nil) |
| return snapshot |
| } |
| } |