SwiftUI: Layout & Interfaces

Nov 18 2021 Swift 5.5, iOS 15, Xcode 13

Part 1: Dynamic View Layout

7. Challenge: Grids with Pinned Views

Episode complete

Play next episode

Next
Save for later
About this episode
See versions

See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 6. PinnedScrollableViews Next episode: 8. Conclusion

This video Challenge: Grids with Pinned Views was last updated on Nov 18 2021

Now, we finally come to the part of the course where you see what that colorful subgenre view you’ve been using was inspired by.

It’s the “Search” view, from Apple Music. Your challenge is to create a vertically-scrolling grid-based layout, which looks like it.

Here’s the result you’ll be going for.I’ve you’ve followed the course so far, you know everything you need, to put it together…

…starting from this point. So, have at it. You’ve got this.

Hey again. Here we are in the starter project for this challenge. When I was at that point, the first thing I did was to put this single, random subgenre view, into a grid.

          LazyVGrid(
            columns: [.init()]
          ) {
            genre.subgenres.randomElement()!.view
          }

And then I used all the subgenres, for the genre.

          ) {
            ForEach(genre.subgenres, content: \.view)
          }

You could have used two flexible columns, to match the result in the introduction. But I went with a single adaptive grid item, with a width of at minimum 150 points.

            columns: [.init(.adaptive(minimum: 150))]

That would allow for more columns, on an iPad, which is how Apple’s implementation works. This phone preview isn’t wide enough to tell, but if we switch to something smaller…

(minimum: 110)

…you get the idea. But I’ll undo that, here.

(minimum: 150)

Next, I moved the header view to be a real section header.

          let genre = Genre.list.randomElement()!

          Section(header: genre.header) {
            LazyVGrid(
              columns: [.init(.adaptive(minimum: 150))]
            ) {
              ForEach(genre.subgenres, content: \.view)
            }
          }
        }

And I wanted to pin it, but to do that, I had to make the VStack Lazy first, because non-lazy VStacks don’t offer pinned views.

LazyVStack(pinnedViews: [.sectionHeaders]) {

But as you see here, that’s not actually enough to see headers. You need to use Scroll Views, in order to use pinned views.

    NavigationView {
      ScrollView {
        ScrollViewReader { scrollProxy in
}

And while I wanted the header to take up the whole width of the screen, I wanted some horizontal padding around the grid.

                  ForEach(genre.subgenres, content: \.view)
                }
                .padding(.horizontal)
              }

And that was all I needed, for one genre. I just added a ForEach with the genre list, to see all of them.

          LazyVStack(pinnedViews: [.sectionHeaders]) {
            ForEach(Genre.list) { genre in
              Section(header: genre.header) {
}

And, to make the scrollProxy work, I picked views to assign genre IDs. The section headers seemed appropriate, to me.

Section(header: genre.header.id(genre)) {

And that seemed to work well…

…but in the latest version of Xcode, that I’m using, it kind of falls apart as you go farther into the list.

So hopefully Apple fixes that bug, soon, but at least manually-scrolling grids work great!