Created
October 22, 2025 20:54
-
-
Save onmyway133/9be54e6b5da1369d2519e4ccb9250073 to your computer and use it in GitHub Desktop.
UICollectionView + SwiftUI
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import SwiftUI | |
| import UIKit | |
| struct CollectionView<Item: Hashable, CellContent: View>: UIViewRepresentable { | |
| let items: [Item] | |
| let columns: Int | |
| let spacing: CGFloat | |
| let cellContent: (Item) -> CellContent | |
| init( | |
| items: [Item], | |
| columns: Int = 2, | |
| spacing: CGFloat = 16, | |
| @ViewBuilder cellContent: @escaping (Item) -> CellContent | |
| ) { | |
| self.items = items | |
| self.columns = columns | |
| self.spacing = spacing | |
| self.cellContent = cellContent | |
| } | |
| func makeUIView(context: Context) -> UICollectionView { | |
| let layout = createCompositionalLayout() | |
| let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) | |
| collectionView.backgroundColor = UIColor.clear | |
| collectionView.showsVerticalScrollIndicator = false | |
| collectionView.showsHorizontalScrollIndicator = false | |
| // Register a basic UICollectionViewCell | |
| collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "Cell") | |
| // Set up the data source | |
| let coordinator = context.coordinator | |
| coordinator.setupDataSource(for: collectionView) | |
| return collectionView | |
| } | |
| private func createCompositionalLayout() -> UICollectionViewCompositionalLayout { | |
| let itemSize = NSCollectionLayoutSize( | |
| widthDimension: .fractionalWidth(1.0 / CGFloat(columns)), | |
| heightDimension: .fractionalWidth(1.0 / CGFloat(columns)) | |
| ) | |
| let item = NSCollectionLayoutItem(layoutSize: itemSize) | |
| let groupSize = NSCollectionLayoutSize( | |
| widthDimension: .fractionalWidth(1.0), | |
| heightDimension: .fractionalWidth(1.0 / CGFloat(columns)) | |
| ) | |
| let group = NSCollectionLayoutGroup.horizontal( | |
| layoutSize: groupSize, | |
| subitems: [item] | |
| ) | |
| group.interItemSpacing = .fixed(spacing) | |
| let section = NSCollectionLayoutSection(group: group) | |
| section.interGroupSpacing = spacing | |
| section.contentInsets = NSDirectionalEdgeInsets( | |
| top: spacing, | |
| leading: spacing, | |
| bottom: spacing, | |
| trailing: spacing | |
| ) | |
| return UICollectionViewCompositionalLayout(section: section) | |
| } | |
| func updateUIView(_ collectionView: UICollectionView, context: Context) { | |
| let coordinator = context.coordinator | |
| coordinator.updateItems(items) | |
| } | |
| func makeCoordinator() -> Coordinator { | |
| Coordinator(self) | |
| } | |
| class Coordinator: NSObject { | |
| let parent: CollectionView<Item, CellContent> | |
| private var dataSource: UICollectionViewDiffableDataSource<Int, Item>? | |
| init(_ parent: CollectionView<Item, CellContent>) { | |
| self.parent = parent | |
| } | |
| func updateItems(_ newItems: [Item]) { | |
| guard let dataSource = dataSource else { return } | |
| var snapshot = NSDiffableDataSourceSnapshot<Int, Item>() | |
| snapshot.appendSections([0]) | |
| snapshot.appendItems(newItems) | |
| dataSource.apply(snapshot, animatingDifferences: true) | |
| } | |
| func setupDataSource(for collectionView: UICollectionView) { | |
| dataSource = UICollectionViewDiffableDataSource<Int, Item>( | |
| collectionView: collectionView | |
| ) { [weak self] collectionView, indexPath, item in | |
| guard let self else { | |
| return UICollectionViewCell() | |
| } | |
| let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) | |
| let content = self.parent.cellContent(item) | |
| cell.contentConfiguration = UIHostingConfiguration { | |
| content | |
| } | |
| return cell | |
| } | |
| } | |
| } | |
| } | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment