首先
你可以用ScrollViewReader
做一些可以从iOS14使用的事情。
但是,我不能做我想做的事情,所以我想我还能做些什么。
再次,可重复使用我尝试过了。
执行
我将首先发布实现的图像。 (Swift Playgrounds 演示)
您可以像这样根据滚动获取坐标。
让我们看看实现。
1. 在 GeometryReader 中获取偏移量
这种方法本身在很多地方都有描述,所以没有什么特别难的或新的。
创建PreferenceKey
并使用preference(key:,value:)
定位它。它是一种以onPreferenceChange(,perform:)
获取值的形式。
[示例代码]
struct OffsetPreferenceKey: PreferenceKey {
static var defaultValue = CGFloat.zero
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value += nextValue()
}
}
ScrollView() {
GeometryReader { geometry in
LazyVStack {
ForEach(0..<100, id: .self) { y in
Text("(y)")
}
}
.preference(
key: OffsetPreferenceKey.self,
value: $0.frame(in: .global).origin.y
)
}
}
.onPreferenceChange(OffsetPreferenceKey.self) { offset in
print(offset)
}
2.设置为背景
“1. 使用 GeometryReader 获取偏移量”很好,但是跨层次的实现即使是泛化的也不容易使用是。
我想像modifier
一样使用它,所以我将它设置为background()
,这样我就可以将它分开。
[示例代码]
ScrollView() {
LazyVStack {
ForEach(0..<100, id: .self) { y in
Text("(y)")
}
}
.background(GeometryReader {
Color.clear.preference(
key: OffsetPreferenceKey.self,
value: $0.frame(in: .global).origin.y
)
})
}
.onPreferenceChange(OffsetPreferenceKey.self) { offset in
print(offset)
}
在不影响屏幕的情况下使用Color.clear
获取值。
(EmptyView
不起作用)
3.同时监控x轴和y轴
目前,需要分别监控 x 轴和 y 轴,同时实现这两个轴会使代码变得更长、更冗长。因此,您可以同时监控两者。
首先,更改PreferenceKey
的设置。
struct OffsetPreferenceKey: PreferenceKey {
static var defaultValue = CGPoint.zero // CGPoint に変更
static func reduce(value: inout Value, nextValue: () -> Value) {
value.x += nextValue().x
value.y += nextValue().y
}
}
现在我们有了坐标。接下来,将接收方也修改为CGPoint
。
- $0.frame(in: .global).origin.y
+ $0.frame(in: .global).origin
现在你可以两者兼得。
(*示例中只有垂直滚动,所以X轴值没有变化)
如您所见,如果保持原样,则该值会随着每次滚动而增加为负值,因此我们将PreferenceKey
稍作修改。
struct OffsetPreferenceKey: PreferenceKey {
static var defaultValue = CGPoint.zero // CGPoint に変更
static func reduce(value: inout Value, nextValue: () -> Value) {
value.x += nextValue().x
value.y += nextValue().y
value.x = -value.x // 追加分
value.y = -value.y // 追加分
}
}
通过反转返回值,我们得到的值为正值将会
(如果有更好的方法,请告诉我......哈哈)
4.隐藏在自己的ScrollView中
如果在View
直接在ScrollView
下没有使用,参考值会改变。所以要小心。
因此,您可以通过将自己的View
包装在ScrollView
中来在一定程度上绑定实现。
struct ScrollWrapperView<Content: View>: View {
let axes: Axis.Set
let showsIndicators: Bool
@ViewBuilder var content: Content
let perform: (CGPoint) -> Void
init(
axes: Axis.Set = .vertical,
showsIndicators: Bool = true,
@ViewBuilder content: () -> Content,
perform: @escaping (CGPoint) -> Void
) {
self.axes = axes
self.showsIndicators = showsIndicators
self.content = content()
self.perform = perform
}
var body: some View {
ScrollView(axes, showsIndicators: showsIndicators, content: {
content
.background(GeometryReader {
Color.clear.preference(
key: OffsetPreferenceKey.self,
value: $0.frame(in: .global).origin
)
})
.onPreferenceChange(
OffsetPreferenceKey.self,
perform: perform
)
})
}
}
然后你可以像这样使用它:
ScrollWrapperView(content: {
LazyVStack {
ForEach(0..<100, id: .self) { y in
Text("(y)")
}
}
}, perform: {
print($0)
})
5. 分离成扩展(奖励)
View
extension
易于使用,因此您可以在任何地方使用它。
但是,如前所述,如果不直接在ScrollView
下使用View
,参考值会改变。因此,用户方面需要谨慎。
extension View {
func onChangeParentScrollViewOffset(perform: @escaping (CGPoint) -> Void) -> some View {
self
.background(GeometryReader {
Color.clear.preference(
key: OffsetPreferenceKey.self,
value: $0.frame(in: .global).origin
)
})
.onPreferenceChange(
OffsetPreferenceKey.self,
perform: perform
)
}
}
然后你可以像这样使用它:
ScrollView {
LazyVStack {
ForEach(0..<100, id: .self) { y in
Text("(y)")
}
}
.onChangeParentScrollViewOffset {
print($0)
}
}
就个人而言,我建议使用类似“4. 将其隐藏在您自己的 ScrollView 中”之类的内容,因为您可以在某种程度上实现它。
其他
.global
指定可以从GeometryReader
获取的帧。通过给它一个专有名称,就可以与它自己的坐标系建立关系。
- $0.frame(in: .global).origin.y
+ $0.frame(in: .named("nameSpace")).origin
指定目标的coordinateSpace
到View
。
Text("target")
.coordinateSpace(name: "nameSpace")
有时它只是不起作用,对吗? (有些部分的可用性尚未正确理解),所以你需要了解coordinateSpace
。
此外,到目前为止的实现不仅可以得到ScrollView
,还可以得到List
等。
在最后
如果你是这么轻的说唱歌手,维护成本低我认为这很容易处理。
它比较容易使用,所以请务必尝试使用它mm
原创声明:本文系作者授权爱码网发表,未经许可,不得转载;
原文地址:https://www.likecs.com/show-308628560.html