Grid
The most powerful Grid container missed in SwiftUI
Install / Use
/learn @exyte/GridREADME
<a href="https://exyte.com/"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/exyte/media/master/common/header-dark.png"><img src="https://raw.githubusercontent.com/exyte/media/master/common/header-light.png"></picture></a>
<a href="https://exyte.com/"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/exyte/media/master/common/our-site-dark.png" width="80" height="16"><img src="https://raw.githubusercontent.com/exyte/media/master/common/our-site-light.png" width="80" height="16"></picture></a> <a href="https://twitter.com/exyteHQ"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/exyte/media/master/common/twitter-dark.png" width="74" height="16"><img src="https://raw.githubusercontent.com/exyte/media/master/common/twitter-light.png" width="74" height="16"> </picture></a> <a href="https://exyte.com/contacts"><picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/exyte/media/master/common/get-in-touch-dark.png" width="128" height="24" align="right"><img src="https://raw.githubusercontent.com/exyte/media/master/common/get-in-touch-light.png" width="128" height="24" align="right"></picture></a>
<img width="480" src="https://github.com/exyte/Grid/raw/media/Assets/calc-animation-mock-iPhone-XS-Max.gif"/>Grid
Grid view inspired by CSS Grid and written with SwiftUI
<a href="https://exyte.com/blog/implementing-grid-layout-in-swiftui">Read Article »</a>
Overview
Grid is a powerful and easy way to layout your views in SwiftUI:
<img src="https://i.imgur.com/pl3k7iE.gif">Check out full documentation below.
Installation
CocoaPods
Grid is available through CocoaPods. To install it, simply add the following line to your Podfile:
pod 'ExyteGrid'
Swift Package Manager
Grid is available through Swift Package Manager.
Add it to an existing Xcode project as a package dependency:
- From the File menu, select Swift Packages › Add Package Dependency…
- Enter "https://github.com/exyte/Grid" into the package repository URL text field
Requirements
- iOS 14.0+ (the latest iOS 13 support is in v0.1.0)
- MacOS 10.15+
- Xcode 12+
Building from sources
git clone git@github.com:exyte/Grid.git
cd Grid/Example/
pod install
open Example.xcworkspace/
Documentation
- Initialization
- View containers
- Track sizes:
- Grid cell background and overlay
- Spanning grid views:
- by rows
- by columns
- View position specifying:
- automatically (implicitly)
- start row
- start column
- both row and column
- Flow direction:
- Content mode:
- Packing mode:
- Vertical and horizontal spacing
- Alignment
- Content updates can be animated
- Caching
- Conditional statements / @GridBuilder
- Release notes
- Roadmap
1. Initialization
<img align="right" width="30%" height="30%" src="https://github.com/exyte/Grid/raw/media/Assets/3-equal-fr-tracks.png"/>You can instantiate Grid in different ways:
- Just specify tracks and your views inside ViewBuilder closure:
Grid(tracks: 3) {
ColorView(.blue)
ColorView(.purple)
ColorView(.red)
ColorView(.cyan)
ColorView(.green)
ColorView(.orange)
}
- Use Range:
Grid(0..<6, tracks: 3) { _ in
ColorView(.random)
}
- Use Identifiable enitites:
Grid(colorModels, tracks: 3) {
ColorView($0)
}
- Use explicitly defined ID:
Grid(colorModels, id: \.self, tracks: 3) {
ColorView($0)
}
2. Containers
ForEach
Inside ViewBuilder you also can use regular ForEach statement.
There is no way to get KeyPath id value from the initialized ForEach view. Its inner content will be distinguished by views order while doing animations. It's better to use ForEach with Identifiable models or GridGroup created either with explicit ID value or Identifiable models to keep track of the grid views and their View representations in animations.
Grid(tracks: 4) {
ColorView(.red)
ColorView(.purple)
ForEach(0..<4) { _ in
ColorView(.black)
}
ColorView(.orange)
ColorView(.green)
}
GridGroup
Number of views in ViewBuilder closure is limited to 10. It's impossible to obtain content views from regular SwiftUI Group view. To exceed that limit you could use GridGroup. Every view in GridGroup is placed as a separate grid item. Unlike the Group view any outer method modifications of GridView are not applied to the descendant views. So it's just an enumerable container. Also GridGroup could be created by Range<Int>, Identifiable models, by ID specified explicitly.
You can bind a view’s identity to the given single Hashable or Identifiable value also using GridGroup. This will produce transition animation to a new view with the same identity.
There is no way to use View's .id() modifier as inner ForEach view clears that value
You can use GridGroup.empty to define a content absence.
Examples:
var arithmeticButtons: GridGroup {
GridGroup {
CalcButton(.divide)
CalcButton(.multiply)
CalcButton(.substract)
CalcButton(.equal)
}
}
var arithmeticButtons: GridGroup {
let operations: [MathOperation] =
[.divide, .multiply, .substract, .add, .equal]
return GridGroup(operations, id: \.self) {
CalcButton($0)
}
}
var arithmeticButtons: GridGroup {
let operations: [MathOperation] =
[.divide, .multiply, .substract, .add, .equal]
return GridGroup {
ForEach(operations, id: \.self) {
CalcButton($0)
}
}
}
var arithmeticButtons: GridGroup {
let operations: [MathOperation] =
[.divide, .multiply, .substract, .add, .equal]
return GridGroup(operations, id: \.self) {
CalcButton($0)
}
}
var arithmeticButtons: GridGroup {
let operations: [MathOperation] =
[.divide, .multiply, .substract, .add, .equal]
return GridGroup(operations, id: \.self) {
CalcButton($0)
}
}
Grid {
...
GridGroup(MathOperation.clear) {
CalcButton($0)
}
}
3. Track sizes
There are 3 types of track sizes that you could mix with each other:
<img align="right" width="30%" height="30%" src="https://github.com/exyte/Grid/raw/media/Assets/3-const-tracks.png"/>Fixed-sized track:
.pt(N) where N - points count.
Grid(tracks: [.pt(50), .pt(200), .pt(100)]) {
ColorView(.blue)
ColorView(.purple)
ColorView(.red)
ColorView(.cyan)
ColorView(.green)
ColorView(.orange)
}
<img align="right" width="30%" height="30%" src="https://github.com/exyte/Grid/raw/media/Assets/3-fit-tracks.png"/>
Content-based size: .fit
Defines the track size as a maximum of the content sizes of every view in track
Grid(0..<6, tracks: [.fit, .fit, .fit]) {
ColorView(.random)
.frame(maxWidth: 50 + 15 * CGFloat($0))
}
Pay attention to limiting a size of views that fills the entire space provided by parent and Text() views which tend to draw as a single line.
Flexible sized track: .fr(N)
<img align="right" width="30%" height="30%" src="https://github.com/exyte/Grid/raw/media/Assets/3-fr-tracks.png"/>
Fr is a fractional unit and .fr(1) is for 1 part of the unassigned space in the grid. Flexible-sized tracks are computed at the very end after all non-flexible sized tracks (.pt and .fit).
So the available space to distribute for them is the difference of the total size available and the sum of non-flexible track sizes.
Grid(tracks: [.pt(100), .fr(1), .fr(2.5)]) {
ColorView(.blue)
ColorView(.purple)
ColorView(.red)
ColorView(.cyan)
ColorView(.green)
ColorView(.orange)
}
Also, you could specify just an Int literal as a track size. It's equal to repeating .fr(1) trac
