首页 > 解决方案 > 嵌入 HStack 时 PreferenceKey 不起作用 - SwiftUI

问题描述

以下代码是该问题的简化版本。

Dimensions视图采用任何视图组合,并显示使用 PreferenceKeys 组合的所有视图的宽度和高度。

如示例 1 所示:当在其中构建所有视图Dimensions,时,它将工作并显示大小。

而在示例 2 和 3 中:如果视图是在外部构建的Dimensions,,它将不起作用。

问题:当视图在外部构建时它会如何工作?谢谢。

在 Xcode 版本 12.5.1 (12E507) 上运行目标 iOS 14.5

在此处输入图像描述

这是代码:

import SwiftUI

struct ContentView: View {
    
    var body: some View {
        VStack {
            
            // Example 1
            Dimensions {
                HStack {
                    
                    Text("Hello, world!")
                        .padding()
                    
                    // HelloWorldVariable   // <===== THIS WILL BREAK IT
                    // HelloWorldView()     // <===== THIS WILL BREAK IT
                }
            }.background(Color.blue)
            
            // Example 2
            Dimensions {
                HStack {
                    HelloWorldVariable // <===== THIS DOES NOT WORK in the HStack
                }
            }.background(Color.green)
            
            // Example 3
            Dimensions {
                HStack {
                    HelloWorldView() // <===== THIS DOES NOT WORK in the HStack
                }
            }.background(Color.yellow)
        }
    }
    
    private var HelloWorldVariable: some View {
        Text("Hello, world!")
            .padding()
    }
}

// MARK:- HelloWorldView
struct HelloWorldView: View {
    var body: some View {
        Text("Hello, world!")
            .padding()
    }
}

// MARK:- Dimensions
struct Dimensions<Content: View>: View {
    
    private var content: () -> Content
    @State private var contentSize: CGSize = .zero
    
    init(@ViewBuilder content: @escaping () -> Content) {
        self.content = content
    }
    
    var body: some View {
        VStack {
            content()
                .background(ObserveViewDimensions())
            
            VStack {
                Text("contentWidth \(contentSize.width)")
                Text("contentHeight \(contentSize.height)")
            }
        }
        .onPreferenceChange(DimensionsKey.self, perform: { value in
            self.contentSize = value
        })
    }
}

// MARK:- ObserveViewDimensions
struct ObserveViewDimensions: View {
    var body: some View {
        GeometryReader { geometry in
            Color.clear.preference(key: DimensionsKey.self, value: geometry.size)
        }
    }
}

// MARK:- DimensionsKey
struct DimensionsKey: PreferenceKey {
    static var defaultValue: CGSize = .zero
    
    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
        value = nextValue()
    }
    
    typealias Value = CGSize
}

// MARK:- Preview
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

标签: swiftswiftui

解决方案


经过进一步调查,事实证明 Canvas (SwiftUI Previews) 在 Xcode 版本 12.5.1 (12E507) 中存在该错误。

原始代码适用于运行 iOS 14.8 的 iPhone X

有一种解决方法可以使其适用于 Canvas,方法是更改​​ PreferenceKey 以保存数组,如下面的代码所示。

灵感来自: https ://swiftui-lab.com/communicating-with-the-view-tree-part-1/

在此处输入图像描述

这是新代码:

import SwiftUI

struct ContentView: View {
    
    var body: some View {
        VStack {
            
            // Example 1
            Dimensions {
                HStack {
                    
                    Text("Hello, world!")
                        .padding()
                    
                     HelloWorldVariable
                    
                     HelloWorldView()
                }
            }
        }
    }
    
    private var HelloWorldVariable: some View {
        Text("Hello, world!")
            .padding()
    }
}

// MARK:- HelloWorldView
struct HelloWorldView: View {
    var body: some View {
        Text("Hello, world!")
            .padding()
    }
}

// MARK:- Dimensions
struct Dimensions<Content: View>: View {
    
    private var content: () -> Content
    @State private var contentSize: CGSize = .zero
    
    init(@ViewBuilder content: @escaping () -> Content) {
        self.content = content
    }
    
    var body: some View {
        VStack {
            content()
                .background(ObserveViewDimensions())
            
            VStack {
                Text("contentWidth \(contentSize.width)")
                Text("contentHeight \(contentSize.height)")
            }
        }
        .onPreferenceChange(DimensionsKey.self, perform: { value in
            // 4. Add this
            DispatchQueue.main.async {
                self.contentSize = value.first?.size ?? .zero
            }
        })
    }
}

// MARK:- ObserveViewDimensions 3. // <== update this with the new data type
struct ObserveViewDimensions: View {
    var body: some View {
        GeometryReader { geometry in
            Color.clear.preference(key: DimensionsKey.self, value: [ViewSizeData(size: geometry.size)])
        }
    }
}

// MARK:- DimensionsKey 2. // <== update this with the new data type
struct DimensionsKey: PreferenceKey {
    static var defaultValue: [ViewSizeData] = []
    
    static func reduce(value: inout [ViewSizeData], nextValue: () -> [ViewSizeData]) {
        value.append(contentsOf: nextValue())
    }
    
    typealias Value = [ViewSizeData] 
}

// MARK:- ViewSizeData 1 <==== Add This
struct ViewSizeData: Identifiable, Equatable, Hashable {
    let id: UUID = UUID()
    let size: CGSize
    
    static func == (lhs: Self, rhs: Self) -> Bool {
        return lhs.id == rhs.id
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
}

// MARK:- Preview
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

推荐阅读