在 SwiftUI 中从 LazyHStack 获取第一个可见索引

Web*_*eak 1 swift swiftui

我有以下用于在 LazyHStack 中显示图像的代码:

ScrollView(.horizontal, showsIndicators: false) {
    LazyHStack(alignment: .top) {
        let photos: [PhotoObject] = []//some array of photo objects
        ForEach(photos, id: \.self) { item in
            KFImage.url(URL(string: item.url))
                    .resizable()
                    .frame(width: 200, height: 200)
                    .cornerRadius(13)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我想每次当堆栈中的第一个可见项目发生变化时执行一个函数(例如,当用户向左滚动太多时,最左边的图像不再可见,而右边的图像现在是第一个可见图像)。Android 的 Jetpack compose对此有非常简单的解决方案。我玩过自定义TrackableScrollView但这只返回偏移量,我不确定它是什么,因为它以 例如 开头1500,当您滚动到末尾时,值是 ~ -1200- 不知道应该如何根据偏移量计算索引一半是积极的,一半是消极的。

Yrb*_*Yrb 8

虽然 UIView 中有相同类型的内置函数,正如您所展示的,但这还没有内置到 SwiftUI 中,但您可以自己做。本质上,您所做的就是获取外部视图的坐标,在本例中是您的ScrollView,并查看图像视图的内部坐标是否至少部分位于外部视图内。这是通过两个GeometryReaders和一个.coordinateSpaces()指示符完成的,因此我们可以可靠地位于同一坐标空间中。如果图像存在于外部坐标空间内,则将其添加到@State作为集合的变量中。如果不是,则将其从集合中删除。这将为您提供当前部分或完全可见的视图的运行列表。

由于您没有提供最小的、可重现的示例,因此我采用了您的代码并用于Rectangles表示您的图像,但您应该能够简单地重新插入您的代码。下面是一个工作示例:

struct ImageIsInView: View {
    
    @State var visibleIndex: Set<Int> = [0,1]
       
    var body: some View {
        VStack {
            Text(visibleIndex.map( { $0.description }).sorted().joined(separator: ", "))
            // The outer GeometryReader has to go directly around your ScrollView
            GeometryReader { outerProxy in
                ScrollView(.horizontal, showsIndicators: false) {
                    LazyHStack(alignment: .top) {
                        ForEach(0..<10, id: \.self) { item in
                            GeometryReader { geometry in
                                Rectangle()
                                    .fill(Color.orange)
                                    .cornerRadius(13)
                                    .overlay(
                                        Text("Item: \(item)")
                                    )
                                    // every time the ScrollView moves, the inner geometry changes and is
                                    // picked up here:
                                    .onChange(of: geometry.frame(in: .named("scrollView"))) { imageRect in
                                        if isInView(innerRect: imageRect, isIn: outerProxy) {
                                            visibleIndex.insert(item)
                                        } else {
                                            visibleIndex.remove(item)
                                        }
                                    }
                            }
                            .frame(width: 200, height: 200)
                        }
                    }
                }
                .coordinateSpace(name: "scrollView")
            }
        }
    }
    
    private func isInView(innerRect:CGRect, isIn outerProxy:GeometryProxy) -> Bool {
        let innerOrigin = innerRect.origin.x
        let imageWidth = innerRect.width
        let scrollOrigin = outerProxy.frame(in: .global).origin.x
        let scrollWidth = outerProxy.size.width
        if innerOrigin + imageWidth < scrollOrigin + scrollWidth && innerOrigin + imageWidth > scrollOrigin ||
            innerOrigin + imageWidth > scrollOrigin && innerOrigin < scrollOrigin + scrollWidth {
            return true
        }
        return false
    }
}
Run Code Online (Sandbox Code Playgroud)