Cleaner grid initialization in SwiftUI

Aug 30, 2023 · Follow on Twitter and Mastodon swiftui

SwiftUI’s LazyVGrid and LazyHGrid are great for creating flexible grids. I however always find myself struggling with their initialization and have therefore created some utilities.

SwiftUI lets you define multiple columns for your grid. For instance, this would create a grid where the items would alternate between a fixed size of 100 and 150:

LazyVGrid(columns: [.init(.fixed(100)), .init(.fixed(150))]) {
    ...
}

and this a grid where all items will try to find an ideal size within a provided size range:

LazyVGrid(columns: [.init(.adaptive(minimum: 100, maximum: 150))]) {
    ...
}

That columns is an array is great, since it lets us combine the different GridItem types for maximum flexibility. However, I think the above reads really bad.

For instance, I tried typing the fixed-size example in plain text when writing this post, but to no surprise it had syntax errors when pasting the code into Xcode.

Also, having to provide an adaptive items array when just having one item, never becomes intuitive to me. Finally, having to type .init(...) makes the code harder to read.

To improve usage and readability, we can add a few GridItem and Collection extensions:

public extension GridItem {
    
    /// Multiple items in the space of a single flexible item.
    static func adaptive(minimum: CGFloat, maximum: CGFloat) -> Self {
        .init(.adaptive(minimum: minimum, maximum: maximum))
    }
    
    /// A single item with the specified fixed size.
    static func fixed(_ size: CGFloat) -> Self {
        .init(.fixed(size))
    }
    
    /// A single flexible item.
    static func flexible(minimum: CGFloat, maximum: CGFloat) -> Self {
        .init(.flexible(minimum: minimum, maximum: maximum))
    }
}

public extension Collection where Element == GridItem {
    
    /// Multiple items in the space of a single flexible item.
    static func adaptive(minimum: CGFloat, maximum: CGFloat) -> [Element] {
        [.adaptive(minimum: minimum, maximum: maximum)]
    }
    
    /// A single item with the specified fixed size.
    static func fixed(_ size: CGFloat) -> [Element] {
        [.fixed(size)]
    }
    
    /// Multiple items with the specified fixed sizes.
    static func fixed(_ sizes: [CGFloat]) -> [Element] {
        sizes.map { .fixed($0) }
    }
    
    /// A single flexible item.
    static func flexible(minimum: CGFloat, maximum: CGFloat) -> [Element] {
        [.flexible(minimum: minimum, maximum: maximum)]
    }
}

These static builders remove the need of having to use .init(...), and make it easier to specify single items, as well as multiple fixed-size items.

With these extensions in place, you can now create grids like this:

LazyVGrid(columns: .fixed(100)) { ... }
LazyVGrid(columns: [.fixed(100), .fixed(150)]) { ... }
LazyVGrid(columns: .adaptive(minimum: 100, maximum: 150)) { ... }

Since we can still specify columns as arrays, just in a little cleaner way, we don’t loose any flexibility and can mix and match items as we like.

Discussions & More

Please share any ideas, feedback or comments you may have in the Disqus section below, or by replying to this tweet or this toot.

If you found this text interesting, make sure to follow me on Twitter and Mastodon for more content like this, and to be notified when new content is published.

If you like & want to support my work, please consider sponsoring me on GitHub Sponsors.