Reputation: 1833
I just upgraded to Xcode 14.0 and when I run our app on iOS 16 devices, calls to:
CLLocationManager.locationServicesEnabled()
Are returning the warning:
This method can cause UI unresponsiveness if invoked on the main thread. Instead, consider waiting for the -locationManagerDidChangeAuthorization:
callback and checking authorizationStatus
first.
I'd need to make significant changes to my code if I have to wait for a failure/callback rather than just calling the CLLocationManager.locationServicesEnabled()
method directly. This only seems to happen on iOS 16 devices. Any suggests on how to address this?
Upvotes: 48
Views: 44018
Reputation: 159
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
DispatchQueue.global().async {
self.locationServicesEnabled = CLLocationManager.locationServicesEnabled()
print(manager.authorizationStatus)
}
}
This approach ensures the locationServicesEnabled variable is only updated when it might actually change. Typically, you don't need to worry about this, and you can simply use locationManager.authorizationStatus. However, if you need to guide the user to the appropriate settings link, you may need to use the locationServicesEnabled method.
Upvotes: 0
Reputation: 1902
Use a dispatch queue to move it off the main queue like so:
DispatchQueue.global().async {
if CLLocationManager.locationServicesEnabled() {
// your code here
}
}
EDIT 5/2/24 The above solves the immediate problem reported by the OP. However, as pointed out in the comments, may lead one to accept that anything may go on the global queue. A more refined solution, therefore, is to use a custom queue managed by your application such as:
let myQueue = DispatchQueue(label:"myOwnQueue")
myQueue.async {
if CLLocationManager.locationServicesEnabled() {
// your code here
}
}
Upvotes: 47
Reputation: 21
If you must to return a value for example from
func didTapMyLocationButton(for mapView: GMSMapView) -> Bool
you can run it in the DispatchQueue.global()
and await for execution with a DispatchSemaphore
like this:
private func hasLocationPermission() -> Bool {
var hasPermission = false
let manager = CLLocationManager()
let semaphore = DispatchSemaphore(value: 0)
DispatchQueue.global().async {
//This method can cause UI unresponsiveness if invoked on the main thread
if CLLocationManager.locationServicesEnabled() {
switch manager.authorizationStatus {
case .notDetermined, .restricted, .denied:
hasPermission = false
case .authorizedAlways, .authorizedWhenInUse:
hasPermission = true
@unknown default:
break
}
} else {
hasPermission = false
}
semaphore.signal()
}
semaphore.wait()
return hasPermission
}
then
func didTapMyLocationButton(for mapView: GMSMapView) -> Bool {
return hasLocationPermission()
}
Upvotes: 0
Reputation: 3031
Instead of calling locationServicesEnabled directly, wrap it in a thread safe manner:
extension CLLocationManager {
func locationServicesEnabledThreadSafe(completion: @escaping (Bool) -> Void) {
DispatchQueue.global().async {
let result = CLLocationManager.locationServicesEnabled()
DispatchQueue.main.async {
completion(result)
}
}
}
}
Usage example:
CLLocationManager().locationServicesEnabledThreadSafe { [weak self] areEnabled in
guard let self = self else {
return assertionFailure()
}
self.handleLocationServices(enabled: areEnabled)
}
Upvotes: 2
Reputation: 9
I'm quite new to Swift but with iOS 17 i've found this to work instead of checking locationServicesEnabled() you check the authorizationStatus:
func checkLocationServicesEnabled() {
self.locationManager = CLLocationManager() // initialise location manager if location services is enabled
self.locationManager!.delegate = self // force unwrap since created location manager on line above so not much of a way this can go wrong
self.locationManager?.requestAlwaysAuthorization()
self.locationManager?.desiredAccuracy = kCLLocationAccuracyBest
switch self.locationManager?.authorizationStatus { // check authorizationStatus instead of locationServicesEnabled()
case .notDetermined, .authorizedWhenInUse:
self.locationManager?.requestAlwaysAuthorization()
case .restricted, .denied:
print("ALERT: no location services access")
case .authorizedAlways:
break
case .none, .some(_):
break
}
}
Upvotes: -1
Reputation: 1
In my case I had to separate both main.async and global().async to check some options in the background.
Upvotes: -1
Reputation: 917
I faced the same issue and overcame it using Async/Await.
Wrap the CLLocationManager
call in an async function.
func locationServicesEnabled() async -> Bool {
CLLocationManager.locationServicesEnabled()
}
Then update the places where you use this function accordingly.
Task { [weak self] in
if await self?.locationServicesEnabled() {
// Do something
}
}
Upvotes: 8