I have this silly issue where I have been debugging for a whole day:
I implemented LocationManager class to retrieve user's coordinate, it worked last night. Then suddenly it stopped working today, I ran locationManager.startUpdatingLocation()
but locationManager
delegates never got fired, no result nor error. The class is a singleton so it stayed there without being destroyed, I checked locationManager.delegate
and it refers my class LocationManager
(not nil)
I have read almost every post about CLLocationManager and I'm desperated.
Here is my Info.plist:
<key>NSWidgetUsesLocation</key>
<true/>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Enable Weather for Power Users</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>Enable Weather for Power Users</string>
and LocationManager.swift
import Foundation
import CoreLocation
// Usage
// LocationManager.Instance.getCoordinateString(() { coordinatesString
// print("Current coordinates string: \(coordinatesString!)")
//)
class LocationManager: NSObject, CLLocationManagerDelegate {
static let Instance = LocationManager()
private var locationManager = CLLocationManager()
private var authorizeCompletion: (() -> Void)?
private var updateCompletion: ((_ coordinateString: String?) -> Void)?
private override init() {
super.init()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
}
public func requestPermission(completion: @escaping () -> Void) {
if status != .notDetermined {
completion()
return
}
authorizeCompletion = completion
locationManager.requestAlwaysAuthorization()
}
public var status: CLAuthorizationStatus {
get {
return locationManager.authorizationStatus
}
}
func getCoordinateString(completion: @escaping (_ coordinateString: String?) -> Void) {
print("LocationManager.getCoordinateString() \(status)", locationManager.delegate)
if status == .denied || status == .restricted {
// print("LocationManager.locationManager(): ERROR Permission:", status)
completion(nil)
return
}
if status == .notDetermined {
completion(nil) // Temporary return basic weather
requestPermission(completion: { [self] in
updateCompletion = completion
locationManager.startUpdatingLocation()
// locationManager.requestLocation()
})
return
}
updateCompletion = completion
locationManager.startUpdatingLocation()
}
// MARK: CLLocationManagerDelegate
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
print("LocationManager.locationManager(): Authorization completed", status)
authorizeCompletion?()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print("LocationManager.locationManager(): didUpdateLocations")
guard let location = locations.last else {
print("LocationManager.locationManager(): ERROR No coodinate found")
updateCompletion?(nil)
updateCompletion = nil
return
}
// Stop updating location after the first result
locationManager.stopUpdatingLocation()
let latitude = String(format: "%.4f", location.coordinate.latitude)
let longitude = String(format: "%.4f", location.coordinate.longitude)
let result = "\(latitude),\(longitude)"
// Call completion handler if it's set
updateCompletion?(result)
updateCompletion = nil
print("LocationManager.locationManager(): Coordinate \(result)")
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("LocationManager.locationManager(): ERROR \(error.localizedDescription)")
updateCompletion?(nil)
updateCompletion = nil
locationManager.stopUpdatingLocation()
}
}
I have tested:
So far I have tried to create a clean project and put my LocationManager.swift and it's working perfectly!? on iOS simulator
Another funny note that even without Info.plist keys being added, the "clean project" I tested at least returns error, but not in my real project.
Here is my usage in "clean project" and it's working:
class GameScene: SKScene {
override func didMove(to view: SKView) {
LocationManager.Instance.requestPermission {
LocationManager.Instance.getCoordinateString(completion: {coordinateString in
print("GameScene", coordinateString)
})
}
}
}
Ok I finally found the issue after 12hours of debugging.
Calling location in on startup immediately does not work, instead I wait for a second then it's working. I guess delegates was not assigned on time?
DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: {
LocationManager.Instance.getCoordinateString(completion: {coordinateString in
print("GameScene", coordinateString)
})
})