如何检查项目是否可见 - SwiftUI ScrollView

问题描述 投票:0回答:2

尝试以编程方式确定 SwiftUI 中的 ScrollView 中的项目何时显示在屏幕上。我知道 ScrollView 是一次性渲染的,而不是在项目出现时渲染(如列表中),但我只能使用 ScrollView,因为我有 .scrollTo 操作。

我还了解,在带有 UIScrollView 的 UIKit 中,可以在 UIScrollViewDelegate 中的项目框架和 ScrollView 框架之间使用 CGRectIntersectsRect,但如果可能的话,我更愿意在 SwiftUI 中找到解决方案。

示例代码如下所示:

ScrollView {
    ScrollViewReader { action in
        ZStack {
            VStack {
                ForEach(//array of chats) { chat in
                    //chat display bubble
                        .onAppear(perform: {chatsOnScreen.append(chat)})
                }.onReceive(interactionHandler.$activeChat, perform: { _ in
                    //scroll to active chat
                })
            }
        }
    }
}

理想情况下,当用户滚动时,它会检查屏幕上有哪些项目并缩放视图以适合屏幕上最大的项目。

ios swiftui scrollview
2个回答
7
投票

当您在

VStack
中使用
ScrollView
时,所有内容都会在构建时立即创建,因此
onAppear
不符合您的意图,而您应该使用
LazyVStack
,它只会在每个子视图出现在屏幕上之前创建它,所以
onAppear
将在您期望的时候被调用。

所以应该是这样的

ScrollViewReader { action in
   ScrollView {
        LazyVStack {                              // << this !!
            ForEach(//array of chats) { chat in
                //chat display bubble
                    .onAppear(perform: {chatsOnScreen.append(chat)})
            }.onReceive(interactionHandler.$activeChat, perform: { _ in
                //scroll to active chat
            })
        }
    }
}

0
投票

受到这个答案的启发:https://stackoverflow.com/a/75823183/22499987

如上所述,使用

onAppear
的问题是它只能保证在第一次加载每个子视图时被调用,而不是当您向后滚动时被调用。

下面的解决方案显示了水平文本元素的 LazyHStack 的示例,并使用 GeometryReader 检测我们所在的索引。我们将每个内部 Text 元素包装在它们自己的 GeometryReader 实例中,我们用它来查找当前的 MidPoint x 坐标。如果它在屏幕中点的阈值内,我们将 currentIndex 设置为此索引。

请注意,屏幕上可见的所有文本都会调用此 onChange 函数。例如,当我们从 1 -> 2 滑动时,onChange 将跟踪 screenMid 周围的 1 的 x。当 2 变得可见时,2 的 x 实际上是负值,因为它的中点位于 0 的左侧(屏幕的左边缘)。

struct SampleView: View {
    @State private var currIndex: Int = 1

    var body: some View {
        let screenWidth = UIScreen.main.bounds.width
        let numLoops = 10
        ScrollView(.horizontal, showsIndicators: false) {
            LazyHStack {
                ForEach(1 ..< numLoops + 1, id: \.self) { index in
                    GeometryReader { inner in
                        Text("\(index)")
                        .onChange(of: inner.frame(in: .global).midX) {
                            // x will be the midPoint of the current view
                            let x = inner.frame(in: .global).midX
                            let screenMid = screenWidth / 2
                            let threshold: CGFloat = screenWidth / 10
                            // check if the midpoint of the current view is within our range of the screen mid
                            if x > screenMid - threshold && x < screenMid + threshold {
                                print("\(index) is in the middle")
                                currIndex = index
                            }
                        }
                    }
                    .frame(width: screenWidth, height: 150)
                }
            }
            .scrollTargetLayout()
        }
        .scrollTargetBehavior(.viewAligned)
        .frame(height: 150)
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.