ios 尝试检测哪个GridItem位于视图SwiftUI的顶部

ulmd4ohb  于 9个月前  发布在  iOS
关注(0)|答案(2)|浏览(145)

我试图确定哪个GridItem将有效地位于位置x:0,y:0。为了实现这一点,我简单地尝试使用preferenceKeyGeometryReader。我将.overlay添加到我的GridItems中,并在gridItemIndex 0处的GridItem上添加GeometryReader围绕Color.clear。我的预期逻辑是跟踪GridItem的Y位置。然后通过将Y偏移除以每个GridItem的高度,我将获得当前位于顶部的项目。
我已经做到了这一点。一旦索引“gridItemIndex”位置0处的GridItem超过某个偏移量,它就不再被读取,y位置停留在0.0。我的假设是由于视图被重用?
目前我没有得到40以上的阅读,但我需要得到,直到底部的LazyVGrid出现。
这是我的代码

struct DetectScrollPosition: View {
    
    let gridRowLayout = Array(repeating: GridItem(spacing: 0), count: 7)
    
    @State private var scrollPosition: Int = 0
    
    var body: some View {
        NavigationView {
            ScrollView (.vertical){
                LazyVGrid(columns: gridRowLayout, spacing: 0){
                    ForEach(0..<1092, id: \.self) { gridItemIndex in
                        Text("\(abs(gridItemIndex / 7))")
                            .overlay {
                                if gridItemIndex == 0 {
                                    GeometryReader { geometryProxy in
                                        Color.clear
                                            .updateViewsYPosition(geometryProxy)
                                        
                                    }
                                }
                            }
                    }
                }
                
                .onPreferenceChange(ScrollOffsetPreferenceKey.self) { value in
                    self.scrollPosition = abs(Int(value))
                }
                
            }
            
            .coordinateSpace(.named("scroll"))
            .navigationTitle("The Top Row is: \(scrollPosition)")
            .navigationBarTitleDisplayMode(.inline)
        }
        
    }
}

struct ScrollOffsetPreferenceKey: PreferenceKey {
    static var defaultValue: CGFloat = 0
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
    }
}

extension View {
    func updateViewsYPosition(_ geometryProxy: GeometryProxy) -> some View {
        let offset = geometryProxy.frame(in: .named("scroll")).origin.y / geometryProxy.frame(in: .named("scroll")).height
        return self.preference(key: ScrollOffsetPreferenceKey.self, value: offset)
    }
    
    
}

字符串
抱歉,GIF图片

hmmo2u0o

hmmo2u0o1#

实际上,视图正在被重用。您应该添加一个视图,为网格的每一行设置首选项。

.overlay {
    if gridItemIndex % 7 == 0 { // adds this for the first view in every row
        GeometryReader { geometryProxy in
            Color.clear
                .updateViewsYPosition(geometryProxy, gridItemIndex)
            
        }
    }
}

字符串
现在我们需要在preference键中实现reduce方法,因为会有多个兄弟视图都设置了自己的首选项。其思想是,在减少所有内容之后,最终结果将指示左上角的视图。
因此,我们需要同时存储视图的帧(用于缩小)和视图的索引(以便我们可以更新scrollPosition)。我们将使用如下类型的首选项键:

struct GridItemPosition: Equatable {
    let index: Int
    let frame: CGRect
}


这就是为什么我们还要在gridItemIndex中传递到overlay中的updateViewsYPosition。“
实际的首选项键将按如下方式实现:

struct ScrollOffsetPreferenceKey: PreferenceKey {
    static var defaultValue = GridItemPosition(index: -1, frame: .null)
    static func reduce(value: inout GridItemPosition, nextValue: () -> GridItemPosition) {
        let next = nextValue()
        if abs(value.frame.minY) > abs(next.frame.minY) {
            value = next
        }
    }
}


实现reduce后,首选项始终是y位置最接近0的视图。您可以根据需要更改此条件。
updateViewsYPosition中,您应该会取得scrollView坐标空间中的框架。

func updateViewsYPosition(_ geometryProxy: GeometryProxy, _ i: Int) -> some View {
    let frame = geometryProxy.frame(in: .scrollView)
    return self.preference(
        key: ScrollOffsetPreferenceKey.self,
        value: GridItemPosition(index: i, frame: frame)
    )
}


完整代码:

struct ContentView: View {
    
    let gridRowLayout = Array(repeating: GridItem(spacing: 0), count: 7)
    
    @State private var scrollPosition: Int = 0
    
    var body: some View {
        NavigationStack {
            ScrollView (.vertical){
                LazyVGrid(columns: gridRowLayout, spacing: 0){
                    ForEach(0..<1092, id: \.self) { gridItemIndex in
                        Text("\(abs(gridItemIndex))")
                            .overlay {
                                if gridItemIndex % 7 == 0 {
                                    GeometryReader { geometryProxy in
                                        Color.clear
                                            .updateViewsYPosition(geometryProxy, gridItemIndex)
                                        
                                    }
                                }
                            }
                    }
                }
                .scrollTargetLayout()
                
                .onPreferenceChange(ScrollOffsetPreferenceKey.self) { value in
                    self.scrollPosition = value.index
                }
            }
            .navigationTitle("The Top Row is: \(scrollPosition)")
            .navigationBarTitleDisplayMode(.inline)
        }
        
    }
}

struct ScrollOffsetPreferenceKey: PreferenceKey {
    static var defaultValue = GridItemPosition(index: -1, frame: .null)
    static func reduce(value: inout GridItemPosition, nextValue: () -> GridItemPosition) {
        let next = nextValue()
        if abs(value.frame.minY) > abs(next.frame.minY) {
            value = next
        }
    }
}

extension View {
    func updateViewsYPosition(_ geometryProxy: GeometryProxy, _ i: Int) -> some View {
        let frame = geometryProxy.frame(in: .scrollView)
        return self.preference(
            key: ScrollOffsetPreferenceKey.self,
            value: GridItemPosition(index: i, frame: frame)
        )
    }
}

struct GridItemPosition: Equatable {
    let index: Int
    let frame: CGRect
}

kse8i1jr

kse8i1jr2#

您只是检测第一个网格项的位置,所以我预计一旦它被滚动到视线之外一定距离,LazyVGrid就会丢弃它。
我建议在每一行的第一个单元格后面放置一个“探测器”。此外,它可以使用.onChange来完成,而不是使用专用的首选项键。像这样:

ScrollView (.vertical){
    LazyVGrid(columns: gridRowLayout, spacing: 0){
        ForEach(0..<1092, id: \.self) { gridItemIndex in
            Text("\(abs(gridItemIndex / 7))")
                .background {
                    if gridItemIndex % gridRowLayout.count == 0 {
                        GeometryReader { geometryProxy in
                            let height = geometryProxy.size.height
                            let minY = geometryProxy.frame(in: .named("scroll")).minY
                            let isTopRow = minY >= 0 && minY - height < 0
                            Color.clear
                                .onChange(of: isTopRow) { oldVal, newVal in
                                    if newVal {
                                        scrollPosition = gridItemIndex / gridRowLayout.count
                                    }
                                }

                        }
                    }
                }
        }
    }
}
.coordinateSpace(.named("scroll"))
.navigationTitle("The Top Row is: \(scrollPosition)")
.navigationBarTitleDisplayMode(.inline)

字符串
其他代码没有变化,只是不再需要ScrollOffsetPreferenceKey和视图扩展updateViewsYPosition

相关问题