Request locations using CLLocationManager in Widget target on iOS 14

Xcode logs an error to console stating that the Location usage descriptions are not available in Widget’s Info.plist although they’re there.
Isn’t it possible to use CLLocationManager in WidgetKit world?

Accepted Reply

I have CLLocationManager working inside a widget. I had to add these two values to the widgets info.plist file:

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

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

Replies

I have CLLocationManager working inside a widget. I had to add these two values to the widgets info.plist file:

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

<key>NSWidgetWantsLocation</key>
<true/>
@markdaws Can you help us out with CLLocationManager inside the widget ? I can't seem to get it to call delegate methods.
What I've seen in beta 3 is:
  • if I initiate the location manager in the TimelineProvider struct I get an error message in console saying that location manager should be allocated in the main thread not bg thread

  • if I initiate it in the View struct of the widget I can see that CLLocationManager is initialised correctly, authStatus code is: "when in use" but still I can't seem to receive the location callbacks

What is you experience like ?
I'm not sure of the best way to use CLLocationManager inside WidgetKit, it would be nice to get a sample from Apple around it showing exactly how we should do it.

To fix the main thread issue, what I did was create a wrapper class that I instantiate in the widget struct that will hold a reference to the CLLocationManager, then on the call to timeline() I create the CLLocationManager instance at that time and store a reference to it in the wrapper, this means it is created on the right thread. If you don't store a reference to this class that lives beyond the call to timeline() the callbacks are never called.

Then, instead of streaming updates from the CLLocationManager instance, I use CLLocationManager requestLocation() to get a one time callback.

Something like this:

LocationWrapper.swift
Code Block swift
final class LocationWrapper {
  var location: CLLocationManager?
func fetchLocation(completion: @escaping (CLLocation) -> Void) {
// call requestLocation(), hook up to callbacks etc.
// call completion once you a
}
}



widget_main.swift
Code Block swift
struct Provider: TimelineProvider {
  var locationWrapper = LocationWrapper()
  public func timeline(with context: Context, completion: @escaping (Timeline<SimpleEntry>) -> ()) {
    if locationManager.location == nil {
      locationManager.location = CLLocationManager()
    }
locationManager.fetchLocation { location in
// call completion, update ui etc.
}
}




typo in widget_main.swift

Code Block struct Provider: TimelineProvider {
    var locationWrapper = LocationWrapper()
    public func timeline(with context: Context, completion: @escaping (Timeline<SimpleEntry>) -> ()) {
        if locationWrapper.location == nil {
            locationManager.location = CLLocationManager()
        }
        locationWrapper.fetchLocation { location in
            // call completion, update ui etc.
        }
}


Hello everyone!
Has anyone managed to get it working?

I have two entries in Widget's info.plist as @markdaws mentioned.
My location manager wrapper looks this way:
Code Block swift
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 getTimeline function this way:
Code Block swift
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)
            ........
        })
    }


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