尝试以编程方式确定 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
})
}
}
}
}
理想情况下,当用户滚动时,它会检查屏幕上有哪些项目并缩放视图以适合屏幕上最大的项目。
当您在
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
})
}
}
}
受到这个答案的启发: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)
}
}