0

I know there are a ton of similar threads, however after three weeks I cannot get this code working. Perhaps Stack Overflow can see an obvious error.

Source of truth comes from this ObservableObject class where a locationManager function updates the @Published variable according to the current geoFence.

class MapViewModel : NSObject, ObservableObject, CLLocationManagerDelegate, MKMapViewDelegate {
    
    @Published var sceneAccordingToMap = 0
    
      func var1toggle() {
          sceneAccordingToMap = 1
      }
      
      func var2toggle() {
          sceneAccordingToMap = 2
          
      }

      func var3toggle() {
          sceneAccordingToMap = 3
      }

    // Bunch of Irrelevant GeoFencing Code

           *
           *
           *
           

 
    // enter safe area (region)

    func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
        print("Entered: \(region.identifier) ")

        if (region.identifier == "SafeArea"){
            print(sceneAccordingToMap)
            var1toggle()
            print(sceneAccordingToMap)
        }

        else if (region.identifier == "SafeArea2"){
            print(sceneAccordingToMap)
            var2toggle()
            print(sceneAccordingToMap)
        }
    }
    
    // exiting safe area (region)
    func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
        print("Left: \(region.identifier) ")
        if (region.identifier == "SafeArea"){
            print(sceneAccordingToMap)
            var1toggle()
            print(sceneAccordingToMap)
        }
        
        else if (region.identifier == "SafeArea2"){
            print(sceneAccordingToMap)
            var2toggle()
            print(sceneAccordingToMap)
        }
    }  
}

Now when passing this @Published variable to a View containing a @StateObject variable, the data is initially passed, however it fails to update when changed by the functions the MapViewModel. The variables are changing, just not in the View


struct ContentView: View {
    
    @StateObject var dataFromMap = MapViewModel()
    
    var body: some View {
        VStack {
            
            MapView().ignoresSafeArea(.all)
            Text("\(dataFromMap.sceneAccordingToMap)")
                .padding()

        }

        .environmentObject(MapViewModel())
// Changed to 
        .environmentObject(dataFromMap)
        
    }
}

Full code of MapView Model:

import CoreLocation
import MapKit
import SwiftUI
import RealityKit
import Combine



// map defaults
enum mapDefaults {
    static let initialLocation = CLLocationCoordinate2D(latitude: -37.81785423438109, longitude:  144.97973738630145)
    static let initialSpan = MKCoordinateSpan(latitudeDelta:0.01, longitudeDelta:0.01)
}

class MapViewModel : NSObject, ObservableObject, CLLocationManagerDelegate, MKMapViewDelegate {
    
    @Published var sceneAccordingToMap = 0
    
      func var1toggle() {
          sceneAccordingToMap = 1
      }
      
      func var2toggle() {
          sceneAccordingToMap = 2
          
      }
      
      func var3toggle() {
          sceneAccordingToMap = 3
      }

    
    
    @Published private(set) var authorizationStatus: UNAuthorizationStatus?
    
    @Published var region: MKCoordinateRegion = MKCoordinateRegion(
        center: mapDefaults.initialLocation,
        span: mapDefaults.initialSpan)
    
    var userLatitude = CLLocationCoordinate2D().latitude
    var userLongitude = CLLocationCoordinate2D().longitude
    
    var initialCoordinate : CLLocationCoordinate2D?
    var movedCoordinate : CLLocationCoordinate2D?
    
    @Published var distanceInMeter : Double = 0.0
    

    
    let locations = [
       Location(name: "Safearea", coordinate: CLLocationCoordinate2D(latitude: 37.331702, longitude:  -122.030785)),
       Location(name: "SafeArea2", coordinate: CLLocationCoordinate2D(latitude: 37.330709, longitude:  -122.030459))

    
    // Create Geofence Region with 10 meter radius
    let geofenceRegion = CLCircularRegion(center:CLLocationCoordinate2D(latitude: 37.331702, longitude:  -122.030785),
                                          radius: 15,
                                          identifier: "SafeArea")
    
    let geofenceRegion2 = CLCircularRegion(center:  CLLocationCoordinate2D(latitude: 37.330709, longitude:  -122.030459),
                                          radius: 15,
                                          identifier: "SafeArea2")



    
    var circle = MKCircle(center: mapDefaults.initialLocation, radius: 10)
    
    var locationMangager: CLLocationManager?
    
    
    // check if location permission is enabled or not
    func checkLocationEnabled() {
        if CLLocationManager.locationServicesEnabled() {
            locationMangager = CLLocationManager()
            locationMangager!.delegate = self
            locationMangager?.startUpdatingLocation()
            locationMangager?.startMonitoring(for: geofenceRegion)
            locationMangager?.startMonitoring(for: geofenceRegion2)
            print("Location permission enabled")
        }
        else{
            print("Location permission not enabled")
        }
    }
    // Check if user authorized location
    private func checkLocationAuth() {
        guard let locationMangager = locationMangager else {
            return
        }
        
        switch locationMangager.authorizationStatus{
            
        case .notDetermined:
            locationMangager.requestWhenInUseAuthorization()
        case .restricted:
            print("User location access is restricted")
        case .denied:
            print("User denied location access")
        case .authorizedAlways, .authorizedWhenInUse:
            region = MKCoordinateRegion(center: locationMangager.location!.coordinate, span: mapDefaults.initialSpan)
        @unknown default:
            break
        }
    }
    
    // Check if user changed location after first time
    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
        //check for location authorization
        checkLocationAuth()
        
        // request for notification access
        requestAuthorization()
    }
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        
        let location = locations.last
        
