Creating a list in SwiftUI
Tags: ios, swift, swiftui
Published: 2020-02-11 07:52:26

---
title: Creating a list in SwiftUI
published: true
description: 
tags: ios, swift, swiftui
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/dgl9nexvb00x9zos5gag.png
series: CometChat SwiftUI course
canonical_url: https://www.cometchat.com/tutorials/creating-a-list-in-swiftui-3-7/
---

By this part of the course, you've already built a couple of different SwiftUI screens using a bunch of SwiftUI views. You've used `Text`s, `Image`s, `Rectangle`s, `VStack`s and `HStack`s, but there's one type of screen we haven't yet built: A list.

Lists (AKA table views) are at the core of almost all iOS apps. The most popular apps like Instagram, Twitter or Reddit all show a big list of stuff on their main screen. In this part of this SwiftUI course, you'll learn how to show a list of items in SwiftUI.

You'll learn this by building a contacts screen. This screen will show all of the users your current user can chat with. By the end of this part, you'll have a screen that looks like this:

![Showing a List in SwiftUI](https://dev-to-uploads.s3.amazonaws.com/i/eetwlcs83ec25g8zxpd3.png)

Before you can build that screen, though, you first need a way to navigate to it. Let's get started!

> You can always find the [finished project code on GitHub](https://github.com/cometchat-pro-tutorials/swift-ui-chat).

## Navigating to the contacts screen

You'll start by creating a new SwiftUI view file for your contacts screen and naming it **ContactsView.swift**. Change the struct to the following:

```swift
import SwiftUI

struct ContactsView: View {
  
  var body: some View {
    Text("Contacts")
  }

}
```

So far it doesn't show much — you'll take care of that later. For now, let's add a way to navigate to this view. Open **LoginView.swift** and change the navigation link's destination to show the newly created contacts view, instead of `EmptyView`:

```swift
NavigationLink(destination: ContactsView(), isActive: $showContacts) {
  EmptyView()
}
```

If you run the app now and click the login button, you'll navigate to the view. Now we can fill it up with contacts! Well... in a bit.

> I am creating this course in collaboration with CometChat, a modern chat platform to help you add chat to you Swift app. During the next few weeks, we’ll be releasing installments of our free SwiftUI course here on dev.to! In the course, you’ll dive deep into SwiftUI by building a real-world production-scale chat app, learning SwiftUI in a practical way, on a scale larger than a simple example app. Follow me to get notified of future parts of this course! You can also follow [@CometChat](https://twitter.com/cometchat) on Twitter see the course of [CometChat’s blog](https://www.cometchat.com/blog/).

[![](https://thepracticaldev.s3.amazonaws.com/i/avpunu4hqp1ut7enf5tq.png)](https://www.cometchat.com)

## What's in a List?

First, we'll lay some groundwork on SwiftUI lists. In SwiftUI, Lists are created by using a view called, shockingly, `List`. Much like `Group` or `VStack`, a `List` is a sort of wrapper around a bunch of child views. It hugs all of its children in a tight embrace and positions them on the screen, one below the other, with separators, paddings, a scroll view and everything else you'd expect from a list.

SwiftUI's `List` is backed by a `UITableView` on iOS, which means that the same cell reuse behavior you're used to is there on SwiftUI, too. Apple is an environmentally conscious company, after all, so cells are **recycled**.

Instead of loading all of your rows at once, SwiftUI will only take up memory for the rows that are currently visible. As you scroll, the rows that disappear are not destroyed, but instead, go into a pool of deactivated rows. When the list needs a new row, instead of allocating one, it will take one from the pool.

When the list takes a row from the pool, it will apply any necessary changes to its views (text values, images, etc.) to match the item at the current index. This gives the appearance of having many rows, while in reality only a couple are ever loaded at once.

All this is to say that `List`s are very fast and memory-efficient, regardless of how many rows there are in the list.

In UIKit, you'd probably first create a table view and then think about placing items in that table. The component-based nature of SwiftUI means you'll have to reverse your thinking: Start from smaller views and work your way up.

That's why you'll first create the view for each row in your list and only then begin assembling the contacts screen.

> **Note**: On websites like StackOverflow, you'll often see people suggesting you replace `List` with a `ScrollView` and a `ForEach` view to achieve a desired look. In some cases, this works well. However, `ScrollView` will load all the items at once. If you have a lot of items, a plain scroll view will be _much less efficient_ than a `List`!

## Making a SiwftUI List row view

Before we create our items, we need a model struct to house information about each contact. Create a new plain Swift file called **Contact.swift**. Change the contents to the following:

```swift
import Foundation

struct Contact: Identifiable, Equatable {
  let name: String
  let avatar: URL?
  let id: String
  var isOnline: Bool
}
```

You'll notice `Contact` conforms to `Identifiable`. To easily show an array of items in a list, those items need to each have a unique identifier. Remember, rows are reused, so the list needs a way of uniquely identifying each item. You conform to `Identifiable` by giving the struct an `id` property that is unique.

To create the row view, create a new SwiftUI `View` file and name it **ContactRow.swift**. 

This is what you'll end up with:

![Creating a list cell / row view in SwiftUI](https://dev-to-uploads.s3.amazonaws.com/i/5bdylaf1crggx98vfker.png)

This view has a few things going on. It shows the avatar with a little online badge, the contact's name, as well as their last message. 

As you just learned, it's better to start from smaller views and build up to larger views. That's why you'll start by creating a separate view for the avatar and the online badge.

First, you'll need to add a few placeholder avatars to your app. Download the [avatar placeholder image](https://github.com/cometchat-pro-tutorials/swift-ui-chat/raw/master/CometChat/CometChat/Assets.xcassets/avatar_placeholder0.imageset/profile0.png) and add it to **Assets.xcassets**. Name the image `avatar_placeholder0`.

With that in place, add the view struct to the file:

```swift
private struct AvatarView: View {
  // 1
  let url: URL?
  let isOnline: Bool
  
  var body: some View {
    // 2
    ZStack {
      // 3
      Image("avatar_placeholder0")
        .resizable()
        .frame(width: 37, height: 37)
      // 4
      Circle()
        .frame(width: 10, height: 10)
        .foregroundColor(isOnline ? .green : .gray)
        .padding([.leading, .top], 25)
    }
  }
}
```

With this code, you create a new SwiftUI view for the avatar. You'll use `AvatarView` inside the view for the list's row. Here's what's going on in the struct:

1. The view has two properties, one for the avatar URL and one that tells it whether that contact is online. For now, you'll use placeholder avatars. Later in the course, you'll learn how to fetch them from the internet.
2. Inside of `body`, you'll position the image and the online indicator in a `ZStack`. You've already learned about `VStack` and `HStack`. `ZStack` is similar to those, but in 3D! Instead of arranging items below or to the right of each other, it arranges items _on top_ of each other. This is useful for when you want two views to overlap. In this example, you want the online indicator to be on top of the image.
3. In the `ZStack`, you first create an image view with the placeholder and give it a fixed size of 37 by 37 points.
4. Next, you create a circular view of 10 by 10 points and color it green or gray depending on if the user is online or not. You also position the circle to be in the bottom-right corner of the image.

You'll need to modify the preview to see the view you just created. Scroll down to the `PreviewProvider` struct and change its content to the following: 

```swift
struct AvatarView_Previews: PreviewProvider {
  static var previews: some View {
    Group {
      AvatarView(url: nil, isOnline: true)
        .previewLayout(.fixed(width: 100, height: 100))
    }
  }
}
```

This shows the avatar view in all its glory, with a fixed size, inside a group.

![Creating a circular avatar view in SwiftUI](https://dev-to-uploads.s3.amazonaws.com/i/e9e7oe4pybzy1yqjynhj.png)

You'll use the group later to show your contact row as well. Speaking of which, let's get on creating that view.

Add another `View` struct to the same file, underneath `AvatarView`:

```swift
struct ContactRow: View {
  
  struct ContactItem: Identifiable {
    let contact: Contact
    let lastMessage: String
    let unread: Bool
    
    var id: String { contact.userID }
  }
  
  let item: ContactItem
  
}
```

This is the view that will be shown in the list. Instead of adding a bunch of properties to the struct, you create a nested struct to house all of the data necessary for displaying a contact.

Again, you use `Identifiable` to help the list reuse items. In this case, passing along the contact's ID will suffice.

For this to be a complete view, you'll also need to add `body` to the struct:

```swift
var body: some View {
  VStack {
    Spacer()
  }
  .background(item.unread ? 
    Color(red: 236 / 255, green: 240 / 255, blue: 254 / 255) : 
    nil)
  .frame(maxWidth: .infinity)
  .frame(height: 67)
}
```

To center the contents of the view, you'll place the image and the texts inside a vertical stack. For now, the stack contains only a spacer. You also give the stack a light blue background color if the last message is unread, set its width to stretch out as far as it can and make sure its height is a fixed 67 points.

Next, modify the preview so it shows the contact view underneath the avatar view:

```swift
Group {
  AvatarView(url: nil, isOnline: true)
    .previewLayout(.fixed(width: 100, height: 100))
    
  ContactRow(item: ContactRow.ContactItem(
    contact: Contact(name: "Some Name", avatar: nil, id: "0", isOnline: true),
    lastMessage: "Last message is a pretty big message",
    unread: true))
    .previewLayout(.fixed(width: 300, height: 67))
    
  ContactRow(item: ContactRow.ContactItem(
    contact: Contact(name: "Other Name", avatar: nil, id: "1", isOnline: false),
    lastMessage: "Last message is a pretty big message",
    unread: false))
    .previewLayout(.fixed(width: 300, height: 67))
}
```

To test all the variations of the contact view, you'll show one preview where the user is online and the message is unread, and one where the user if offline and the message was already read.

All of this code to display a completely empty view! Let's fix that. 

Add the avatar view inside a horizontal stack to `ContactRow`'s `body`:

```swift
var body: some View {
  VStack {
    Spacer()

    HStack {
      AvatarView(url: nil, isOnline: item.contact.isOnline)
        .padding(.leading, 20)

      Spacer()
    }
    
    Spacer()
  }
  ...
}
```

You place the avatar in a horizontal stack so that you can add the two text views to the right of the avatar. You also add another spacer to the bottom of the `VStack` so that everything is vertically centered in the row.

Next, add the two texts to the view between the avatar view and the spacer in the `HStack`:

```swift
VStack {
  ...
  HStack {
    AvatarView(url: nil, isOnline: item.contact.isOnline)
      .padding(.leading, 20)
    
    // New code:
    VStack(alignment: .leading) {
      Text(item.contact.name)
        .foregroundColor(.body)
        .fontWeight(item.unread ? .medium : .regular)
        .lineLimit(1)
        
      Text(item.lastMessage)
        .foregroundColor(.body)
        .font(.system(size: 12))
        .fontWeight(item.unread ? .medium : .regular)
        .lineLimit(1)
        .padding(.top, 2)
    }
    .padding(.leading, 10)
    .padding(.trailing, 20)
    // ---
    
    Spacer()
  }
  ...
}
```

Place the two texts inside a vertical stack since they're arranged one on top of the other. You'll also limit the number of lines of text to 1, to make sure the text doesn't wrap. Finally, you'll add some padding around the vertical stack so that there's a space between the texts and the image.

![Creating a list cell / row view in SwiftUI](https://dev-to-uploads.s3.amazonaws.com/i/wfqa8krot3y7mqmia8kb.png)

That's your contact item! It displays the name, the avatar, the last message as well as if the contact is online or not, all at a glance.

## Using SwiftUI Lists

Now, it's time to head over to **ContactsView.swift** to add a bunch of contacts inside a list.

You'll start by creating a state property that will hold your contacts. For now, fill it up with a couple of fake contacts:

```swift
@State private var items: [ContactRow.ContactItem] = [
  .init(
    contact: Contact(name: "Some Name", avatar: nil, id: "0", isOnline: true),
    lastMessage: "This is my last message that I sent you",
    unread: true),
  .init(
    contact: Contact(name: "Other Name", avatar: nil, id: "1", isOnline: false),
    lastMessage: "This is my last message that I sent you",
    unread: false),
  .init(
    contact: Contact(name: "Third Name", avatar: nil, id: "2", isOnline: true),
    lastMessage: "This is my last message that I sent you",
    unread: false)
]
```

You can use this array to populate a list. Replace `body` with the following:

```swift
var body: some View {
  List(items) { item in
    ContactRow(item: item)
  }
}
```

To use a `List` with an array, you can pass the array to the list, as well as a function that will create the view for each row, given the array element for that row. Since you already created your row view, you can just create that view with the contact.

> **Note:** Remember that to show a dynamic array of items in a `List`, those items need to conform to the `Identifiable` protocol as described above.

![Showing a list of custom views in SwiftUI](https://dev-to-uploads.s3.amazonaws.com/i/ur9ibgm9jvsirxupdesh.png)

In only three lines of code, you have a list of dynamic items from an array. Take _that_, UIKit!

## Removing separators from a SwiftUI List

However, there is a slight problem with this screen. Instead of a nice-looking list, we have a space and a separator between each item. This happens because SwiftUI uses a plain `UITableView` to show the list, which has built-in separators and insets.

To change this, you first need to tweak the way you created the list. I showed you a shortcut for creating a list from an array, but there's another way to do it using `ForEach`. Change `body` to the following:

```swift
var body: some View {
  List {
    ForEach(0..<items.count, id: \.self) { i in
      ContactRow(item: self.items[i])
    }
  }
}
```

Here, `List` receives a builder that builds an _array_ of views. You'll build this array using `ForEach`. `ForEach` is a special type of view that replaces itself with a list of views, one below the other. In the above code,

```swift
ForEach(0..<items.count, id: \.self) { i in
  ContactRow(item: self.items[i])
}
```

...is exactly the same as writing:

```swift
ContactRow(item: self.items[0])
ContactRow(item: self.items[1])
ContactRow(item: self.items[2])
```

Each of these three views will get treated as a separate row by the list. Again, because a `List` needs to identify items, you'll pass the current index (`self`) as the ID of that item.

> **Note:** In UIKit it was somewhat difficult to combine different types of cells. It was especially hard to combine static and dynamic cells. In SwiftUI, since `List` receives an array of rows, it's very easy to combine static and dynamic content. You can first return a hard-coded row, then a `ForEach` of a couple of different dynamic rows, then another static one, and so on. A `List` can hold any combination of static and dynamic rows of different types.

Now that you changed the way you build your list, you can apply a little hack to remove the separators. 

Since `List` is backed by a `UITableView`, any global changes you make to table views will also apply to SwiftUI lists. UIKit provides an API to make global changes to all instances of a view called `UIAppearance`. Each `UIView` subclass has an `appearance` method that returns something called an **appearance proxy**. Changing properties of the appearance proxy propagates those changes to all instances of the class, like changing a static property.

You can change the table view's appearance proxy to remove the separators, thus removing all separators from you `List`.

Add the following initializer to the view struct:

```swift
init() {
  UITableView.appearance().tableFooterView = UIView()
  UITableView.appearance().separatorStyle = .none
}
```

> **Note:** Appearance proxies are not limited to table views. Lots of other SwiftUI views are backed by UIKit views. You can also use this pattern to change the look of navigation bars, toggles, texts and other views.
> 
> Keep in mind though, this is a bit of a hack. For now, SwiftUI is backed by UIKit, but this is an implementation detail that you don't want to rely on. Nothing is stopping Apple from ditching `UITableView` and making their own thing in SwiftUI. I'm showing you this trick because, currently, there's no better way to remove separators.

When the struct loads, it will remove all separators from every table view in the app.

![How to remove separators from a SwiftUI List](https://dev-to-uploads.s3.amazonaws.com/i/dl7fiut33jfilu3m5pug.png)

The list now looks much cleaner, but I think it looks better _with_ separators. 

## Adding custom separators to a SwiftUI List

You might be pulling your hair out by now because you just removed them! Well, SwiftUI is flexible enough that, instead of using `UITableView`'s separators, it's better to make our own using plain SwiftUI views.

Go back the view for your item: **ContactRow.swift**. Inside `body`, use a rectangle view at the bottom of the `VStack` to add separators back in:

```swift
  var body: some View {
    VStack {
      Spacer()

      HStack {
        ...
      }
      
      Spacer()
      
      // New code:
      Rectangle()
        .frame(height: 1)
        .foregroundColor(Color(UIColor.separator))
    }
    ...
  }
```

The rectangle will stretch the width of the view by default, all you need to change is to set the height to 1 point and set the color to whichever separator color you'd like.

Head back to **ContactView.swift** to see your list:

![Adding a custom List separator in SwiftUI](https://dev-to-uploads.s3.amazonaws.com/i/h0tebda0o5k2s1cmlydi.png)

It's already looking great, but there's one small issue I can see. There's still extra space around each item.

## Removing space around SwiftUI List rows

SwiftUI adds space around each row in a `List` by default. This space is called the _inset_ of a list row. The inset is an instance of `EdgeInsets`, a struct that holds values for how much the view is shifted from the leading, trailing, top and bottom edges.

You can remove the inset by calling `listRowInsets` on the view inside the list, and giving it a value of `EdgeInsets()`, where all the values are zero.

Add the following to `body`:

```swift
var body: some View {
  List {
    ForEach(0..<items.count, id: \.self) { i in
      ContactRow(item: self.items[i])
        .listRowInsets(EdgeInsets())
    }
  }
}
```

Okay, _now_ it's looking perfect.

![Removing the default spacing around SwiftUI List rows](https://dev-to-uploads.s3.amazonaws.com/i/tkkajuqfpqpvjfbydkxk.png)

Except... I'd still like to add one final touch. In the original design, there was a shadow underneath the last item, giving the whole screen a sense of depth that is currently missing.

## Adding a shadow to a SwiftUI view

To add a shadow to the row you'll call, you guessed it: `shadow`. This method takes the color of the shadow, its radius, as well as an x and y value that determines how much the shadow is offset from the view.

SwiftUI adds shadows to _opaque_ parts of the view. That is parts that have some sort of color. Right now, the background of your rows is _transparent_. It appears white because the color underneath them is white. To show the shadow underneath a row, you need to first color the background in white, and _then_ add the shadow.

Add the following to `body`:

```swift
var body: some View {
  List {
    ForEach(0..<items.count) { i in
      ContactRow(item: self.items[i])
        .listRowInsets(EdgeInsets())
        .background(Color.white)
        .shadow(
          color: i == self.items.count - 1 ? 
            Color(UIColor.black.withAlphaComponent(0.08)) : 
            Color.clear,
          radius: 10, x: 0, y: 2)
    }
  }
}
```

You set the background color to white and apply a shadow to the view. You only want to add a shadow to the last row, though, so you make the shadow completely transparent if the row is not the last.

![Adding a shadow to a SwiftUI view](https://dev-to-uploads.s3.amazonaws.com/i/809ew79guu9qv6spkcyc.png)

Okay, _now_ I'm happy with the look of this screen. Let's run this in the simulator so see it in action.

![Creating a SwiftUI List view](https://dev-to-uploads.s3.amazonaws.com/i/52z0u05zud92lkwjx9mq.png)

Oh boy, it looks like you're not off the hook yet! There's one more issue to take care of. The original design shows a title in the navigation bar, which isn't there in our version. Thankfully, that's easy to add.

## Adding a navigation bar title in SwiftUI

To add a navigation bar title, you need to call `navigationBarTitle` on the _root view_ of your `body`. In your case, that's the list view:

```swift
var body: some View {
  List {
    ...
  }.navigationBarTitle("Contacts", displayMode: .inline)
}
```

By default, SwiftUI adds a large, bold navigation bar title with lots of space around it. You already saw this when you were building your login screen. In this case, you want a more subtle title that is part of the navigation bar. That's why you pass `.inline` as the display mode, integrating the title with the bar.

![Adding a regular navigation bar to a SwiftUI view](https://dev-to-uploads.s3.amazonaws.com/i/eetwlcs83ec25g8zxpd3.png)

_Now_ it looks perfect. No, for real this time. You can take a break and pat yourself on the back, I won't be bothering you with design changes anymore.

## Conclusion

You're slowly moving more and more towards a fully-fledged SwiftUI chat app! You built out a contacts screen that shows a list of users.

If you want to know more about SwiftUI lists, here are a few links:

 * In UIKit, table views can hold sections of items. The same is true for SwiftUI `List`s. To organize rows into sections, place a [`Section` view](https://developer.apple.com/documentation/swiftui/section) inside a list, and rows inside the section.
 * There's a couple of different list styles that you can use off the shelf. There's a default list, a plain list, a grouped one (used in the Settings app in iOS), a carousel and one that looks like the lists in an iOS sidebar. You can find all of them [here](https://developer.apple.com/documentation/swiftui/liststyle). To change the style of your list, call [`listStyle`](https://developer.apple.com/documentation/swiftui/list/3364399-liststyle) on the list.

While the contacts in your list might be hard-coded for now, everything is in place to load these users from the internet — in later parts of this SwiftUI course. So keep reading!