Hey there, welcome to SwiftUI Play Time. In this article, we will build on top of our previous post, using the business card we created and embed it into a vertically scrollable list.
The code we’re writing is publicly accessible here: https://github.com/Rusti18/SwiftUI-Play-Time. You will be able to see the latest commit of each article by checking the tags section.
As in the first article, we’ll approach each step in a fairly simplistic manner, explaining each thing we do, so that it’s easy to understand for somebody who’s just started with programming. Feel free and fast forward through these sections if you find it too simple.
The problem we will be approaching today is fairly simple – build a list of business contacts, using the business card we created previously.
We will build a quick list of tasks, based on the model we used in the previous article. Before we get to that, we need to consider the following aspects:
- Our list should be dynamic – it should be able to display any number of items and those items should potentially differ from one another, meaning it should not repeat the same card over and over again. Though these requirements are straightforward for someone with experience, it’s something who’s currently just learning might not think of right away.
- Based on the previous point – our current card uses hardcoded data. We should look for a way to make it configurable – so let’s start with that.
Our card currently looks like this:
By looking at this piece of code – we can see what the hardcoded pieces are: “man”, “John Doe”, “Attorney at law” and “Berkley Ltd.”. In order to make the card configurable, we are going to extract these into properties and create an initializer, where we’ll directly specify these values. Our business card will become:
workplace are self-explanatory,
imageName needs a small explanation – it represents the name of the image as it can be seen under the project Assets section.
Now that we have this in place, the next step is to set up the infrastructure for the vertical business contacts list. In SwiftUI, we can achieve this in more than a single way. We shall discuss two of these ways:
- Using the
- Using a
List Component Way
SwiftUI has a native component called
List that fits our use case perfectly. You can read about it in detail here. Let’s see how it applies for our example.
We create a simple list, but we are going to hardcode two items for now. We will come back to making it dynamic later.
First, let’s create a new file, called
BusinessCardList.swift, where we’re going to define the list:
File -> New -> Under the iOS platform look for
User Interface and select ->
SwiftUI View. Insert the
As mentioned, we will only define a simple
List with hardcoded cards:
which, when running the Preview, looks like this:
Pretty simple, isn’t it? Let’s look at the
ScrollView Component Way
List applies some customizations to the items, that are out of our control, or that we need to remove ourselves – such as, for example, the separator between the items. With
ScrollView, there are no changes applied to the items, thus giving us more customization freedom.
In order to achieve the same result – or at least visually similar, due to changes provided by
List by default – you need to embed the content into a
LazyVStack, depending on the needs.
The decision between
LazyVStack is performance. In this article, Apple recommends using the second only when performance issues arise. While this might not always be straightforward, a good rule of thumb is that we should use the first for smaller lists, which are not likely to change in the long run, and go with
LazyVStack when we could use a data source that we don’t have a size guarantee of, and we’ll be expecting a lot of elements in that list.
Using the same, 2-element hardcoded option, we’re going to create a new file,
BusinessCardList_Scroll.swift to showcase this different implementation:
You can immediately notice that there is a difference here:
ScrollView does not apply any changes to the items. They’re just displayed one under the other, without separators, background color changes, corner rounding or other adjustments.
Both these examples are using two hardcoded items. How do we proceed in case we receive a larger list of items, of unknown length – for example from a server – and we want to display all of them?
Using a data source
Whenever we want to display multiple items into a dynamic context, we need to use a data source of some sort. In our case, we can use an array of items tailored for the business card, so let’s create one –
Swift File and let’s name it
Let’s make it a struct and declare the four properties we use in the business card:
We can now also update the
BusinessCard – we will use a view model to initialize the parameters, instead of explicitly passing each parameter:
We need to also update our existing
ScrollView implementations, so that the
BusinessCard is initialized with a view model instead of explicitly passing the values. I will leave this as a quick exercise for you before moving forward.
Let’s now focus on using a data source for populating our two list implementations. We can use a simple array of
BusinessCardViewModel and iterate over it inside our implementations. So a simple list implementation would become:
However, we notice that the compiler starts to complain:
Initializer 'init(_:rowContent:)' requires that 'BusinessCardViewModel' conform to 'Identifiable'
What is this and why does it matter?
For each layout rendering run,
SwiftUI explores the view and, whenever a difference is found, performs an update. I will not go in too deep into this mechanism, as it’s outside the scope of this article – you can see it explained in great depth here.
Identifiable is part of this difference detection mechanism, used to say which view is which inside the view hierarchy. It’s a protocol that adds the requirement that items conforming to it expose a property called
id that is
In order to fulfill this constraint, we will make
BusinessCardViewModel adhere to
Identifiable by adding a property called
id of type
UUID (meaning Universally Unique Identifier). We’ll initialize it at declaration time, so that it’s not synthesized within the initializer:
Once we do this, the compiler will stop complaining and we’ll be able to declare our
List without issues.
In order to create a
ScrollView – since it does not provide the same initializers as
List does – you need a component to iterate through the array of view models.
ForEach is the construct to use in this situation.
It does a similar job to what we’ve see in
List: either iterate through a collection of
Identifiable items, or iterate through a list of items, while providing a parameter to be used as a unique identifier (see the next section for more details on this).
Let’s see it in action:
The principle is the same: you iterate through a collection of items and create a card for each item! It’s even more similar when you see the same construct used for
List, which is also possible:
Another way we could iterate through view models, without having our
BusinessCardViewModel conform to
Identifiable, would be to rely on one of its properties and use it as a unique identifier. This works as long as the item is
Hashable. Basic types (
String etc.) all conform to
Hashable by default, so we can achieve it easily within our example.
List initializer and the
ForEach iteration construct we’ve mentioned support looping through a collection of items that don’t adhere to
Identifiable, as long as we provide a property of the items we’re looping through that can be used as a unique identifier.
In our case, the potential for repetition within the view model’s parameters is extremely high (you can see that even in our example, we’re using the same
imageName for both view models).
Based on that, using
imageName as an identifier, we will get:
While it works, it does not work correctly for us, as in our example we’re not using a unique
imageName, so the
List will be incorrectly built:
Since it relies on a unique parameter to build the list items, and
imageName is not unique within our data source, it will reuse a previously built card, instead of creating a new one.
Make sure to always use parameters with unique values! Otherwise, you will experience similar situations and it will not always be straightforward why the list is not rendered correctly!
This applies to using
Identifiable items too – the id we expose should also be unique!
If we replace
name – the list will be rendered correctly. However, in a real world situation, there can easily be more people with the same name, so while it would work correctly for this example, I would never rely on it in a production application.
- Our goal was to build a simple list of business cards, using the card we built in the first article. ✅
- We described two ways of achieving this:
- using the
- using the
- using the
- While we also showed an option where we hardcoded our
BusinessCarditems in the list ✅, we also presented a way to dynamically create a list from a data source ✅
- Briefly explained
Identifiable✅, how to get around it ✅, and what pitfalls to avoid whenever relying on unique identifiers to define our layout ✅
As in the first article, we’ve built these components without running the project once, by the power of SwiftUI previews.
You can find all the code, free to download, publicly accessible, at https://github.com/Rusti18/SwiftUI-Play-Time.
Hope you learnt something new today. Let me know if you have any questions. Cheers!
Leave a Reply