Home > Mobile >  Trying to create LocationManger, but the MKCoordinateRegion doesn't seem to be returning
Trying to create LocationManger, but the MKCoordinateRegion doesn't seem to be returning

Time:12-17

I am trying to create a LocationManager class that my ViewModel will use in a view. I want to be able to use the checkIfLocationServicesIsEnabled() function in my ViewModel to be able to get the MKCoordinateRegion and initialize it to the @Published var region variable in my ViewModel. However, the checkIfLocationServicesIsEnabled() function in my LocationManager seems to only return a nil value. I am struggling to understand why this is the case.

My code:

LocationManager

import CoreLocation
import MapKit
import SwiftUI

final class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
    
    static let shared = LocationManager()
    private override init() {}
    
    // Optional because user can have location services in phone turned off.
    var locationManager: CLLocationManager?
    
    var region: MKCoordinateRegion?
    
    func checkIfLocationServicesIsEnabled() -> MKCoordinateRegion? {
        if CLLocationManager.locationServicesEnabled() {
            self.locationManager = CLLocationManager()
            locationManager!.delegate = self
            return region
        } else {
            print("Show alert letting the user know this is off and to go turn it on.")
            return nil
        }
    }

    private func checkLocationAuthorization() {
        guard let locationManager else { return }
        
        switch locationManager.authorizationStatus {
        case .notDetermined:
            locationManager.requestAlwaysAuthorization()
        case .restricted:
            print("Parental controls prevent us from using your location.")
        case .denied:
            print("You have denied location permission.")
        case .authorizedAlways, .authorizedWhenInUse:
            region = MKCoordinateRegion(center: locationManager.location!.coordinate,
                                        span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05))
        @unknown default:
            break
        }
    }
    
    // Called when locationManager is created, or when authorization is changed.
    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
        checkLocationAuthorization()
    }
}

ViewModel

import CoreLocation
import MapKit
import SwiftUI

final class ViewModel: ObservableObject {    
    @Published var isSettingLocationManually = false
    @Published var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 25, longitude: 46),
                                               span: MKCoordinateSpan(latitudeDelta: 30, longitudeDelta: 30))
    
    
    func checkIfLocationServicesIsEnabled() {
        if let region = LocationManager.shared.checkIfLocationServicesIsEnabled() {
            self.region = region
            print("success")
        }
        print("did it work?")
    }
}

The function returns a nil value. How can I get it to return an MKCoordinateRegion? Any help would be greatly appreciated.

CodePudding user response:

You've got a couple problems here. You shouldn't be using ! at all. You need to tell the location manager that you want it to start tracking. Even if you did tell it to start tracking

        if CLLocationManager.locationServicesEnabled() {
            self.locationManager = CLLocationManager()
            locationManager!.delegate = self
            return region
        }

At this time it would not have a region, because it wouldn't have had the chance to start tracking yet.

I just implemented something very similar in one of my apps, dig through this and you can see how it all comes together.

//
//  ContentView.swift
//
//  Created by Andrew Carter on 12/15/22.
//

import SwiftUI
import CoreLocation
import MapKit

// Don't forget to add NSLocationWhenInUseUsageDescription to your info plist!

class LocationManager: NSObject, CLLocationManagerDelegate {
 
    private let manager = CLLocationManager()
    private var continuations: [CheckedContinuation<MKCoordinateRegion, Never>] = []
    
    override init() {
        super.init()
        
        manager.delegate = self
        manager.desiredAccuracy = kCLLocationAccuracyBest
        manager.requestWhenInUseAuthorization()
    }
    
    func fetchLocation() async -> MKCoordinateRegion {
        manager.startUpdatingLocation()

        return await withCheckedContinuation { continuation in
            self.continuations.append(continuation)
        }
    }
    
    // MARK: - CLLocationManagerDelegate
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let location = locations.last else {
            return
        }
        
        continuations.forEach { continuation in
            let region = MKCoordinateRegion(center: location.coordinate,
                                            span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05))
            continuation.resume(returning: region)
        }
        
        continuations.removeAll()
        manager.stopUpdatingLocation()
    }
    
}

@MainActor
class MyLocationProvider: ObservableObject {
    
    @Published var region: MKCoordinateRegion?
    private let locationManager = LocationManager()
    
    init() {
        Task {
            self.region = await locationManager.fetchLocation()
        }
    }
    
}



struct ContentView: View {
    
    @StateObject var locationProvider = MyLocationProvider()
    
    var body: some View {
        if let region = locationProvider.region {
            Text("Region is "   String(describing: region))
        } else {
            Text("Fetching region...")
        }
    }
    
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

We can use async await to make a nicer api. You can even constantly publish updates if you wanted to instead of fetching the location just once. Also note that you need NSLocationWhenInUseUsageDescription defined in your bundle's info plist!

enter image description here

  • Related