        userLatitude = location!.coordinate.latitude
        userLongitude = location!.coordinate.longitude
        
        // Starting position
        initialCoordinate = CLLocationCoordinate2D(latitude: userLatitude , longitude: userLongitude)
        // Updated position
        movedCoordinate = CLLocationCoordinate2D(latitude: location!.coordinate.latitude , longitude: location!.coordinate.longitude)
        self.distanceInMeter = Double(movedCoordinate!.distance(to: initialCoordinate!))
        print(distanceInMeter)
    }
  
    
    // enter safe area (region)
    func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
        print("Entered: \(region.identifier) ")

        if (region.identifier == "SafeArea"){
            print("we in pew pew pew pew")
            print(sceneAccordingToMap)
            var1toggle()
            print(sceneAccordingToMap)

        }

        else if (region.identifier == "SafeArea2"){
            print("we in doof doof doof")
            
            print(sceneAccordingToMap)
            
            var2toggle()
            print(sceneAccordingToMap)


        }
    }
    
    // exiting safe area (region)
    func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
        print("Left: \(region.identifier) ")
        if (region.identifier == "SafeArea"){
            print("we outta pew pew pew pew")
            print(sceneAccordingToMap)
            var1toggle()
            print(sceneAccordingToMap)


        }
        
        else if (region.identifier == "SafeArea2"){
            print("we outta doof doof doof")
            print(sceneAccordingToMap)
            var2toggle()
            print(sceneAccordingToMap)


        }
    }

    // request for notification
    func requestAuthorization() {
        UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { isGranted, _ in
            DispatchQueue.main.async {
                self.authorizationStatus = isGranted ? .authorized : .denied
            }
        }
    }
    
    // create user notification
    func triggerLocalNotification(subTitle: String, body: String){
        // configure notification content
        let content = UNMutableNotificationContent()
        content.title = "Alert!"
        content.subtitle = subTitle
        content.body = body
        content.sound = UNNotificationSound.default
        
        // setup trigger
        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
        
        // create request
        let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
        
        // add notification request
        UNUserNotificationCenter.current().add(request)
    }
    
    func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
         if let tileOverlay = overlay as? MKTileOverlay {
           let renderer = MKTileOverlayRenderer(overlay: tileOverlay)
           return renderer
         }
         return MKOverlayRenderer()
    }
}
   
   
// return distance in meters
extension CLLocationCoordinate2D {
    
    /// Returns the distance between two coordinates in meters.
    func distance(to: CLLocationCoordinate2D) -> CLLocationDistance {
        MKMapPoint(self).distance(to: MKMapPoint(to))
    }
    
}

  • 1
    You have two instances of `MapViewModel` – jnpdx Oct 08 '22 at 19:00
  • Hi @jnpdx, thanks for pointing it out. Where is the second instance? Do you mean I should pass the dataFromMap instance instead of MapViewModel() in the .environmentObject() modifier? I tried that with no success. Excuse me please, I'm still learning. – Theuns Botha Oct 08 '22 at 19:36
  • Yes, you should only create MapViewModel once. Right now, you create it on the `@StateObject` declaration and on the `environmentObject()`. Beyond this, there's really not enough to represent the problem. Where does the location manager get started? Have you used the debugger to see if the callback functions are called? – jnpdx Oct 08 '22 at 19:42
  • Thank you @jnpdx , makes a lot of sense. I added the entire MapViewModel for you to see. I used print statements to debug. According to these the Published variable gets correctly updated each time a geofence barrier is crossed. – Theuns Botha Oct 08 '22 at 20:32
  • What happens if you surround any call of `sceneAccordingToMap =` with `Task { @MainActor in ... }` – jnpdx Oct 08 '22 at 20:44
  • Changed the func's inside MapViewModel to `Task { @MainActor in sceneAccordingToMap = 1 }` Still no update on ContentView() however. I really appreciate all the advice @jnpdx – Theuns Botha Oct 08 '22 at 20:55
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/248664/discussion-between-theuns-botha-and-jnpdx). – Theuns Botha Oct 08 '22 at 22:30

0 Answers0