SWiftUI主播参考内部列表

SWiftUI主播参考内部列表,swiftui,swiftui-list,geometryreader,Swiftui,Swiftui List,Geometryreader,我正在使用AnchorReferences设置GeometryReader的高度,以适应其内容的高度。我遇到的问题是,在上下滚动列表几次后,应用程序冻结,设计变得混乱,我在控制台中收到以下消息: Bound preference CGFloatPreferenceKey tried to update multiple times per frame. 有没有办法解决这个问题 重要提示:我已经尽可能地简化了我的设计 代码如下: struct ListAndPreferences: View

我正在使用
AnchorReferences
设置
GeometryReader
高度,以适应其内容的
高度。我遇到的问题是,在上下滚动
列表几次后,应用程序冻结,设计变得混乱,我在控制台中收到以下消息:

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()
    }
}
struct ListAndPreferences:视图{
var body:一些观点{
列表(1..一些视图){
VStack{
HStack{
文本(“你好”)
.padding()
.背景(颜色.紫色)
文本(“世界”)
.padding()
.背景(颜色.紫色)
}               
}
.框架(宽度:maxWidth)
}
}
结构CGFloatPreferenceKey:PreferenceKey{
静态变量defaultValue:CGFloat=0
静态函数reduce(值:inout CGFloat,nextValue:()->CGFloat){
value=nextValue()
}
}

事实上,我们不知道SwiftUI在堆栈上可以呈现自定义视图的主体多少次,但首选项实际上只需要编写一次(然后应该进行转换,这更复杂)

可能的解决方案是在首选项中使用不同类型的容器,这样值就不会重新写入,而是累积

这是您代码的修改部分。使用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)
    }
}

//使用字典存储每个视图的计算高度idx
结构CGFloatPreferenceKey:PreferenceKey{
静态变量defaultValue:[Int:CGFloat]=[:]
静态函数reduce(值:inout[Int:CGFloat],下一个值:()->[Int:CGFloat]){
value.merge(nextValue()){$1}
}
}
结构内部视图:视图{
@状态变量高度:CGFloat=0
变量idx:Int
var body:一些观点{
GeometryReader{中的代理
generateContent(maxWidth:proxy.frame(in:.global).size.width)
.AnchorReference(键:CGFloatPreferenceKey.self,值:Anchor.Source.bounds,转换:{Anchor in
[idx:代理[anchor]。大小。高度]
})
}
.框架(最小高度:高度)
.onPreferenceChange(CGFloatPreferenceKey.self,执行:{中的值
高度=值[idx]??.0
})
}
private func generateContent(maxWidth:CGFloat)->一些视图{
VStack{
HStack{
文本(“你好”)
.padding()
.背景(颜色.紫色)
文本(“世界”)
.padding()
.背景(颜色.紫色)
}
}
.框架(宽度:maxWidth)
}
}

您的
列表中的代码试图做得太多了。SwiftUI的一个好处是您不需要手动设置视图的高度。可能简化示例时,
GeometryReader
只是遗留下来的,但在这种简化的情况下,您不需要它(或首选项).这就是您所需要的:

导入快捷界面
结构列表和首选项:视图{
var body:一些观点{

列表(1..
,但首选项实际上只需要编写一次
——因此“尝试每帧更新多次”这是一件坏事吗?我不认为这是坏事,这只是一个警告,意味着您存储的某些首选项值可能会丢失,而不在需要时应用。啊,这是有道理的。谢谢!谢谢@Asperi!有没有办法在InnerView中“生成”idx,这样我就不必将其作为参数发送?