TableView的头随着列表的滑动而拉伸,常规做法是给List做一个背景。再通过监听滚动视图的偏移量,从而通过拉伸背景从而实现拉伸的效果、
一、那么在SwiftUI
中该如何监听滚动视图的偏移量?
这里需要了解三个概念:
-
GeometryReader
它允许我们根据父视图的大小和位置来动态调整子视图的布局和位置 -
CoordinateSpace
坐标系,它允许我们在不同坐标系中转化坐标; -
preference
偏好,它允许我们跨视图传递信息;
-
GeometryReader
的特性常常会使我们不能有效的获取到我们想要的坐标,它总是读取到最大的可持有空间。解决这个问题我们需要了解另一个概念background
背景 -
background
背景的最大空间就是当前的视图的空间,因而GeometryReader
的最大空间就是当前视图的空间。
如此的话,我们可以通过读取背景的偏移量获取到滚动视图的偏移量,而又不影响原有的视图布局;
public struct ListScaleViewModifier: ViewModifier {
@State private var headFirstHeight: CGFloat = 0
@Binding var scaleHeight: CGFloat
public init(scaleHeight: Binding<CGFloat>) {
self._scaleHeight = scaleHeight
}
public func body(content: Content) -> some View {
content.background(GeometryReader { geometry in
Color.clear
.preference(key: ScaleKey.self, value: ScaleKey.Offset(minY: geometry.frame(in: .global).minY, height: geometry.frame(in: .local).height))
.onPreferenceChange(ScaleKey.self, perform: { value in
if headFirstHeight == 0 {
headFirstHeight = geometry.frame(in: .global).minY
}
scaleHeight = max(0, value.minY - headFirstHeight + value.height)
})
})
}
}
利用偏好获取到滚动偏移量,从而计算出列表头放大或缩小后的高度
fileprivate extension ListScaleViewModifier {
struct ScaleKey: PreferenceKey {
struct Offset: Equatable {
let minY: CGFloat
let height: CGFloat
static let zero = Offset(minY: 0, height: 0)
}
static var defaultValue: Offset = .zero
static func reduce(value: inout Offset, nextValue: () -> Offset) {
value = nextValue()
}
}
}
二、动态改变背景高度
动态改变背景高度,实现头随着滚动视图的滚动而放大或者缩小。
struct ListScaleView: View {
@State private var headheight: CGFloat = 0
var body: some View {
List {
ListHeader()
.modifier(ListScaleViewModifier(scaleHeight: $headheight))
.listRowBackground(Color.clear)
// 列表内容
ForEach(0..<10, id: \.self) { index in
Text("Item \(index)")
}
.listRowBackground(Color.clear)
}
.listStyle(.plain)
.background(
VStack(spacing: 0) {
Color.red.frame(height: headheight)
Spacer()
}
)
}
}