I found a workaround by changing the PreferenceKey to hold an array; it solved the problem.
Inspired by: https://swiftui-lab.com/communicating-with-the-view-tree-part-1/
The new code:
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()
}
}