SWiftUI anchorPreference inside List

馋奶兔 提交于 2021-01-04 06:00:29

问题


I'm using anchorPreferences to set the height of GeometryReader to fit the height of its content. The issue I'm experiencing is, after scrolling up and down the List a few times, the app freezes, the design gets messy, and I get the following message in the console:

Bound preference CGFloatPreferenceKey tried to update multiple times per frame.

Any ideas how I can fix this?

IMPORTANT NOTE: I've simplified my design as much as I could.

Here is the code:

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()
    }
}

回答1:


Actually we don't know how many times on stack SwiftUI can render a body of our custom view, but preferences really required to be written only once (and then they should be transformed, which is more complex).

The possible solution is to use different type of container in Preferences, so values not re-written but accumulated.

Here is modified parts of your code. Tested with 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)
    }
}



来源:https://stackoverflow.com/questions/65386898/swiftui-anchorpreference-inside-list

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!