4 Replies
      Latest reply on Oct 25, 2019 4:36 AM by rreimche
      rreimche Level 1 Level 1 (0 points)

        I have a GameView that has "@State var locationManager: LocationManager" which is passed to my MapView as a binding. The MapView conforms to UIViewRepresentable protocol. LocationManager conforms, among other things, to ObservableObject and has the following conformance-related code:

         

        var didChange = PassthroughSubject<locationmanager, never="">()
        
        
            var lastKnownLocation: CLLocation {
                didSet {
                   
                    // Propagate the update to the observers.
                    didChange.send(self)
                    print("Finished propagating lastKnownLocation to the observers.")
                }

            }

         

        I suppose that GameView and consequently MapView must be updated every time LocationManager.lastKnownLocation changes. In practice I only see MapView.updateUIView() called when I exit the app per home button. At this point control gets in updateUIView() and when I open the app again (not compile-install-run), I get the update. This also happens once short after the GameView() was presented.

         

        Is my understanding of how SwiftUI works wrong or is it some bug? How do I get it right?

        • Re: UIViewRepresentable.updateUIView(_:context:) is not called when @State, @Binding or @EnvironmentObject are changed.
          Jim Dovey Level 3 Level 3 (140 points)

          If LocationManager conforms to ObservableObject, then you need to use the @ObservedObject attribute when passing it down. @State and @Binding are intended for value types, and so they fire when the value they wrap is changed. In the case of your LocationManager class, it looking at the pointer value that provides the reference to your class's instance storage—this is why the update is firing when your app quits and the value is (I would assume) set to nil.

           

          Change your variables to use @ObservedObject rather than @State and you should be golden—that wrapper works by subscribing to the value publisher.

           

          Last thing: ObservableObject can largely be automated for you: instead of declaring the didChange property yourself you can use the @Published attribute on any contents of your LocationManager to have all that synthesized for you.

            • Re: UIViewRepresentable.updateUIView(_:context:) is not called when @State, @Binding or @EnvironmentObject are changed.
              rreimche Level 1 Level 1 (0 points)

              Thank you very much, it has cleared my understanding. However that did not help, unfortunately. I have changed @State and @Binding to @ObservedObject and nothing changed. Then I have also updated the code to use "@Published" and it did not help either. Here is my code before the changes:

               

              struct GameView: View { 
                @State var locationManager: LocationManager 
              
              
                var body: some View { 
                MapView(locationManager: $locationManager)
                }
              }
              
              struct MapView: UIViewRepresentable {
              
                @Binding var locationManager: LocationManager
              
                func makeUIView(context: Context) -> GMSMapView{ ... }
              
                func updateUIView(_ mapView: GMSMapView, context: Context) { ... }
              
              }
              
              class LocationManager: NSObject, CLLocationManagerDelegate, ObservableObject {
              
                var didChange = PassthroughSubject<locationmanager, never="">()
              
                var lastKnownLocation: CLLocation {
                didSet {
              
              
                // Propagate the update to the observers.
                didChange.send(self)
                print("Finished propagating lastKnownLocation to the observers.")
                }
                }
              
                ...
              
              }

               

              And here it is after the proposed changes:

               

              struct GameView: View { 
                @ObservedObject var locationManager: LocationManager 
              
              
                var body: some View { 
                MapView(locationManager: locationManager)
                }
              }
              
              struct MapView: UIViewRepresentable {
              
                @ObservedObject var locationManager: LocationManager
              
                func makeUIView(context: Context) -> GMSMapView{ ... }
              
                func updateUIView(_ mapView: GMSMapView, context: Context) { ... }
              
              }
              
              class LocationManager: NSObject, CLLocationManagerDelegate, ObservableObject {
              
                @Published var lastKnownLocation: CLLocation
                ...
              
              }
              • Re: UIViewRepresentable.updateUIView(_:context:) is not called when @State, @Binding or @EnvironmentObject are changed.
                rreimche Level 1 Level 1 (0 points)

                Today the thing worked as intended and I don't know why. Now it still does not, I suppose it is because I've made locationManagers @EnvironmentObject instead of @ObserverdObject. Would @EnvironmentObject pass for my needs?

                • Re: UIViewRepresentable.updateUIView(_:context:) is not called when @State, @Binding or @EnvironmentObject are changed.
                  rreimche Level 1 Level 1 (0 points)

                  New data: the view is updated if I change the device orientation (horizontal/vertical).