0

I have use @EnvironmentObject in my source code, but it doesn't work correctly.

it's just my code:

import Foundation
import MapKit

class Configure: ObservableObject
{
    @Published var mapType = MKMapType.satellite
}

I have the Configure object in the userData:

class UserData: ObservableObject
{
    @Published var configure: Configure
}

And use the configure object in the MapHome:

struct MapHome: View
{
    @EnvironmentObject var userData: UserData

    NavigationView
    {
        ZStack
        {
            MapView(mapViewState: mapViewHomeState)
                    .edgesIgnoringSafeArea(.all)            
        }
        Button(action: {
                    switch self.userData.configure.mapType
                    {
                        case .hybrid:
                            self.userData.configure.mapType = .standard
                        case .standard:
                            self.userData.configure.mapType = .satellite
                        case .satellite:
                            self.userData.configure.mapType = .hybrid
                        default:
                            self.userData.configure.mapType = .standard
                    }
                }
        )
        {
            if self.configure.mapType == .hybrid
            {
                Image("HybridIcon")
            }
            else if self.configure.mapType == .standard
            {
                Image("StandardIcon")
            }
            else if self.configure.mapType == .satellite
            {
                Image("SatelliteIcon")
            }
        }

    }
}

MapView is just like that:

import SwiftUI
import MapKit

struct MapView: UIViewRepresentable
{ 
    @ObservedObject var configure: Configure

    func makeUIView(context: Context) -> MKMapView
    {
        return MKMapView(frame: .zero)
    }

    func updateUIView(_ view: MKMapView, context: Context)
    {
        //Set the map type
        view.mapType = configure.mapType        
    }    
}

Whe I click the button, the source code has go to MapView(mapViewState: mapViewHomeState).edgesIgnoringSafeArea(.all) , but not call MapView.updateUIView ! So, the map view doesn't refresh with the MKMapType!

What's wrong with my code? How could I do with it? Thanks very much!

norains
  • 743
  • 1
  • 5
  • 12
  • Your declaration of `MapView` and the way you are invoking it don’t match. The property is named `configure` in the declaration but `mapViewState` in the invocation. – Paulw11 Oct 21 '19 at 03:25
  • Check this out https://stackoverflow.com/questions/58406287/how-to-tell-swiftui-views-to-bind-to-nested-observableobjects/58406402#58406402 – Sorin Lica Oct 21 '19 at 04:44

2 Answers2

0

As @SorinLica mentioned in a comment, at this time, Nested ObservableObjects don't work in SwiftUI.

Probably the simplest approach is to add your Configure object to the environment directly (It is a class, so it will be the same instance that your UserData instance refers to).

In your SceneDelegate -

let userData = UserData()
let contentView = MapHome().environmentObject(userData).environmentObject(userData.configure)

Then you can use it in your MapHome

struct MapHome: View
{
    @EnvironmentObject var userData: UserData
    @EnvironmentObject var configure: Configure

    NavigationView
    {
        ZStack
        {
            MapView(mapViewState: $configure.mapType)
                    .edgesIgnoringSafeArea(.all)            
        }
        Button(action: {
                    switch self.userData.configure.mapType
                    {
                        case .hybrid:
                            self.userData.configure.mapType = .standard
                        case .standard:
                            self.userData.configure.mapType = .satellite
                        case .satellite:
                            self.userData.configure.mapType = .hybrid
                        default:
                            self.userData.configure.mapType = .standard
                    }
                }
        )
        {
            if self.userData.configure.mapType == .hybrid
            {
                Image("HybridIcon")
            }
            else if self.userData.configure.mapType == .standard
            {
                Image("StandardIcon")
            }
            else if self.userData.configure.mapType == .satellite
            {
                Image("SatelliteIcon")
            }
        }

    }
}

Your MapView will need to mapType as a @Binding -

struct MapView: UIViewRepresentable
{
    @Binding var mapType: MKMapType

    func makeUIView(context: Context) -> MKMapView
    {
        return MKMapView(frame: .zero)
    }

    func updateUIView(_ view: MKMapView, context: Context)
    {
        //Set the map type
        view.mapType = mapType
    }
}

Another approach would be to mirror the map type to a @State property in your MapHome

struct MapHome: View
{
    @EnvironmentObject var userData: UserData
    @State var mapType: MKMapType

    NavigationView
    {
        ZStack
        {
            MapView(mapViewState: $mapType)
                    .edgesIgnoringSafeArea(.all)            
        }
        Button(action: {
                    switch self.userData.configure.mapType
                    {
                        case .hybrid:
                            self.userData.configure.mapType = .standard
                        case .standard:
                            self.userData.configure.mapType = .satellite
                        case .satellite:
                            self.userData.configure.mapType = .hybrid
                        default:
                            self.userData.configure.mapType = .standard
                    }
                    self.mapType = self.userdata.configure.mapType
                }
        )
        {
            if self.userdata.configure.mapType == .hybrid
            {
                Image("HybridIcon")
            }
            else if self.userdata.configure.mapType == .standard
            {
                Image("StandardIcon")
            }
            else if self.userdata.configure.mapType == .satellite
            {
                Image("SatelliteIcon")
            }
        }

    }
}

You will need to provide the initial value for your map type in the SceneDelegate

 let userData = UserData()
 let contentView = MapHome(mapType: userData.configure.mapType).environmentObject(userData)
Paulw11
  • 108,386
  • 14
  • 159
  • 186
-1

Where do you assign your environment object?

Typically, you would do it like this:

MapHome().environmentObject(UserData())
Yevgeniy Leychenko
  • 1,287
  • 11
  • 25