如何在 iOS 16 中使用 SwiftUI Plain List 移除节标题的顶部填充

19
当我们使用SwiftUI List组件并且需要一个分区标题时,每个分区的标题上方会出现顶部填充。
在iOS15下,我们使用UITableView.appearance().sectionHeaderTopPadding = 0来解决这个问题。请查看附件iOS15&Xcode14 build截图。
但是在iOS16中,这个设置似乎不起作用。请查看附件iOS16&Xcode14 build截图,在滚动时:iOS16&Xcode14 build scroll
因此,我的问题是如何在iOS16中去除分区标题的顶部填充,并且当滚动List时,它的分区标题将被固定,就像附件iOS15&Xcode14 build scroll所示。
自从iOS16以来,SwiftUI List组件似乎使用UICollectionView而不是UITableView,因此我尝试通过全局设置UICollectionView的分区顶部填充来解决这个问题。不幸的是,我没有找到设置UICollectionView分区顶部填充的方法。
我的代码如下。
//
//  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")
}


非常感谢。

许多感谢。


嘿,@Eric。你选择了哪个解决方案来解决这个问题? - Alexander Khitev
5个回答

4

您可以使用以下方法从UICollectionView中的节标题中移除顶部填充:

var layoutConfig = UICollectionLayoutListConfiguration(appearance: .plain)
layoutConfig.headerMode = .supplementary
layoutConfig.headerTopPadding = 0
let listLayout = UICollectionViewCompositionalLayout.list(using: layoutConfig)
UICollectionView.appearance().collectionViewLayout = listLayout

编辑: 不幸的是,我刚看到这会覆盖某些SwiftUI修饰符,例如.listRowSeparator().onDelete()


2
感谢您的解决方案。这个解决方案解决了问题的一部分。正如您所提到的,它覆盖了SwiftUI List的某些设置。当我们同时使用没有节头的List时,它会导致第一行重复显示。此外,在屏幕跳转后还会出现一些布局崩溃等现象。 - Eric
警告:使用此功能将破坏列表的布局更新并导致崩溃,因此除非您的列表保证是静态的,否则我不建议使用。 - Jon Reed

2

我有和你一样的困境。起初,我想把我的标题作为另一个单元格使用,虽然我用.environment(\.defaultMinListRowHeight, 0)消除了所有间距,但是当然我没有固定标题来滚动。最终,我用一个ScrollView里面的LazyVStack替换了我的List,并调整了我的内容,使其看起来与以前的样子相似(带有间距、列表分隔线等)。

ScrollView {
  LazyVStack(alignment: .leading, pinnedViews: [.sectionHeaders]) {
    Content()
  }
}

感谢您的评论。这确实解决了标题顶部填充问题,但当我们选择使用ScrollView-LazyVStack来替换List时,我们发现由于行的类型不统一,我们需要调整每种类型的行的布局,这需要打破当前项目行组件的代码结构,并且修改成本有点高,因此我们继续寻找低成本的解决方案。 - Eric

1
虽然覆盖全局的UICollectionView.appearance()可以解决这个问题,但我建议使用SwiftUI-Introspect来专门修改SwiftUI组件。
因为某些iOS内部的集合视图会受到这个覆盖的影响。例如,在iOS 16上长按textView时,应该显示编辑菜单,但会引发异常。我不得不移除覆盖以解决崩溃问题。
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)
}

0

您可以使用GeometryReader查找标题的高度,然后使用负填充将列表向上移动。像这样:

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
        })
}}

-3

尝试在部分标题上使用.offset(y:)视图修饰符,像这样:

SectionHeader(...)
   .offset(y: -20) // or whatever numeric value works for you

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接