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
Since I'm not using Catalina Beta, I wrote here my code you can run on Xcode 11 (Mojave) as a playground to take advantage of run-time compile and Preview
Basically when u look for a grid approach u should take in mind that SwiftUI child View get ideal size parameter from parent view so they can auto-adapt based on their own content, this behavior can be overridden (do not confuse with swift Override directive) by forcing view to a specific size via .frame(...) method.
In my opinion this make View behavior stable as well as the Apple SwiftUI framework has been correctly tested.
import PlaygroundSupport
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
ForEach(0..<5) { _ in
HStack(spacing: 0) {
ForEach(0..<5) { _ in
Button(action: {}) {
Text("Ok")
}
.frame(minWidth: nil, idealWidth: nil, maxWidth: .infinity, minHeight: nil, idealHeight: nil, maxHeight: .infinity, alignment: .center)
.border(Color.red)
}
}
}
}
}
}
let contentView = ContentView()
PlaygroundPage.current.liveView = UIHostingController(rootView: contentView)
iOS 14 and XCode 12
SwiftUI for iOS 14 brings a new and nativ grid view that is easy to use, its called LazyVGrid: https://developer.apple.com/documentation/swiftui/lazyvgrid
You can start with defining an array of GridItem's. GridItems are used to specify layout properties for each column. In this case all GridItems are flexible.
LazyVGrid takes an array of GridItem's as its parameter and displays the containing views according to the defined GridItems.
import SwiftUI
struct ContentView: View {
let columns = [
GridItem(.flexible()),
GridItem(.flexible()),
GridItem(.flexible()),
GridItem(.flexible())
]
var body: some View {
ScrollView {
LazyVGrid(columns: columns) {
ForEach(0...100, id: \.self) { _ in
Color.orange.frame(width: 100, height: 100)
}
}
}
}
}
I’ve written a small component called
XCode 11.0
After looking for a while I decided that I wanted all the convenience and performance form the UICollectionView. So I implemented the UIViewRepresentable
protocol.
This example does not implement the DataSource and has a dummy data: [Int]
field on the collection view.
You would use a @Bindable var data: [YourData]
on the AlbumGridView
to automatically reload your view when the data changes.
AlbumGridView
can then be used like any other view inside SwiftUI.
class AlbumPrivateCell: UICollectionViewCell {
private static let reuseId = "AlbumPrivateCell"
static func registerWithCollectionView(collectionView: UICollectionView) {
collectionView.register(AlbumPrivateCell.self, forCellWithReuseIdentifier: reuseId)
}
static func getReusedCellFrom(collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> AlbumPrivateCell{
return collectionView.dequeueReusableCell(withReuseIdentifier: reuseId, for: indexPath) as! AlbumPrivateCell
}
var albumView: UILabel = {
let label = UILabel()
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.addSubview(self.albumView)
albumView.translatesAutoresizingMaskIntoConstraints = false
albumView.topAnchor.constraint(equalTo: contentView.topAnchor).isActive = true
albumView.leftAnchor.constraint(equalTo: contentView.leftAnchor).isActive = true
albumView.rightAnchor.constraint(equalTo: contentView.rightAnchor).isActive = true
albumView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init?(coder: NSCoder) has not been implemented")
}
}
struct AlbumGridView: UIViewRepresentable {
var data = [1,2,3,4,5,6,7,8,9]
func makeUIView(context: Context) -> UICollectionView {
let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
collectionView.backgroundColor = .blue
collectionView.translatesAutoresizingMaskIntoConstraints = false
collectionView.dataSource = context.coordinator
collectionView.delegate = context.coordinator
AlbumPrivateCell.registerWithCollectionView(collectionView: collectionView)
return collectionView
}
func updateUIView(_ uiView: UICollectionView, context: Context) {
//
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
private let parent: AlbumGridView
init(_ albumGridView: AlbumGridView) {
self.parent = albumGridView
}
// MARK: UICollectionViewDataSource
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
self.parent.data.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let albumCell = AlbumPrivateCell.getReusedCellFrom(collectionView: collectionView, cellForItemAt: indexPath)
albumCell.backgroundColor = .red
return albumCell
}
// MARK: UICollectionViewDelegateFlowLayout
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = collectionView.frame.width / 3
return CGSize(width: width, height: width)
}
}
}
Try using a VStack and HStack
var body: some View {
GeometryReader { geometry in
VStack {
ForEach(1...3) {_ in
HStack {
Color.orange.frame(width: 100, height: 100)
Color.orange.frame(width: 100, height: 100)
Color.orange.frame(width: 100, height: 100)
}.frame(width: geometry.size.width, height: 100)
}
}
}
}
You can wrap in a ScrollView if you want scrolling
Although the next WWDC is right around the corner, we're still missing a collection view in SwiftUI
. I have tried to provide a decent example on how to create your own SwiftUI collection view using UIViewControllerRepresentable
and Combine
. I opted for creating my own collection view instead of using open source libraries as either I felt they were missing some key features or were bloated with too many things. In my example, I have used UICollectionViewDiffableDataSource
and UICollectionViewCompositionalLayout
to create the collection view. It supports both pullToRefresh
and pagination
functionality.
The full implementation can be found in: https://github.com/shabib87/SwiftUICollectionView.