1

I have a GeometryReader nested inside of a NavigationView. Inside the GeometryReader I am using the dimensions supplied by the GeometryProxy with an if-statement. In my project I stumbled over the fact, that the content inside the GeometryReader is evaluated twice and the provided dimensions are width 0 and height 0 the first time. Also the onAppear() method of the content inside of the if case is called twice which lead to more problems.

Could someone explain that behavior so I can improve my code?

I made a minimal example to showcase the problem:

import SwiftUI

struct ContentView: View {
    var body: some View {
        NavigationView() {
            NavigationLink(destination: SecondView()) {
                Text("Click me!")
            }
        }
        .navigationViewStyle(StackNavigationViewStyle())
    }
}

struct SecondView: View {
    private func showHorizontal(_ w: CGFloat, _ h: CGFloat) -> Bool {
        print("Dimensions \(w), \(h)")
        return (w > h)
    }
    
    var body: some View {
        GeometryReader { proxy in
            if self.showHorizontal(proxy.size.width, proxy.size.height) {
                Text("Landscape")
                .onAppear() {
                    print("Landscape appeared")
                }
            } else {
                Text("Portrait")
                .onAppear() {
                    print("Portrait appeared")
                }
            }
        }
        .onAppear() {
            print("GeometryReader appeared")
        }
    }
}

On my iPad in landscape orientation I get the following console output:

Dimensions 0.0, 0.0
Dimensions 1194.0, 688.0
GeometryReader appeared
Landscape appeared
Landscape appeared
DFox
  • 86
  • 8

2 Answers2

1

GeometryReader is updating on changes in its geometry, which makes sense. In your if-clause, you are creating a new Text view on each evaluation. What you're seeing is the onAppear of each of these.

if self.showHorizontal(proxy.size.width, proxy.size.height) {
         Text("Landscape")
         .onAppear() {
             print("Landscape appeared")
     }
}

If you needed the Text to be constant for some reason, you can assign it an id using the id modifier, like so:

if self.showHorizontal(proxy.size.width, proxy.size.height) {
         Text("Landscape")
         .id("fixed id")
         .onAppear() {
             print("Landscape appeared")
     }
}
kid_x
  • 1,415
  • 1
  • 11
  • 31
0

I tried running OP's example code in 12.4 (12D4e) but did not get the double evaluation behavior. On launch I get:

Dimensions 1024.0, 1366.0
Dimensions 1024.0, 1220.0
GeometryReader appeared
Portrait appeared

and rotating 4 times (full circle):

Dimensions 1366.0, 878.0
Landscape appeared
Dimensions 1366.0, 898.0
Dimensions 1024.0, 1220.0
Portrait appeared
Dimensions 1366.0, 878.0
Landscape appeared
Dimensions 1366.0, 898.0
Dimensions 1024.0, 1220.0
Portrait appeared

Hard-coding an ID, like .id("landscape"), didn't affect the behavior and yielded the same print statements from above.

To get around having "Portrait/Landscape appeared" print with every rotation, I factored Text() out of the if-statement:

GeometryReader { proxy in
    let text = self.showHorizontal(proxy.size.width, proxy.size.height) ? "Lanscape" : "Portrait"
    Text("\(text)")
        // .id(text)  // un-comment if you do want to see "Orientation appeared" w/every rotation
        .onAppear() {
            print("\(text) appeared")
        }
}
.onAppear() {
    print("GeometryReader appeared")
}

Now launch and a full circle of rotations yields:

Dimensions 1024.0, 1220.0
Portrait appeared
GeometryReader appeared
Dimensions 1366.0, 878.0
Dimensions 1366.0, 898.0
Dimensions 1024.0, 1220.0
Dimensions 1366.0, 878.0
Dimensions 1366.0, 898.0
Dimensions 1024.0, 1220.0
Zach Young
  • 10,137
  • 4
  • 32
  • 53