SwiftUI ScrollView:查找当前在屏幕中心可见的子视图。子视图在 ScrollView 中的位置。偏好键。滚动视图代理

Posted

技术标签:

【中文标题】SwiftUI ScrollView:查找当前在屏幕中心可见的子视图。子视图在 ScrollView 中的位置。偏好键。滚动视图代理【英文标题】:SwiftUI ScrollView: find subview that is currently visible at center of screen. Subview's position within ScrollView. PreferenceKey. ScrollViewProxy 【发布时间】:2021-04-03 20:38:29 【问题描述】:

挑战:我想跟踪 ScrollView 的哪个子视图位于此 ScrollView 的可见区域的中间。

问题:我知道没有原生 SwiftUI 方法可以确定 ScrollView 的子视图当前是否在屏幕上可见。还是我错过了什么?

我的解决方案:我正在使用 PreferenceKey 收集子视图位置并在滚动 ScrollView 时连续发生的 onPreferenceChange(s) 操作:检查最靠近屏幕中心的视图.

问题:基于 PreferenceKey 的解决方案适用于一些子视图。但我需要跟踪多达 3000 个子视图(从数据库构建的大型结构化文档的视图)。大量视图的性能无法接受,即使在实施了一些我已经能够提出的优化之后。

我的问题是:有没有

一种提高下面所示解决方案性能的方法,或 应对这一挑战的不同方式

ios 14.4 / Xcode 12.4)

private struct ViewOffsetsKey: PreferenceKey 
    static var defaultValue: [Int: CGFloat] = [:]
    static func reduce(value: inout [Int: CGFloat], nextValue: () -> [Int: CGFloat]) 
        value.merge(nextValue(), uniquingKeysWith:  $1 )
    


struct ContentView: View 
    @State private var offsets: [Int: CGFloat] = [:]
    @State private var mainViewHeight: CGFloat = 800   // demo approximation
    @State private var highlightItem: Bool = false
    @State private var timer = Timer.publish(every: 0.5, on: .main, in: .common).autoconnect()
    
    var body: some View 
        ZStack(alignment: .top) 
            VStack 
                ScrollView 
                    VStack 
                        ForEach(0..<3000)  i in
                            Text("Item \(i)")
                                .id(i)
                                .padding()
                                .background(GeometryReader  geo in
                                    Color.clear.preference(
                                        key: ViewOffsetsKey.self,
                                        value: [i: geo.frame(in: .named("scrollView")).origin.y]) )
                                .overlay((i == middleItemNo && highlightItem) ? Color.orange.opacity(0.5) : Color.clear)
                        
                    
                    .onPreferenceChange(ViewOffsetsKey.self, perform:  prefs in
                        let filteredPrefs = prefs.filter  $1 > 0 && $1 < mainViewHeight 
                        // Cleaning offsets seams to increase reliablilty.
                        offsets = [:]
                        // Dispatch to silence "Bound preference ... update multiple times per frame" warning.
                        DispatchQueue.main.async 
                            for pref in filteredPrefs  offsets[pref.key] = pref.value 
                        
                        timer = Timer.publish(every: 0.5, on: .main, in: .common).autoconnect()
                    )
                .coordinateSpace(name: "scrollView")
            
            
            HStack 
                Text("Middle item no: \(middleItemNo)")
                    .padding(5)
                    .background(Color.white)
                Spacer()
            .onReceive(timer)  _ in
                highlightItem = true
                timer.upstream.connect().cancel()
                DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) 
                    highlightItem = false
                
            
        
    
    
    private var middleItemNo: Int 
        offsets.sorted(by:  $0 < $1 ).first(where:  $1 >= mainViewHeight / 2 - 100 )?.key ?? 0
    

【问题讨论】:

这是一个很好的项目你已经开始了,如果一切都按你的意愿工作,这个项目如何能够足够快地处理海量数据?例如,您将有 2000 行,然后您将使用 2000 行几何数据加上 2000 的 onPreferenceChange,使用此设置,我认为您也无法使用 LazyVStack,您是否想过? @swiftPunk 是的,我在发布问题几分钟后就意识到了 LazyVStack 的改进。这就是为什么我稍后通过这个改进回答了我自己的问题。 【参考方案1】:

在 ScrollView 中为 LazyVStack 更改 VStack 可以解决性能问题。只有在发布帖子后才提出这个想法。我会把它留在网上供其他人学习。

【讨论】:

以上是关于SwiftUI ScrollView:查找当前在屏幕中心可见的子视图。子视图在 ScrollView 中的位置。偏好键。滚动视图代理的主要内容,如果未能解决你的问题,请参考以下文章

获取 SwiftUI ScrollView 的当前滚动位置

ScrollView 位置数据 / SwiftUi

SwiftUI:ScrollView 偏移滚动内容

SwiftUI - 淡出 ScrollView

SwiftUI:调整 ScrollView 的边界

SwiftUI:为啥这个 ScrollView 的内容错位了?