【问题标题】:SWiftUI anchorPreference inside List列表内的 SWIftUI anchorPreference
【发布时间】:2020-12-21 02:34:51
【问题描述】:

我使用anchorPreferences 来设置GeometryReaderheight 以适应其内容的height。我遇到的问题是,在上下滚动List 几次后,应用程序冻结,设计变得混乱,我在控制台中收到以下消息:

Bound preference CGFloatPreferenceKey tried to update multiple times per frame.

有什么办法可以解决这个问题吗?

重要提示:我已尽可能简化我的设计。

代码如下:

struct ListAndPreferences: View {
    var body: some View {
        List(1..<35) { idx in
            HStack {
                Text("idx: \(idx)")
                
                InnerView(idx: idx)
            }
        }
    }
}

struct InnerView: View {
    @State var height: CGFloat = 0
    var idx: Int
    
    var body: some View {
        GeometryReader { proxy in
            generateContent(maxWidth: proxy.frame(in: .global).size.width)
                .anchorPreference(key: CGFloatPreferenceKey.self, value: Anchor<CGRect>.Source.bounds, transform: { anchor in
                    proxy[anchor].size.height
                })
        }
        .frame(height: height)
        .onPreferenceChange(CGFloatPreferenceKey.self, perform: { value in
            height = value
        })
    }
    
    private func generateContent(maxWidth: CGFloat) -> some View {
            VStack {
                HStack {
                    Text("hello")
                        .padding()
                        .background(Color.purple)
                    
                    Text("world")
                        .padding()
                        .background(Color.purple)
                }               
            }
            .frame(width: maxWidth)
    }
}

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

【问题讨论】:

    标签: swiftui swiftui-list geometryreader


    【解决方案1】:

    实际上我们不知道 SwiftUI 可以在堆栈上渲染多少次自定义视图的主体,但偏好确实只需要编写一次(然后它们应该被转换,这更复杂)。

    可能的解决方案是在 Preferences 中使用不同类型的容器,因此值不会重写而是累积。

    这是您的代码的修改部分。使用 Xcode 12.1 / iOS 14.1 测试

    // use dictionary to store calculated height per view idx
    struct CGFloatPreferenceKey: PreferenceKey {
        static var defaultValue: [Int: CGFloat] = [:]
        static func reduce(value: inout [Int: CGFloat] , nextValue: () -> [Int: CGFloat]) {
            value.merge(nextValue()) { $1 }
        }
    }
    
    struct InnerView: View {
        @State var height: CGFloat = 0
        var idx: Int
        
        var body: some View {
            GeometryReader { proxy in
                generateContent(maxWidth: proxy.frame(in: .global).size.width)
                    .anchorPreference(key: CGFloatPreferenceKey.self, value: Anchor<CGRect>.Source.bounds, transform: { anchor in
                        [idx: proxy[anchor].size.height]
                    })
            }
            .frame(minHeight: height)
            .onPreferenceChange(CGFloatPreferenceKey.self, perform: { value in
                height = value[idx] ?? .zero
            })
        }
        
        private func generateContent(maxWidth: CGFloat) -> some View {
                VStack {
                    HStack {
                        Text("hello")
                            .padding()
                            .background(Color.purple)
                        
                        Text("world")
                            .padding()
                            .background(Color.purple)
                    }
                }
                .frame(width: maxWidth)
        }
    }
    
    

    【讨论】:

    • but preferences really required to be written only once - 因此“尝试每帧更新多次”。是坏事吗?
    • 我不认为这是,这只是一个警告,意味着您存储的一些偏好值可能会丢失,而在需要时不应用。跨度>
    • 嗯,有道理。谢谢!
    • 谢谢@Asperi!有没有办法在 InnerView 中“生成”“idx”,这样我就不必将其作为参数发送?
    【解决方案2】:

    List 中的代码试图做太多事情。 SwiftUI 的好处之一是您无需手动设置视图的高度。也许GeometryReader 只是在您简化示例时遗留下来的,但在这种简化的情况下,您不需要它(或首选项)。这就是你所需要的:

    import SwiftUI
    
    struct ListAndPreferences: View {
        var body: some View {
            List(1..<35) { idx in
                HStack {
                    Text("idx: \(idx)")
                    InnerView()
                }
            }
        }
    }
    
    struct InnerView: View {
        var body: some View {
            VStack {
                HStack {
                    Text("hello")
                        .padding()
                        .background(Color.purple)
    
                    Text("world")
                        .padding()
                        .background(Color.purple)
                }
            }.frame(maxWidth: .infinity)
        }
    }
    
    struct InnerView_Previews: PreviewProvider {
        static var previews: some View {
            ListAndPreferences()
        }
    }
    

    如果您出于某种原因确实需要通过onPreferenceChange 或就此而言onChange(of:) 监听更改,则需要确保任何影响 GUI 的更改都发生在主线程上.以下更改将使您在原始代码中看到的警告静音:

    .onPreferenceChange(CGFloatPreferenceKey.self) { value in
        DispatchQueue.main.async {
            height = value
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-04-28
      • 2022-07-03
      • 1970-01-01
      • 2021-01-01
      • 2021-09-02
      • 1970-01-01
      相关资源
      最近更新 更多