'SwiftUI Pass Value from Geometry Reader to Function

I have a view which uses Geometry Reader to calculate how large the image area should be:

GeometryReader { metrics in
    ZStack{
        self.image
           .resizable()
           .frame(width:  (metrics.size.height - 10) * 0.561403509 , height: metrics.size.height - 10, alignment: .top)
           .clipped()
    }
}

But I have a function where I want to use the frame height and width calculated by the GeometryReader in order to crop the image.

I have a separate function which crops the image when a button is pressed:

 DownloadImageButton(prepareImagefunction: { self.prepareImage() })

Which then calls a prepareImage function:

func prepareImage( ) {
 // In order for image cropping to work, we need the equivalent of view.bounds in order to adjust the crop so that it correctly measures the crop area. We need metrics.width and metrics.height 
 var adjustmentScaleForDisplaySize = targetSize.width / metrics.width

Note that the GeometryReader is a child of the parent view where prepareImage is called. Therefore, ideally, the child would save the metrics values in an EnvironmentObject or Binding to the parent.



Solution 1:[1]

Is there a way to pass the value calculated by GeometryReader to a function?

Yes, you can do this. The var metrics is a GeometryProxy, so function signature should be like in the following example

private func resizedImage(for metrics: GeometryProxy) -> some View {
    self.image
       .resizable()
       .frame(width:  (metrics.size.height - 10) * 0.561403509 , height: metrics.size.height - 10, alignment: .top)
       .clipped()
}

so your usage

GeometryReader { metrics in
    ZStack{
       self.resizedImage(for: metrics)
    }
}

Update: for changed request

Assuming you have in child view binding as

@Binding var cropSize: CGSize

the above function can be modified as follow

private func resizedImage(for metrics: GeometryProxy) -> some View {
    let size = CGSize(width: (metrics.size.height - 10) * 0.561403509, 
                           height: metrics.size.height - 10)
    self.cropSize = size
    return self.image
       .resizable()
       .frame(width: size.width, height: size.height, alignment: .top)
       .clipped()
}

backup

Solution 2:[2]

Since I actually ONLY needed the crop size, and didn't necessarily need an image returned, I did the following which also helped to avoid throwing an error which can occur in the accepted answer:

[SwiftUI] Modifying state during view update, this will cause undefined behavior.

I added an .onAppear function call which helps to avoid the above error, and then properly passes the crop dimension to the parent:

GeometryReader { metrics in
  ZStack{
   self.image
   .resizable()
   .frame(width:  (metrics.size.height - 10) * 0.561403509 , height: metrics.size.height - 10, alignment: .top)
   .clipped()
  }.onAppear{
    self.saveCropSize(for: metrics)
  }....

private func saveCropSize(for metrics: GeometryProxy){
    let size = CGSize(width: (metrics.size.height - 10) * 0.561403509, 
                           height: metrics.size.height - 10)
    self.cropSize = size
}

Solution 3:[3]

I made a solution I think is useful, using a new type:

public struct GeometryPasser: View {
    private let op: (GeometryProxy) -> Void
    public init(_ op: @escaping (GeometryProxy) -> Void) {
        self.op = op
    }
    
    public var body: some View {
        GeometryReader { proxy in
            Color.clear.onAppear {
                op(proxy)
            }
        }
    }
}

extension View {
    public func passGeometry(_ op: @escaping (GeometryProxy) -> Void) -> some View {
        self.background(
            GeometryPasser(op)
        )
    }
}

and call like:

Rectangle()
    .fill(.yellow)
    .passGeometry { geo in
        print("do whatever you want here: \(geo)")
    }

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 dot3
Solution 3 Logan