Skip to content

Instantly share code, notes, and snippets.

@onmyway133
Created October 22, 2025 20:54
Show Gist options
  • Save onmyway133/9be54e6b5da1369d2519e4ccb9250073 to your computer and use it in GitHub Desktop.
Save onmyway133/9be54e6b5da1369d2519e4ccb9250073 to your computer and use it in GitHub Desktop.
UICollectionView + SwiftUI
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