首先

你可以用ScrollViewReader做一些可以从iOS14使用的事情。
但是,我不能做我想做的事情,所以我想我还能做些什么。
再次,可重复使用我尝试过了。

执行

我将首先发布实现的图像。 (Swift Playgrounds 演示)

[Swift] SwiftUI の ScrollView で offset を検知する

您可以像这样根据滚动获取坐标。
让我们看看实现。

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

对 CGPoint 的更改
- $0.frame(in: .global).origin.y
+ $0.frame(in: .global).origin

现在你可以两者兼得。
(*示例中只有垂直滚动,所以X轴值没有变化)
[Swift] SwiftUI の ScrollView で offset を検知する

如您所见,如果保持原样,则该值会随着每次滚动而增加为负值,因此我们将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 获取的帧。通过给它一个专有名称,就可以与它自己的坐标系建立关系。

对 CGPoint 的更改
- $0.frame(in: .global).origin.y
+ $0.frame(in: .named("nameSpace")).origin

指定目标的coordinateSpaceView

。迅速
Text("target")
    .coordinateSpace(name: "nameSpace")

有时它只是不起作用,对吗? (有些部分的可用性尚未正确理解),所以你需要了解coordinateSpace

此外,到目前为止的实现不仅可以得到ScrollView,还可以得到List等。

在最后

如果你是这么轻的说唱歌手,维护成本低我认为这很容易处理。

它比较容易使用,所以请务必尝试使用它mm


原创声明:本文系作者授权爱码网发表,未经许可,不得转载;

原文地址:https://www.likecs.com/show-308628560.html

相关文章: