The Dimensions view takes any combination of views and shows the width & height of all the views combined using PreferenceKeys.
As seen in Example 1: When constructing all the views inside Dimensions, it will work and show the sizes.
While in Examples 2 & 3: If the views are constructed outside of Dimensions, it will not work.
QUESTION: How would it work when the views are constructed outside? Thanks.
Running on Xcode Version 12.5.1 (12E507) target iOS 14.5
The code:
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()
}
}
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()
}
}