Fetching current location in iOS 14 Widget
Asked Answered
U

2

10

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?

Unclear answered 11/9, 2020 at 9:40 Comment(0)
S
10

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)
        .......
    })
}
Scamander answered 12/9, 2020 at 12:41 Comment(10)
Unfortunately this does not help.Unclear
So the problem remains the same that didUpdateLocations is not called ? have you checked if didFailWithError is called, if yes, what is the error there? Are you testing on Device or on Simulator?Scamander
Testing on device. Sometimes I get KCLErrorDomain error 0, but as far, as I know, it can be safely ignored. This error appears when Allow Location Simulation is on in scheme's options, but default location is not set.Unclear
I tried this, and it sort of works, and it's good for getting the current location at that time. But how do you track the locations changes, such that it automatically refreshes the widget? What kind of timeline refresh policy should be used? Do you just keep refreshing every few minutes and checking current location again and again?Probity
@ZS No, that would be a bad idea to constantly refresh widget for location, you should be managing location updates in main app, with startUpdatingLocation and in didUpdateLocations, saving previous update location and comparing to new one. If distance between the 2 locations is greater than what you consider significant location change, call WidgetCenter.shared.reloadTimelines(ofKind:) to refresh widget. If you want this to happen in background, than you should also use background location updates and use startMonitoringSignificantLocationChanges when app goes to backgroundScamander
Thanks; that works. One more question: I have the 'NSWidgetWantsLocation' key in my widget's Info.plist. But this prompts the app to launch the locations permissions prompt as soon as the app launches the first time, and every time unless I respond with an answer. Is this expected? It makes for a poor user experience.Probity
I am experiencing the similar thing, even after I delete the app, after reinstall as soon as I start the app it prompts for location permission. If I delete the app, restart the device and then run, location permission is NOT asked. I would say, there is some kind of a permission clearing problem, but can not say 100%.Scamander
For the widget, would I need NSLocationWhenInUseUsageDescription or NSLocationAlwaysAndWhenInUseUsageDescription? The former seems not applicable since a widget is always "in use", but the documentation offers no clue.Stove
@Mr.Zystem I currently use NSLocationWhenInUseUsageDescription in my app, it is used for the main app and not the widget. if you use NSLocationAlwaysAndWhenInUseUsageDescription it doesn't mean that widget will also be updating location non stop.Scamander
NSWidgetWantsLocation is important to remember. You set in in the widgetExtensions info.plist, set it to true. This will also prompt the user whenever they add the widget to the Home Screen.Sufferance
Q
1

Make sure you have both these plist values set in your widgets Info.plist file:

<key>NSWidgetWantsLocation</key>
<true/>
<key>NSLocationUsageDescription</key>
<string>Put some text here</string>
Quenna answered 9/7, 2023 at 8:33 Comment(2)
I would add the reasoning why these plist values would be applicable in this case.Mhd
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Barnacle

© 2022 - 2024 — McMap. All rights reserved.