NSLocationWhenInUseUsageDescription key to your Info.plist, keep a strong reference to CLLocationManager, set its delegate, and call requestWhenInUseAuthorization(). Once authorized, call requestLocation() for a single update or startUpdatingLocation() for continuous updates, then read coordinates in locationManager(_:didUpdateLocations:).Why this happens
Accessing device coordinates requires explicit privacy authorization and an active system lifecycle listener. Developers frequently encounter silent failures where delegate methods never fire, coordinates remain nil, or the app crashes immediately upon startup.
First, iOS strictly requires a privacy rationale string in your application target’s configuration. If your app requests location authorization without an explanation key defined in Info.plist, the operating system terminates the process instantly without presenting an alert.
Second, CLLocationManager instances must be held with a strong reference. Instantiating a location manager inside a local function scope causes ARC (Automatic Reference Counting) to deallocate the manager before it can process hardware callbacks or present authorization dialogs. In addition, calling requestLocation() without implementing the mandatory failure delegate method causes an unhandled runtime error.
How to fix it
1. Add privacy usage descriptions to Info.plist
Before executing any Swift code, configure your project’s property list to describe why your application requires location access.
- Select your project in the Xcode Project Navigator.
- Choose your app target and open the Info tab (or open
Info.plistdirectly). - Add the key Privacy – Location When In Use Usage Description (
NSLocationWhenInUseUsageDescription). - Set a clear, user-facing string value explaining why the app needs access, such as:
"We need your location to display nearby services on the map."
Avoid requesting
NSLocationAlwaysAndWhenInUseUsageDescriptionunless your app delivers true background location tracking (e.g., turn-by-turn navigation). App Store Review will reject apps requesting background permissions without active background modes enabled.
2. Request a one-time coordinate with requestLocation()
If you only need to grab the user’s location once (for example, to center a map view or find nearby stores), use requestLocation() instead of continuous tracking. This saves battery and automatically powers down the GPS hardware once resolved.
Create a dedicated controller or manager class with a strong reference to CLLocationManager:
import UIKit
import CoreLocation
final class LocationService: NSObject, CLLocationManagerDelegate {
private let locationManager = CLLocationManager()
override init() {
super.init()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
}
func checkAuthorizationAndFetch() {
switch locationManager.authorizationStatus {
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
case .authorizedWhenInUse, .authorizedAlways:
locationManager.requestLocation()
case .denied, .restricted:
print("Location access denied by user or parental controls.")
@unknown default:
break
}
}
// Handle authorization changes (iOS 14+)
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
switch manager.authorizationStatus {
case .authorizedWhenInUse, .authorizedAlways:
manager.requestLocation()
case .denied, .restricted:
print("User declined authorization.")
default:
break
}
}
// Required: Handles successful location updates
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let latestLocation = locations.last else {
return
}
let coordinate = latestLocation.coordinate
print("Latitude: \(coordinate.latitude), Longitude: \(coordinate.longitude)")
}
// Required when using requestLocation(): Must be implemented to prevent runtime crash
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Failed to retrieve location: \(error.localizedDescription)")
}
}
3. Handle continuous coordinate updates
If your application tracks real-time movement, use startUpdatingLocation(). Ensure you set an appropriate desiredAccuracy and stop updates as soon as continuous tracking is no longer needed.
import CoreLocation
final class TrackingManager: NSObject, CLLocationManagerDelegate {
private let locationManager = CLLocationManager()
func startTracking() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = 10.0 // Updates every 10 meters moved
if locationManager.authorizationStatus == .notDetermined {
locationManager.requestWhenInUseAuthorization()
} else if locationManager.authorizationStatus == .authorizedWhenInUse {
locationManager.startUpdatingLocation()
}
}
func stopTracking() {
locationManager.stopUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
for location in locations {
// Filter out cached points that are older than 15 seconds
guard abs(location.timestamp.timeIntervalSinceNow) < 15 else { continue }
print("Continuous coordinate: \(location.coordinate.latitude), \(location.coordinate.longitude)")
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("Tracking failed: \(error.localizedDescription)")
}
}
4. Modern Swift async/await with CLLocationUpdate (iOS 17+)
In modern Swift codebases targeting iOS 17 or newer, you can bypass the traditional delegate pattern entirely by streaming locations using the async sequence CLLocationUpdate.liveUpdates():
import CoreLocation
@MainActor
final class AsyncLocationProvider {
func streamCoordinates() async {
let manager = CLLocationManager()
if manager.authorizationStatus == .notDetermined {
manager.requestWhenInUseAuthorization()
}
do {
let updates = CLLocationUpdate.liveUpdates()
for try await update in updates {
guard let location = update.location else { continue }
print("Async coordinate: \(location.coordinate.latitude), \(location.coordinate.longitude)")
// Break the loop if only one reading is required
break
}
} catch {
print("Location stream failed: \(error.localizedDescription)")
}
}
}
How to prevent it
- Store your
CLLocationManagerinstance as a strong property on a long-lived class (e.g., anObservableObject, service singleton, or view controller) rather than inside a transient function. - Always implement
locationManager(_:didFailWithError:)whenever you useCLLocationManagerDelegate. Core Location will trigger a runtime assertion failure ifrequestLocation()encounters an error and this method is missing. - Stop location updates using
stopUpdatingLocation()when your view disappears or tracking is complete to avoid heavy battery consumption. - Discard locations with negative horizontal accuracy (
location.horizontalAccuracy < 0), which signals an invalid GPS reading. - Test location callbacks on the iOS Simulator by going to Features > Location in the simulator menu bar and selecting Custom Location or City Run.
FAQ
Why does locationManager(_:didUpdateLocations:) never get called?
Check that your CLLocationManager is held by a strong class reference rather than a local variable that gets deallocated immediately. In addition, verify that the simulator or physical device has Location Services enabled and that you implemented locationManagerDidChangeAuthorization(_:) to trigger updates once permissions are granted.
What is the difference between requestLocation() and startUpdatingLocation()?
requestLocation() requests a single reading, delivers it to the delegate, and automatically turns off the location hardware. startUpdatingLocation() continuously monitors and delivers coordinates until you explicitly invoke stopUpdatingLocation().
Can I get location coordinates in the background?
Yes, but you must request requestAlwaysAuthorization(), configure the NSLocationAlwaysAndWhenInUseUsageDescription key in Info.plist, and enable the Location updates option under Signing & Capabilities > Background Modes in Xcode.
Parts of this guide are adapted from content licensed under CC BY-SA 4.0; this article is shared under the same license.
