Fetching current location in iOS 14 Widget

Viewed 3055

Has anyone tried to update user's location in iOS 14 Widget? After reading Apple Developer forums I've come up with the writing wrapper around CLLocationManager and using it this way:

class WidgetLocationManager: NSObject, CLLocationManagerDelegate {
    var locationManager: CLLocationManager? {
        didSet {
            self.locationManager!.delegate = self
        }
    }
    private var handler: ((CLLocation) -> Void)?
    
    func fetchLocation(handler: @escaping (CLLocation) -> Void) {
        self.handler = handler
        self.locationManager!.requestLocation()
    }

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        self.handler!(locations.last!)
    }
    
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        print(error)
    }
}

And using it this way:

var widgetLocationManager = WidgetLocationManager()
    func getTimeline(for configuration: SelectPlaceIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> Void) {
        if widgetLocationManager.locationManager == nil {
            widgetLocationManager.locationManager = CLLocationManager()
            widgetLocationManager.locationManager!.requestWhenInUseAuthorization()
        }
        widgetLocationManager.fetchLocation(handler: { location in
            print(location)
            .......
        })
    }

I also have these 2 entries in Widget's info.plist:

<key>NSLocationUsageDescription</key>
<string>1</string>

<key>NSWidgetWantsLocation</key>
<true/>

When locationManager.requestLocation() is being called, authorisation status is authorisedWhenInUse, but delegate's method is never being called. What am I missing?

1 Answers

First of all, the obvious problem that I see:

<key>NSLocationUsageDescription</key>
<string>1</string>

NSLocationUsageDescription is deprecated: Apple Documentation , so you should be using NSLocationWhenInUseUsageDescription or NSLocationAlwaysAndWhenInUseUsageDescription instead. Be sure to include the permission that you choose in main apps Info.plist as well

Additionally, creating CLLocationManager in

func getTimeline(for configuration: SelectPlaceIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> Void) {
    ...
}

might be problematic, since it can get called from background thread, so I would refactor your WidgetLocationManager like this:

class WidgetLocationManager: NSObject, CLLocationManagerDelegate {
    var locationManager: CLLocationManager? 
    private var handler: ((CLLocation) -> Void)?

    override init() {
        super.init()
        DispatchQueue.main.async {
            self.locationManager = CLLocationManager()
            self.locationManager!.delegate = self
            if self.locationManager!.authorizationStatus == .notDetermined {
                self.locationManager!.requestWhenInUseAuthorization()
            }
        }
    }
    
    func fetchLocation(handler: @escaping (CLLocation) -> Void) {
        self.handler = handler
        self.locationManager!.requestLocation()
    }

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        self.handler!(locations.last!)
    }
    
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        print(error)
    }
}

and later use it like this:

var widgetLocationManager = WidgetLocationManager()

func getTimeline(for configuration: SelectPlaceIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> Void) {
    widgetLocationManager.fetchLocation(handler: { location in
        print(location)
        .......
    })
}
Related