Reputation: 241
When we use SwiftUI List component and need a section header, top padding will appear on the header of each section.
Under iOS15, we use UITableView.appearance().sectionHeaderTopPadding = 0
to solve this problem.
Check out attachmentiOS15&Xcode14 build screenshot.
But in iOS16, this setting doesn't seem to work. Check out attachmentiOS16&Xcode14 build screenshot, when scrolling: iOS16&Xcode14 build scroll.
So, my problem is how to remove section header top padding in iOS16, and when scrolling List, its section header will be pinned, just like attachment iOS15&Xcode14 build scroll.
Since iOS16, SwiftUI List component seems to use UICollectionView instead of UITableView, so I tried to solve this problem by setting the section top padding of UICollectionView globally. Unfortunately, I didn't find a way to set the section top padding of UICollectionView.
My code is below👇.
//
// ContentView.swift
// ListIniOS16
//
// Created by Eric on 2022/07/23.
//
import SwiftUI
struct ContentView: View {
@State private var collections: [ListData] = ListData.collection
init() {
let appBarTheme = UINavigationBarAppearance()
appBarTheme.configureWithOpaqueBackground()
appBarTheme.backgroundColor = UIColor(.gray)
UINavigationBar.appearance().standardAppearance = appBarTheme
UINavigationBar.appearance().scrollEdgeAppearance = appBarTheme
if #available(iOS 15.0, *) {
// can fix sectionHeaderTopPadding issue in iOS15,
// but in iOS16 it doesn't work.
UITableView.appearance().sectionHeaderTopPadding = 0
}
}
var body: some View {
NavigationView {
VStack {
List {
ForEach(collections) { item in
Section(header: SectionHeader(title: item.group)) {
ForEach(item.rows) { row in
Text(row.name)
}
}
.listRowBackground(Color.gray.opacity(0.3))
}
}
.listStyle(.plain)
}
.navigationTitle("ListSectionHeader")
.navigationBarTitleDisplayMode(.inline)
}
}
struct SectionHeader: View {
let title: String
var body: some View {
ZStack(alignment: .leading) {
Color(.lightGray)
.frame(maxWidth: .infinity)
Text(title)
.font(.system(size: 16, weight: .bold))
.foregroundColor(.primary)
.padding(EdgeInsets(top: 10, leading: 18, bottom: 10, trailing: 18))
}
.listRowInsets(EdgeInsets())
}
}
}
struct ListData: Codable, Identifiable {
var id = UUID()
let group: String
let rows: [Row]
}
extension ListData {
static let collection: [ListData] = [.a, .b, .c]
static let a = ListData(group: "A", rows: [.row1, .row2, .row3])
static let b = ListData(group: "B", rows: [.row1, .row2, .row3, .row1, .row2, .row3])
static let c = ListData(group: "C", rows: [.row1, .row2, .row3, .row1, .row2, .row3, .row1, .row2, .row3])
}
struct Row: Codable, Equatable, Identifiable {
var id = UUID()
let name: String
}
extension Row {
static let row1 = Row(name: "Row1")
static let row2 = Row(name: "Row2")
static let row3 = Row(name: "Row3")
}
Many thanks.
Upvotes: 24
Views: 7935
Reputation: 987
Although override the global UICollectionView.appearance()
will fix this issue. But I recommend use the SwiftUI-Introspect to modify the SwiftUI component specifically.
Because some iOS internal collection view will be changed by this override. For example, on iOS 16 long press the textView the edit menu should be display but raise an exception. I have to remove the override to fix the crash problem.
List{
…
}
.listStyle(.plain)
.introspect(.list, on: .iOS(.v16, .v17)) { collectionView in
var configuration = UICollectionLayoutListConfiguration(appearance: .plain)
configuration.headerMode = .supplementary
configuration.headerTopPadding = .zero
let layout = UICollectionViewCompositionalLayout.list(using: configuration)
collectionView.setCollectionViewLayout(layout, animated: false)
}
Upvotes: 3
Reputation: 655
You can find out the height of the header using GeometryReader than using negative padding move the list up. Something like this:
struct RootView: View {
@State var headerHeight = 0.0
var body: some View {
List {
Section {
Text("11111")
Text("22222")
Text("22222")
Text("22222")
Text("22222")
Text("22222")
Text("22222")
Text("22222")
} header: {
header
}
.listRowInsets(EdgeInsets())
}
.listStyle(GroupedListStyle())
.padding(.top, -headerHeight)
}
private var header: some View {
Color
.clear
.background(GeometryReader { geometryReader -> Color in
DispatchQueue.main.async {
print("\(geometryReader.frame(in: .local).height)")
if headerHeight != geometryReader.frame(in: .local).height {
headerHeight = geometryReader.frame(in: .local).height
}
}
return .clear
})
}}
Upvotes: 3
Reputation: 31
I had the same predicament that you had. At first I thought about using my headers as another cells, and while I did eliminate all spacing with .environment(\.defaultMinListRowHeight, 0)
, of course I didn't have pinned headers for scrolling. Ultimately, I replaced my List with a LazyVStack inside a ScrollView and adjusted my content to resemble how it looked before (with spacing, list separators, etc.).
ScrollView {
LazyVStack(alignment: .leading, pinnedViews: [.sectionHeaders]) {
Content()
}
}
Upvotes: 2
Reputation: 69
You can remove the top padding from section headers in UICollectionViews like this:
var layoutConfig = UICollectionLayoutListConfiguration(appearance: .plain)
layoutConfig.headerMode = .supplementary
layoutConfig.headerTopPadding = 0
let listLayout = UICollectionViewCompositionalLayout.list(using: layoutConfig)
UICollectionView.appearance().collectionViewLayout = listLayout
Edit:
Unfortunately I just saw that this overrides certain SwiftUI modifiers like .listRowSeparator()
or .onDelete()
.
Upvotes: 6