0

So I use the GeometryReader to read the available width of a container. Then I calculate how large the buttons should be and that decides the true size. However the GeometryReader seem to not be aware of the size of its children.

How should you tell GeometryReader about its true size?

My guess is:

import SwiftUI

struct ContentView: View {
    @State private var containerHeight: Double = 0.0

    var body: some View {
        GeometryReader { proxy in
            Text("Test")
                .task { containerHeight = calculateCustomHeight(using: proxy) }
        }
        .frame(height: containerHeight)
    }
}

This works. However I am not really sure if this is the SwiftUI way or an infinite cycle.

The container height is calculated, which updates @State, which updates the views, which calculates the container height, which updates @State, etc.

I presume it works now because the value is the same, thus does not trigger another update of the view.

Mark
  • 16,906
  • 20
  • 84
  • 117
  • Can you show a [mcve] of what is not working? As you said, the code you showed does work. It is unclear what the problem you are trying to solve is. – Sweeper Aug 31 '23 at 11:01
  • 1
    You should use Preferences (https://developer.apple.com/documentation/swiftui/preferences) If you want to access the sizes of the child view – YodagamaHeshan Aug 31 '23 at 11:09

1 Answers1

0

I assume you would like to obtain the size value of a view and work with it later. This can be accomplished by utilizing Preferences.

1. Creating the PreferenceKey

struct SizePreferenceKey: PreferenceKey {
    static var defaultValue: CGSize = .zero
    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
        value = nextValue()
    }
}

2. Using the PreferenceKey

Implement the PreferenceKey and make use of GeometryReader on the desired View to extract its size.

                .background {
                    GeometryReader { geometry in
                        Color.clear.preference(key: SizePreferenceKey.self,
                                               value: geometry.size)
                    }
                }

3. Retrieving the size value

        .onPreferenceChange(SizePreferenceKey.self) { size in
            print("size of the red text is: \(size)")
            // use the value however you want
            sizeOfText = size
        }

The entire code then looks like this:

struct GeometryReaderTest: View {
    
    @State private var sizeOfRedText: CGSize = .zero
    
    var body: some View {
        VStack {
            Text("Green text")
                .background { Color.green }
            
            Text("Red text")
                .background { Color.red }
            // PreferenceKey & GeometryReader usage
                .background {
                    GeometryReader { geometry in
                        Color.clear.preference(key: SizePreferenceKey.self,
                                               value: geometry.size)
                    }
                }
        }
        .onPreferenceChange(SizePreferenceKey.self) { size in
            print("size of the red text is: \(size)")
            // use the value however you want
            sizeOfRedText = size
        }
    }
}

struct SizePreferenceKey: PreferenceKey {
    static var defaultValue: CGSize = .zero
    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
        value = nextValue()
    }
}

4. Extension

You can create an extension to conveniently extract the size of different views within your project.

extension View {
    func getSizeOfView(_ getSize: @escaping ((CGSize) -> Void)) -> some View {
        return self
            .background {
                GeometryReader { geometry in
                    Color.clear.preference(key: SizePreferenceKey.self,
                                           value: geometry.size)
                    .onPreferenceChange(SizePreferenceKey.self) { value in
                        getSize(value)
                    }
                }
            }
    }
}

Then you can call the getSizeOfView like this:

            Text("Red text")
                .background { Color.red }
                .getSizeOfView { size in
                    print("size of the red text is: \(size)")
                    sizeOfRedText = size
                }
Hollycene
  • 287
  • 1
  • 2
  • 12