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:
While name
, occupation
and 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
List
component - Using a
ScrollView
with aForEach
and aVStack
orLazyVStack
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 BusinessCardList
name.
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
option.
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 VStack
or LazyVStack
, depending on the needs.
The decision between VStack
and 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 – File
-> New
-> Swift File
and let’s name it BusinessCardViewModel
.
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 List
and 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?
Introducing Identifiable
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.
Simply put, 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 Hashable
.
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.
ScrollView
and List
with ForEach
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:
Working around Identifiable
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 (Int
, String
etc.) all conform to Hashable
by default, so we can achieve it easily within our example.
Both the 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 imageName
with 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.
Wrap-up
- 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
List
component ✅ - using the
ScrollView
component ✅
- using the
- While we also showed an option where we hardcoded our
BusinessCard
items 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