我没有找到开箱即用的SwiftUI解决方案.但我能够通过观察List
偏移量,然后计算正确的标题插入(firstRowInset - safeAreaTopInset
)来解决这个问题.
struct ContentView: View {
@State private var scrollOffset = 0.0
var body: some View {
NavigationStack {
ItemsList(items: [0, 1, 2, 3])
.safeAreaInset(edge: .top, alignment: .leading) {
Text("Safe Area Header")
.foregroundStyle(Color.blue)
.font(.title)
.padding()
.offset(y: max(scrollOffset, 0.0))
}
.navigationTitle("Navigation Title")
.onPreferenceChange(
ScrollOffsetPreferenceKey.self,
perform: { scrollOffset = $0 }
)
}
}
private struct ScrollOffsetPreferenceKey: PreferenceKey {
static var defaultValue: CGFloat = 0
static func reduce(
value: inout CGFloat,
nextValue: () -> CGFloat
) {
value += nextValue()
}
}
private struct ItemsList: View {
let items: [Int]
var body: some View {
GeometryReader { geometry in
List(items, id: \.self) { item in
Row(value: item)
.background {
if item == items.first {
ScrollOffsetPreferenceView(
safeAreaInsets: geometry.safeAreaInsets
)
}
}
}
}
}
}
private struct Row: View {
let value: Int
var body: some View {
Text("\(value)")
.frame(
maxWidth: .infinity,
maxHeight: .infinity,
alignment: .leading
)
.listRowInsets(EdgeInsets())
.padding()
}
}
private struct ScrollOffsetPreferenceView: View {
let safeAreaInsets: EdgeInsets
var body: some View {
GeometryReader { geometry in
Color.clear
.preference(
key: ScrollOffsetPreferenceKey.self,
value: offset(geometry: geometry)
)
}
}
private func offset(geometry: GeometryProxy) -> CGFloat {
let safeAreaTopInset = safeAreaInsets.top
let rowOffset = geometry.frame(in: .global).origin.y
return rowOffset - safeAreaTopInset
}
}
}