GeometryReader Stops View Updating

I have a number of Observable Object classes and for each, I define a "resultsView" variable that summarises key data from the class in an AnyView. This variable can then be embedded in a View struct. Typically, I use a protocol to allow the View struct to display the "resultsView" of a number of data object classes.

This works fine and redraws the view when the Observable Object updates. I have now added a GeometryReader within this variable in the class. Now, any data items within the Geometry Reader no longer update when the view is rebuilt, following a change in the @Published Value in the Observable Object.

Below is a test View demonstrating this. It is a simple button incrementing the published value on the object. The View body also includes the testObject.resultsView which is an AnyView type summarising the results from the class.

Code Block
struct ContentView: View {
  @ObservedObject var testObject: TEST = TEST()
   
   
  var body: some View {
    VStack {
      Text("Current Value From View \(testObject.testValue)")
      Button<Text>(action: {self.testObject.testValue += 1}, label: { Text("Add One Button")})
      testObject.resultView
    }
  }
}
struct ContentView_Previews: PreviewProvider {
  static var previews: some View {
    ContentView()
  }
}

This is my class, including the resultsView variable.

Code Block
class TEST: ObservableObject {
  @Published var testValue: Double = 0
  var resultView: AnyView {
    AnyView(
      VStack{
      Spacer()
      Text("Text From Object, Outside of GeometryReader")
        Text("Value \(self.testValue.description)")
      GeometryReader {
        geom in
        VStack {
        Text("Text From Object Inside Geom Reader")
        Text(self.testValue.description).frame(width: geom.size.width / 2, height: geom.size.height / 2)
        }.background(Color.secondary)
        }}.background(Color.yellow)
    )
  } // End of resultView
} //End of Class
}


When I run this all values update on the button press, except the one that is returned in the GeometryReader section of the AnyView variable. It seems anything I put in the Geometry reader section no longer updates.

I have also tried passing as a () -> AnyView closure and using a @ViewBuilder function with the same result. It is being called to update as the first return of the value is updating but not the value from within the GeometryReader.

Any Ideas?







Answered by hktron in 656449022
I solved this eventually by adding another AnyView(...) to enclose all the view components inside the GeometryReader.

I tried all sorts of things including using @ViewBuilder for the variable and with a function to return the view but the problem remained. Adding AnyView(...) to enclose the elements in the GeometryReader was the only way I found to make it work.

Code Block class TEST: ObservableObject {
  @Published var testValue: Double = 0
  var resultView: AnyView {
    AnyView (VStack{
      Text("Text From Object, Outside of GeometryReader")
      Text("Value \(self.testValue.description)")
    GeometryReader {
    geom in
      AnyView( -> AnyView added to enclose code within GeometryReader
      VStack{
        VStack {
        Text("Text From Object Inside Geom Reader")
        Text(self.testValue.description).frame(width: geom.size.width / 2, height: geom.size.height / 3)
        }.background(Color.secondary)
        }
      ) -> Close of AnyView added
    } -> End of GeomReader
}
    )
    }
} // End of class


Hi, I’m having a similar issue, altho not exactly the same. In my case, GeometryReader is making my view update in two steps (first it updates state variables and then a binding).

Did you find any workaround or any reason why this was happening to you?
Accepted Answer
I solved this eventually by adding another AnyView(...) to enclose all the view components inside the GeometryReader.

I tried all sorts of things including using @ViewBuilder for the variable and with a function to return the view but the problem remained. Adding AnyView(...) to enclose the elements in the GeometryReader was the only way I found to make it work.

Code Block class TEST: ObservableObject {
  @Published var testValue: Double = 0
  var resultView: AnyView {
    AnyView (VStack{
      Text("Text From Object, Outside of GeometryReader")
      Text("Value \(self.testValue.description)")
    GeometryReader {
    geom in
      AnyView( -> AnyView added to enclose code within GeometryReader
      VStack{
        VStack {
        Text("Text From Object Inside Geom Reader")
        Text(self.testValue.description).frame(width: geom.size.width / 2, height: geom.size.height / 3)
        }.background(Color.secondary)
        }
      ) -> Close of AnyView added
    } -> End of GeomReader
}
    )
    }
} // End of class


I think this may be related to a problem I've been having. A GeometryReader inside a .background or .overlay never runs/updates its children on a real device or simulator, but does work correctly in previews. I tried your suggestion of adding an AnyView and that made it work on the simulator and real phone. FWIW I made a simple demo of the bug. Without the AnyView(), on a real device or simulator the first Text stays stuck on "Square size unknown", but the second does update to "Did run updater: true".

Usually one should use .onAppear and/or .onChange(of:) to alter state, but they do not work in this context regardless of whether I use the AnyView hack. I've tried them in the GeometryUpdater, outside and inside the AnyView, and also in UpdateStateDuringViewUpdate. They never get called. This bug affects previews too.

import SwiftUI

struct UpdateStateDuringViewUpdate<T>: View {
    init(_ stateBinding: Binding<T>, _ newValue: T) {
        DispatchQueue.main.async {
            stateBinding.wrappedValue = newValue
        }
    }

    var body: some View { EmptyView() }
}

struct TestGeomReader: View {
    @State var squareSize = "unknown"
    @State var didRunUpdater = "false"

    var body: some View {
        VStack(alignment: .center) {
            GreySquare()
                .background {
                    GeometryUpdater(updatingStringWithSize: $squareSize)
                }
            Text("Square size \(squareSize)")
            Text("Did run updater: \(didRunUpdater)")
        }
    }

    @ViewBuilder func GreySquare() -> some View {
        Rectangle()
            .fill(Color(fromARGB: 0x80808080))
            .frame(maxWidth: 200, maxHeight: 200, alignment: .center)
    }

    @ViewBuilder func GeometryUpdater(
        updatingStringWithSize binding: Binding<String>
    ) -> some View {
        UpdateStateDuringViewUpdate($didRunUpdater, "true")
        GeometryReader { geom in
            AnyView(UpdateStateDuringViewUpdate($squareSize, "\(geom.size.width)"))
        }
    }
}

struct TestGeomReader_Previews: PreviewProvider {
    static var previews: some View {
        TestGeomReader()
    }
}
GeometryReader Stops View Updating
 
 
Q