Overlapping text for pinned section headers
By default, the pinned section headers of a plain List appear overlapping the list rows on iOS 26:
Update: 🎉 In Beta 8, if you use .scrollEdgeEffectStyle(.hard, for: .top)
, pinned headers now appear attached to the top bar (same as .safeAreaBar
):
To make section headers unpinned, a LazyVStack can be used as a workaround.
Discussion: https://developer.apple.com/forums/thread/795159
List Section Header playground
swift
// » SwiftUI Garden
// » https://swiftui-garden.com/Misc/iOS-26/Overlapping-text-for-pinned-section-headers
import SwiftUI
struct CityCountry {
let name: String
let continent: String
static let data = [
// Europe
CityCountry(name: "London, United Kingdom", continent: "Europe"),
CityCountry(name: "Paris, France", continent: "Europe"),
CityCountry(name: "Berlin, Germany", continent: "Europe"),
CityCountry(name: "Rome, Italy", continent: "Europe"),
CityCountry(name: "Madrid, Spain", continent: "Europe"),
CityCountry(name: "Amsterdam, Netherlands", continent: "Europe"),
// Asia
CityCountry(name: "Tokyo, Japan", continent: "Asia"),
CityCountry(name: "Beijing, China", continent: "Asia"),
CityCountry(name: "Seoul, South Korea", continent: "Asia"),
CityCountry(name: "Bangkok, Thailand", continent: "Asia"),
CityCountry(name: "Singapore, Singapore", continent: "Asia"),
CityCountry(name: "Mumbai, India", continent: "Asia"),
// North America
CityCountry(name: "New York, USA", continent: "North America"),
CityCountry(name: "Los Angeles, USA", continent: "North America"),
CityCountry(name: "Toronto, Canada", continent: "North America"),
CityCountry(name: "Mexico City, Mexico", continent: "North America"),
CityCountry(name: "Vancouver, Canada", continent: "North America"),
// South America
CityCountry(name: "São Paulo, Brazil", continent: "South America"),
CityCountry(name: "Buenos Aires, Argentina", continent: "South America"),
CityCountry(name: "Lima, Peru", continent: "South America"),
// Africa
CityCountry(name: "Cairo, Egypt", continent: "Africa"),
CityCountry(name: "Cape Town, South Africa", continent: "Africa"),
]
}
struct ListPinnedSectionHeadersExample: View {
private let groupedCities = Dictionary(grouping: CityCountry.data, by: { $0.continent })
.mapValues { $0.sorted { $0.name < $1.name } }
var body: some View {
NavigationStack {
List {
let continents = groupedCities.keys.sorted()
ForEach(continents, id: \.self) { continent in
Section(header: Text(continent)) {
ForEach(groupedCities[continent] ?? [], id: \.name) { city in
Text(city.name)
}
}
}
}
.listStyle(.plain)
.scrollEdgeEffectStyle(.hard, for: .top)
.navigationTitle("Cities & Countries")
.navigationBarTitleDisplayMode(.inline)
}
}
}
#Preview {
ListPinnedSectionHeadersExample()
}
Original issue
FB19434244 Mitigating overlapping text for sticky section headers for a plain List with .scrollEdgeEffectStyle
For a List with .listStyle(.plain),
- for .scrollEdgeEffectStyle(.soft, for: .top), the headers should be un-pinned or there should be a modifier to make them un-pinned
- for .scrollEdgeEffectStyle(.hard, for: .top), the pinned header view should attach to the top bar and get a blurry material background, similar to a safeAreaBar()