Leave a rating/review
All of this stacking is really exciting but how is it going to impact your development when it comes to building more familiar interfaces?
If you continue with our Apple Music example, how might you create a list of similar views, like a track list?
I’m going to show you two options: forEach and List!
-
You have a data source in the starter project. Nothing too fancy. Just some mock data for a Meow! Mix featuring cat-related songs.
-
I moved the header view out into its own file.
-
And in ContentView, I set up a couple of little views for you to use in this episode.
-
First, you’ve got this “TrackRow”
-
It’s just an HStack with the information from a given track in it.
You could manually make copies of this little row view inside of a VStack for each track in the mix, but there’s an easier way to do it.
- Inside of this VStack, just under the header and divider, start a “List”
List
There are a ton of ways to initialize a List! The absolute simplest is to use it like a VStack and just open up a closure and put some views in there…
List {
Text("Meow")
Text("Mix")
}
But there is another way to use a list that’s dynamic.
- Get rid of this closure, and let autocomplete help you find another initializer.
- This one that takes 3 arguments: data, id, and content.
- For the data, pass in the tracks array from the mix.
- The ID is meant to be a unique identifier: a way to guarantee that each item in the collection of data is unique. For this example, we could just use a keypath to a track’s title property, since I know that all of the track titles are different.
- The content closure is exactly what you’d expect from a SwiftUI component.
List(mix.tracks, id: \.title) { track in
}
It’s going to take in one item from the data, and we need to take that and return some kind of view.
- I’ve got this TrackRow all set up, so, use that and pass in the track.
List(mix.tracks, id: \.title) { track in
TrackRow(track: track)
}
- Now over in the canvas you’ve got a row for each track in the mix, with all of the data slotted in for you.
- Take a look at the model for our views over in MeowMix.swift
If you have a more complex model than this, and you really need a way to guarantee that a List can differentiate between each element in its data,
- you can use the Identifiable protocol.
Track: Identifiable
- It has one requirement, an id property
- If you’re using a class, Identifiable has a default implementation, so you don’t have to do anything else
- For a struct, like this, the easiest and fastest way to do that is to use this handy UUID type. It’s specifically designed to create a unique value for identifying things.
struct Track: Identifiable {
let id = UUID()
- Now, back in ContentView
- You don’t have to specify an ID for the List. It can automatically grab the ID property from the Track.
There’s another way to make a bunch of instances of a little view and fill them with data. You’re probably familiar with using forEach on Swift collections.
SwiftUI also has a ForEach! You’re going to use it to make a horizontally scrolling list of all of the artists in this mix.
- I’ve got a little starter view set up for that called “Featured Cats”.
- Right under the text, here, start an HStack
🤘HStack {
}
-
This view has a property that holds an array of artists
-
And we’ve got a little “FeaturedArtist” view that has a place to put the artist
-
So, we’re going to use ForEach to put them together.
-
Again, we’re going to pass in some data, an ID, and content
-
This time, the data is the artists array, and for the id, we can use a keypath to “self” since these are just strings, and we don’t need to do anything fancy with them.
ScrollView(.horizontal) {
HStack(alignment: .top, spacing: 25.0) {
ForEach(artists, id: \.self) { artist in
FeaturedArtist(artist: artist)
}
}
.padding(.horizontal)🛑
}
- The last bit is to use that FeaturedArtist view, and pass in the artist.
Notice over in the canvas that we don’t get so much fancy formatting with ForEach. But we can still customize things a bit.
- Give the HStack a top alignment
- And spacing of 25
- And give the whole Hstack a little padding horizontally.
Now, all of these views are trying to fit horizontally inside this view, but there’s not really room. We can give them more space, and make the Hstack scrollable.
- Start by embedding an HStack inside of a stack of some kind…
- And then change the stack to a ScrollView. This will let our view extend outside the bounds of the screen.
- You can specify the scrolling direction. If you don’t, it will assume you want to be able to scroll vertically AND horizontally. We just want horizontal.
ScrollView(.horizontal) {
}
That’s looking a little nicer.
- To try out the scrolling, add our FeaturedCats view to the main ContentView VStack, just at the bottom.
- Grab just the artists out of the tracks with a little map and keypath magic
- Add a little vertical padding
- and a light gray background.
FeaturedCats(artists: mix.tracks.map(\.artist))
.padding(.vertical)
.background(Color.gray.opacity(0.2))
There’s a bit of a weird space between the track list and the featured cats. If you don’t want any spacing between views by default, set the spacing on a stack explicitly to 0
var body: some View {
VStack(spacing: 0.0) {
Now you can try live previewing this whole thing!
Horizontal scrolling is working here on the bottom… And hey, the track list is scrolling too!
So, ForEach is great for dynamically creating multiple views. They work with H and V Stacks and give you a lot of flexibility.
Lists give you some more functionality out of the box. They’ve got this neat formatting with the dividers, and scrolling built in. There’s also some specific modifiers you can use to do things like move and delete rows, making Lists behave a bit more like table views in UIKit.
If that’s the kind of power you’re looking for, and you want to learn more, I recommend checking out the “Your Second iOS & SwiftUI App” course. It’s all about building up an app that features a fancy List.