When building apps with SwiftUI, you might need to add a SegmentedControl on top of a List that scrolls along with the list.
Usually, you’d think of placing the List
inside a VStack
and adding a Picker
with the .segmented
style above it, like this:
VStack(spacing: .zero) {
Picker("Conact type", selection: $genre) {
ForEach(Genre.allCases, id: \.self) { genre in
Text(genre.string)
.textCase(nil)
}
}
.pickerStyle(.segmented)
.padding(.horizontal)
.background(Color(uiColor: .systemGroupedBackground))
List {
Section {
ForEach(songs, id: \.id) { song in
VStack(alignment: .leading) {
Text(song.title)
.foregroundStyle(.primary)
.bold()
Text(song.artist)
.foregroundStyle(.secondary)
}
}
}
}
}
However, this approach doesn’t give the desired result:
Instead of adding the Picker
in a VStack
, you can include it in the List's section header, like this:
List {
Section {
ForEach(songs, id: \.id) { song in
VStack(alignment: .leading) {
Text(song.title)
.foregroundStyle(.primary)
.bold()
Text(song.artist)
.foregroundStyle(.secondary)
}
}
} header: { Picker("Conact type", selection: $genre) { ForEach(Genre.allCases, id: \.self) { genre in Text(genre.string) .textCase(nil) } } .pickerStyle(.segmented) }}
This gives a much better result:
You might notice unwanted spacing around the SegmentedControl. To fix this, apply the .listRowInsets(EdgeInsets())
modifier to the Picker:
List {
Section {
ForEach(songs, id: \.id) { song in
VStack(alignment: .leading) {
Text(song.title)
.foregroundStyle(.primary)
.bold()
Text(song.artist)
.foregroundStyle(.secondary)
}
}
} header: {
Picker("Conact type", selection: $genre) {
ForEach(Genre.allCases, id: \.self) { genre in
Text(genre.string)
.textCase(nil)
}
}
.pickerStyle(.segmented)
.listRowInsets(EdgeInsets()) .padding(.vertical)
}
}
Here’s the final output: a SegmentedControl that scrolls seamlessly with the list.
Thanks for reading! If you have any questions or suggestions, feel free to send me DM on Twitter.