How to create grid of square items (for example like in iOS Photo Library) with SwiftUI?
I tried this approach but it doesn\'t work:
var body: some
I've been tackling this problem myself, and by using the source posted above by @Anjali as a base, a well as @phillip, (the work of Avery Vine), I've wrapped a UICollectionView that is functional...ish? It'll display and update a grid as needed. I haven't tried the more customizable views or any other things, but for now, I think it'll do.
I commented my code below, hope it's useful to someone!
First, the wrapper.
struct UIKitCollectionView: UIViewRepresentable {
typealias UIViewType = UICollectionView
//This is where the magic happens! This binding allows the UI to update.
@Binding var snapshot: NSDiffableDataSourceSnapshot
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: UIViewRepresentableContext) -> UICollectionView {
//Create and configure your layout flow seperately
let flowLayout = UICollectionViewFlowLayout()
flowLayout.sectionInsets = UIEdgeInsets(top: 25, left: 0, bottom: 25, right: 0)
//And create the UICollection View
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout)
//Create your cells seperately, and populate as needed.
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "customCell")
//And set your datasource - referenced from Avery
let dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { (collectionView, indexPath, object) -> UICollectionViewCell? in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "customCell", for: indexPath)
//Do cell customization here
if object.id.uuidString.contains("D") {
cell.backgroundColor = .red
} else {
cell.backgroundColor = .green
}
return cell
}
context.coordinator.dataSource = dataSource
populate(load: [DataObject(), DataObject()], dataSource: dataSource)
return collectionView
}
func populate(load: [DataObject], dataSource: UICollectionViewDiffableDataSource) {
//Load the 'empty' state here!
//Or any default data. You also don't even have to call this function - I just thought it might be useful, and Avery uses it in their example.
snapshot.appendItems(load)
dataSource.apply(snapshot, animatingDifferences: true) {
//Whatever other actions you need to do here.
}
}
func updateUIView(_ uiView: UICollectionView, context: UIViewRepresentableContext) {
let dataSource = context.coordinator.dataSource
//This is where updates happen - when snapshot is changed, this function is called automatically.
dataSource?.apply(snapshot, animatingDifferences: true, completion: {
//Any other things you need to do here.
})
}
class Coordinator: NSObject {
var parent: UIKitCollectionView
var dataSource: UICollectionViewDiffableDataSource?
var snapshot = NSDiffableDataSourceSnapshot()
init(_ collectionView: UIKitCollectionView) {
self.parent = collectionView
}
}
}
Now, the DataProvider class will allow us to access that bindable snapshot and update the UI when we want it to. This class is essential to the collection view updating properly. The models DataSection and DataObject are of the same structure as the one provided by Avery Vine - so if you need those, look there.
class DataProvider: ObservableObject { //This HAS to be an ObservableObject, or our UpdateUIView function won't fire!
var data = [DataObject]()
@Published var snapshot : NSDiffableDataSourceSnapshot = {
//Set all of your sections here, or at least your main section.
var snap = NSDiffableDataSourceSnapshot()
snap.appendSections([.main, .second])
return snap
}() {
didSet {
self.data = self.snapshot.itemIdentifiers
//I set the 'data' to be equal to the snapshot here, in the event I just want a list of the data. Not necessary.
}
}
//Create any snapshot editing functions here! You can also simply call snapshot functions directly, append, delete, but I have this addItem function to prevent an exception crash.
func addItems(items: [DataObject], to section: DataSection) {
if snapshot.sectionIdentifiers.contains(section) {
snapshot.appendItems(items, toSection: section)
} else {
snapshot.appendSections([section])
snapshot.appendItems(items, toSection: section)
}
}
}
And now, the CollectionView, which is going to display our new collection. I made a simple VStack with some buttons so you can see it in action.
struct CollectionView: View {
@ObservedObject var dataProvider = DataProvider()
var body: some View {
VStack {
UIKitCollectionView(snapshot: $dataProvider.snapshot)
Button("Add a box") {
self.dataProvider.addItems(items: [DataObject(), DataObject()], to: .main)
}
Button("Append a Box in Section Two") {
self.dataProvider.addItems(items: [DataObject(), DataObject()], to: .second)
}
Button("Remove all Boxes in Section Two") {
self.dataProvider.snapshot.deleteSections([.second])
}
}
}
}
struct CollectionView_Previews: PreviewProvider {
static var previews: some View {
CollectionView()
}
}
And just for those visual referencers (ye, this is running in the Xcode Preview window):