SwiftUI lists have a predefined appearance which depends on the platform and situation. A list in the NavigationView sidebar has a SidebarListStyle. Using one of the default stylings will give your app the same look as native Apple apps. But what if you want to add your own styling to the list?
In this blog post, I will show you how to customize the SwiftUI List style: hide separators, change separator colors, adjust the list background color, and the background color of individual cells. I will also give you examples of how to change section headers and footers.
By the end of this tutorial, you will be able to describe SwiftUI lists that don´t look unique and are super cool. If you are just getting started with List view, I suggest you check out my other blog post: SwiftUI List View: A Deep Dive into one of the most important components of SwiftUI.
⬇️ Download the project files here
Example Data Structure
In a previous post about SwiftUI List view, I used Food items, which I want to further extend here. I added a color variable that we will use to style our lists.
import SwiftUI
struct Food: Identifiable, Hashable {
var name: String
var icon: String
var isFavorite: Bool
var color: Color
let id = UUID()
static func goodExamples() -> [Food] {
return [Food(name: "Apple", icon: "🍎", isFavorite: true, color: .red),
Food(name: "Orange", icon: "🍊", isFavorite: false, color: .orange),
Food(name: "Banana", icon: "🍌", isFavorite: false, color: .yellow)
]
}
static func unhealthyExamples() -> [Food] {
[Food(name: "Pizza", icon: "🍕", isFavorite: false, color: .blue),
Food(name: "Burger", icon: "🍔", isFavorite: false, color: .green)]
}
}
Using a Custom Cell
We can write a custom cell to make it faster to write different implementations for List. The subview shows the food`s icon, name, and icon for the isFavorite property.
struct FoodRow: View {
let food: Food
var body: some View {
HStack {
Text(food.icon)
.font(.title)
Text(food.name)
Spacer()
Image(systemName: food.isFavorite ? "heart.fill" : "heart")
}
}
}
List Separators
When you use PlainListStyle, GroupedListStyle, or InsetListStyle, List shows separators between rows and sections. A List separator is not the same as a Divider. If you try to use a Divider view, it will be treated as a list row and additional spacing and styling are added. It is much better to customize the default List separators instead.
How do I hide line separators?
You can remove the List row separators with a new feature for iOS 15 and macOS 13 which is listRowSeparator. You can attach this view modifier to ForEach, a section, or directly to the individual list cell.
Each section also gets separators added after the section. You can hide section separators with the listSectionSeparator modifier:
struct ContentView: View {
@State private var unhealthFoods = Food.unhealthyExamples()
var body: some View {
List {
Section {
ForEach(unhealthFoods) { food in
FoodRow(food: food)
}
.listRowSeparator(.hidden)
} header: {
Text("Unhealthy Foods")
} footer: {
Text("You should try to avoid them and only eat them occasionally.")
}
.listSectionSeparator(.hidden)
}
}
}
How to adjust List row separator color?
You can change the color of the List separators with the listRowSeparatorTint modifier. If it is attached to ForEach all separators inside are changed. ListRowSeparatorTint has an argument for the edge, which has options for all, top and bottom. It is also possible to change the separator color for individual rows by directly applying listRowSeparatorTint to the row. In our example, I used the food`s color property to apply different separator colors (first section in the following example).
If you add listRowSeparatorTint to a section, also the section separator gets the same tint color. If you only want to change the section separator use listSectionSeparatorTint.
struct ContentView: View {
@State private var healthFoods = Food.goodExamples()
@State private var unhealthFoods = Food.unhealthyExamples()
var body: some View {
List {
Section {
ForEach(healthFoods) { food in
FoodRow(food: food)
.listRowSeparatorTint(food.color)
}
} header: {
Text("Healthy Foods")
} footer: {
Text("You should try to eat them regularly.")
}
Section {
ForEach(unhealthFoods) { food in
FoodRow(food: food)
}
} header: {
Text("Unhealthy Foods")
} footer: {
Text("You should try to avoid them and only eat them occasionally.")
}
.listRowSeparatorTint(.green, edges: .all)
Section {
ForEach(unhealthFoods) { food in
FoodRow(food: food)
}
.listRowSeparatorTint(.blue, edges: .top)
} header: {
Text("Unhealthy Foods")
} footer: {
Text("You should try to avoid them and only eat them occasionally.")
}
.listSectionSeparatorTint(.red)
}
.listStyle(.inset)
}
}
List Row Size and Insets
SwiftUI sets default values for the height of the rows and section headers. But also for the list insets, which depend on the ListStyling. GroupedInsetListStyling will have more inset than PlainListStyling. You can override these behaviours with newer features that are backward compatible with iOS 13 and macOS 10.15.
How do I change the List row height?
List uses the default row height, that is passed in the environment. The environment value is called defaultMinListRowHeight
. You can set your own value by overriding the environment value:
List {
...
}
.environment(\.defaultMinListRowHeight, 70)
Similarly, you can change the height of a section header with another environment property, which is defaultMinListHeaderHeight
:
List {
Section {
....
} header: {
Text("Good Foods")
}
}
.environment(\.defaultMinListRowHeight, 70)
How do I adjust List row insets?
We now have a listRowInsets modifier, where you can specify EdgeInsets. For rows you can change the leading and trailing insets. For sections, you can also set the top and bottom insets. If you attach listRowInsets to a section, the rows, section header, and footer are changed:
ForEach(unhealthFoods) { food in
FoodRow(food: food)
}
.listRowInsets(EdgeInsets.init(top: 0, leading: 50, bottom: 0, trailing: 50))
If you want to remove all edge insets you can set .listRowInsets(.none)
.
struct ContentView: View {
@State private var unhealthFoods = Food.unhealthyExamples()
var body: some View {
List {
Section {
ForEach(unhealthFoods) { food in
FoodRow(food: food)
}
} header: {
Text("Unhealthy Foods")
} footer: {
Text("You should try to avoid them and only eat them occasionally.")
}
Section {
ForEach(unhealthFoods) { food in
FoodRow(food: food)
}
} header: {
Text("Unhealthy Foods")
} footer: {
Text("You should try to avoid them and only eat them occasionally.")
}
.listRowInsets(EdgeInsets.init(top: 20, leading: 50,
bottom: 20, trailing: 50))
Section {
ForEach(unhealthFoods) { food in
FoodRow(food: food)
}
.listRowInsets(EdgeInsets.init(top: 0, leading: 40,
bottom: 0, trailing: 40))
} header: {
Text("Unhealthy Foods")
} footer: {
Text("You should try to avoid them and only eat them occasionally.")
}
}
}
}
List Section Headers and Footers
You can add sections for Lists with different initializers. Some of them are deprecated with iOS 16.2 and macOS 13.1 like init(header:content:). Use init(content:header)
instead, which is available for iOS 13.0+ and macOS 10.15+. This has the advantage that you can specify a view for the header and footer. For example, the following code creates a header with a larger font size that has a foreground color of pink.
Section {
ForEach(unhealthFoods) { food in
FoodRow(food: food)
}
} header: {
Text("Unhealthy Foods")
.font(.title)
.bold()
.foregroundColor(.pink)
.textCase(.lowercase)
.padding()
}
You can also use an HStack to add a button next to the section header text:
Section {
ForEach(unhealthFoods) { food in
FoodRow(food: food)
}
} header: {
HStack {
Text("Unhealthy Foods")
Spacer()
Button { } label: {
Image(systemName: "plus.circle.fill")
}
}
.foregroundColor(.blue)
} footer: {
Text("You should try to avoid them and only eat them occasionally.")
.font(.body)
}
For iOS 15.0+ you can also increase the header prominence, which makes the section header larger and bold. This works also with dynamic types.
Section {
ForEach(healthFoods) { food in
FoodRow(food: food)
}
} header: {
Text("Healthy Foods")
} footer: {
Text("You should try to eat them regularly.")
}
.headerProminence(.increased)
Changing the SwiftUI List Background Color
One of the most requested adjustments for the List has been to change the background of the List and the individual cells. With SwiftUI 4 the implementation of List changes. Before it was based on UITableView and now it uses UICollectionView. If you are used to some hacky solutions with UITableView.appearance(), you are in for an awesome improvement.
How to set SwiftUI List background to clear?
With iOS 16+ and macOS 13+, you can now directly remove the list background with a new modifier scrollContentBackground. This also works for ScrollView and TextEditor.
List {
...
}
.scrollContentBackground(.hidden)
How do I modify the background color of a List in SwiftUI?
You can use scrollContentBackground
to hide the default List background and then add your own:
struct ContentView: View {
@State private var healthFoods = Food.goodExamples()
@State private var unhealthFoods = Food.unhealthyExamples()
var body: some View {
List {
Section {
ForEach(healthFoods) { food in
FoodRow(food: food)
}
} header: {
Text("Healthy Foods")
} footer: {
Text("You should try to eat them regularly.")
}
Section {
ForEach(unhealthFoods) { food in
FoodRow(food: food)
}
} header: {
Text("Unhealthy Foods")
} footer: {
Text("You should try to avoid them and only eat them occasionally.")
}
.foregroundColor(.black)
}
.scrollContentBackground(.hidden)
.background(Color.mint.edgesIgnoringSafeArea(.all))
}
}
Adjust Cell Background inside a List
You can use the listRowBackground modifier to change the list row background. It takes a view as an argument, so you can set a color or a shape. You can add listRowBackground to Section, ForEach, or each individual row:
List {
Section {
ForEach(healthFoods) { food in
FoodRow(food: food)
.listRowBackground(food.color)
}
} header: {
Text("Healthy Foods")
} footer: {
Text("You should try to eat them regularly.")
}
Section {
ForEach(unhealthFoods) { food in
FoodRow(food: food)
}
.listRowBackground(
Capsule()
.fill(Color.gray)
.padding(2)
)
} header: {
Text("Unhealthy Foods")
} footer: {
Text("You should try to avoid them and only eat them occasionally.")
}
Section {
ForEach(unhealthFoods) { food in
FoodRow(food: food)
}
} header: {
Text("Unhealthy Foods")
} footer: {
Text("You should try to avoid them and only eat them occasionally.")
}
.listRowBackground(Color.blue)
}
An Extravagant Example for SwiftUI List Style
In order to see for myself, how much we can do to achieve a unique appearance for SwiftUI list. I went and applied pretty much all available tools to a simple-looking list. Can you believe how much we now can do with SwiftUI List? – Just amazing 🙂
struct ContentView: View {
@State private var healthyFoods = Food.goodExamples()
@State private var unhealthFoods = Food.unhealthyExamples()
var body: some View {
List {
Section {
ForEach(healthyFoods) { food in
FoodRow(food: food)
.listRowBackground(food.color)
}
} header: {
Text("Healthy Foods")
}
.listRowBackground(Color.blue)
.foregroundColor(.black)
Section {
ForEach(healthyFoods) { food in
FoodRow(food: food)
}
.listRowBackground(
Capsule()
.fill(Color(white: 1, opacity: 0.8))
.padding(.vertical, 2).padding(.horizontal, 20)
)
.listRowSeparator(.hidden)
.foregroundColor(.indigo)
} header: {
Text("Healthy Foods")
.font(.title)
.bold()
.foregroundColor(.white)
.shadow(radius: 5)
}
.listRowInsets(.init(top: 0, leading: 40, bottom: 0, trailing: 40))
Section {
ForEach(unhealthFoods) { food in
FoodRow(food: food)
}
.foregroundColor(.pink)
.listRowInsets(.init(top: 0, leading: 60, bottom: 0, trailing: 60))
.listRowSeparatorTint(.orange)
} header: {
Text("Unhealthy Foods")
.foregroundColor(.black)
} footer: {
Text("You should try to avoid them and only eat them occasionally.")
.foregroundColor(.white)
}
.listRowBackground(Color.black)
.listSectionSeparatorTint(.yellow)
.headerProminence(.increased)
}
.scrollContentBackground(.hidden)
.background(
Image("candies")
.resizable()
.scaledToFill()
.clipped()
.edgesIgnoringSafeArea(.all)
.blur(radius: 3)
.overlay(Color.indigo.opacity(0.2))
)
.environment(\.defaultMinListHeaderHeight, 45)
.environment(\.defaultMinListRowHeight, 50)
}
}
Conclusion
SwiftUI 4 brings many great improvements to the List view. You can now adjust the minimum height of a row, edge insets, section headers, and footers with ease. You also have more control over colors and backgrounds than ever before. With this tutorial, you should be able to create beautiful lists with SwiftUI. Happy coding!
Super good blog. it was very useful. i found answers to my questions on which i had been working on for hours. Thank a lot.
Happy that is helpful 😉
This really helps. you mentioned everything in one article that we needed to customize the table view.
Thank you for the post – is there a way to force a certain size (practically, height as a function of width) for a specific section row? Example: Displaying a view (an image for example, scaled in proportion, in one section row); .frame modifier doesn’t seem to do anything.