Location services enable apps to access user location for features like navigation, weather, and local search. Learn Core Location and MapKit.
Core Location Setup
Permission Request
swift
import CoreLocation
import SwiftUI
class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
private let locationManager = CLLocationManager()
@Published var location: CLLocation?
@Published var authorizationStatus: CLAuthorizationStatus = .notDetermined
@Published var errorMessage: String?
override init() {
super.init()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
}
func requestAuthorization() {
locationManager.requestWhenInUseAuthorization()
}
func requestAlwaysAuthorization() {
locationManager.requestAlwaysAuthorization()
}
func startUpdating() {
locationManager.startUpdatingLocation()
}
func stopUpdating() {
locationManager.stopUpdatingLocation()
}
}Location Manager Delegate
swift
extension LocationManager {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
DispatchQueue.main.async {
self.location = location
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
DispatchQueue.main.async {
self.errorMessage = error.localizedDescription
}
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
DispatchQueue.main.async {
self.authorizationStatus = status
}
}
}MapKit Integration
Basic Map View
swift
import MapKit
import SwiftUI
struct BasicMapView: View {
@StateObject private var locationManager = LocationManager()
var body: some View {
VStack {
Map(coordinateRegion: $locationManager.region)
.onAppear {
locationManager.requestAuthorization()
locationManager.startUpdating()
}
}
}
}
extension LocationManager {
@Published var region = MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194),
span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
)
func updateRegion(for location: CLLocation) {
region = MKCoordinateRegion(
center: location.coordinate,
span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
)
}
}Custom Annotations
swift
struct Place: Identifiable {
let id = UUID()
let name: String
let coordinate: CLLocationCoordinate2D
}
struct MapWithAnnotations: View {
@State private var region = MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194),
span: MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1)
)
let places = [
Place(name: "Golden Gate Bridge", coordinate: CLLocationCoordinate2D(latitude: 37.8199, longitude: -122.4783)),
Place(name: "Fisherman's Wharf", coordinate: CLLocationCoordinate2D(latitude: 37.8087, longitude: -122.4177)),
Place(name: "Alcatraz Island", coordinate: CLLocationCoordinate2D(latitude: 37.8267, longitude: -122.4233))
]
var body: some View {
Map(coordinateRegion: $region, annotationItems: places) { place in
MapMarker(coordinate: place.coordinate, tint: .blue)
}
}
}Advanced Map Features
Custom Annotation Views
swift
struct CustomAnnotationView: View {
let place: Place
var body: some View {
VStack {
Image(systemName: "mappin.circle.fill")
.font(.title)
.foregroundColor(.red)
Text(place.name)
.font(.caption)
.padding(4)
.background(Color.white)
.cornerRadius(4)
}
}
}
struct MapWithCustomAnnotations: View {
@State private var region = MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194),
span: MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1)
)
let places = [
Place(name: "Location 1", coordinate: CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194)),
Place(name: "Location 2", coordinate: CLLocationCoordinate2D(latitude: 37.7849, longitude: -122.4094))
]
var body: some View {
Map(coordinateRegion: $region, annotationItems: places) { place in
MapAnnotation(coordinate: place.coordinate) {
CustomAnnotationView(place: place)
}
}
}
}User Tracking
swift
struct UserTrackingView: View {
@StateObject private var locationManager = LocationManager()
@State private var trackingMode: MapUserTrackingMode = .follow
var body: some View {
Map(
coordinateRegion: $locationManager.region,
showsUserLocation: true,
userTrackingMode: $trackingMode
)
.onAppear {
locationManager.requestAuthorization()
locationManager.startUpdating()
}
}
}Geocoding
Forward Geocoding
swift
class Geocoder: ObservableObject {
@Published var location: CLLocation?
func geocode(address: String) async {
let geocoder = CLGeocoder()
do {
let placemarks = try await geocoder.geocodeAddressString(address)
if let location = placemarks.first?.location {
DispatchQueue.main.async {
self.location = location
}
}
} catch {
print("Geocoding error: \(error)")
}
}
}Reverse Geocoding
swift
extension Geocoder {
func reverseGeocode(location: CLLocation) async -> String? {
let geocoder = CLGeocoder()
do {
let placemarks = try await geocoder.reverseGeocodeLocation(location)
return placemarks.first?.locality
} catch {
print("Reverse geocoding error: \(error)")
return nil
}
}
}Geofencing
Create Geofence
swift
class GeofenceManager: NSObject, ObservableObject, CLLocationManagerDelegate {
private let locationManager = CLLocationManager()
@Published var enteredRegion: String?
override init() {
super.init()
locationManager.delegate = self
}
func startMonitoring(region: CLRegion) {
locationManager.startMonitoring(for: region)
}
func createGeofence(
at coordinate: CLLocationCoordinate2D,
radius: CLLocationDistance,
identifier: String
) -> CLCircularRegion {
let region = CLCircularRegion(
center: coordinate,
radius: radius,
identifier: identifier
)
region.notifyOnEntry = true
region.notifyOnExit = true
return region
}
}Geofence Delegate
swift
extension GeofenceManager {
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
DispatchQueue.main.async {
self.enteredRegion = "Entered \(region.identifier)"
}
// Schedule local notification
sendNotification(for: region, entered: true)
}
func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
DispatchQueue.main.async {
self.enteredRegion = "Exited \(region.identifier)"
}
sendNotification(for: region, entered: false)
}
private func sendNotification(for region: CLRegion, entered: Bool) {
let content = UNMutableNotificationContent()
content.title = entered ? "Location Entered" : "Location Exited"
content.body = "You have \(entered ? "entered" : "exited") \(region.identifier)"
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
let request = UNNotificationRequest(
identifier: UUID().uuidString,
content: content,
trigger: trigger
)
UNUserNotificationCenter.current().add(request)
}
}Distance Calculation
Calculate Distance
swift
extension LocationManager {
func calculateDistance(from: CLLocation, to: CLLocation) -> CLLocationDistance {
from.distance(from: to)
}
func calculateDistanceToDestination(
from: CLLocationCoordinate2D,
to: CLLocationCoordinate2D
) -> CLLocationDistance {
let fromLocation = CLLocation(latitude: from.latitude, longitude: from.longitude)
let toLocation = CLLocation(latitude: to.latitude, longitude: to.longitude)
return fromLocation.distance(from: toLocation)
}
}Background Location Updates
Background Updates
swift
extension LocationManager {
func startBackgroundUpdates() {
locationManager.allowsBackgroundLocationUpdates = true
locationManager.pausesLocationUpdatesAutomatically = false
locationManager.startUpdatingLocation()
}
func stopBackgroundUpdates() {
locationManager.allowsBackgroundLocationUpdates = false
locationManager.stopUpdatingLocation()
}
}Best Practices
- 1Privacy: Always respect user privacy
- 2Permissions: Request permissions appropriately
- 3Battery: Optimize for battery life
- 4Accuracy: Use appropriate accuracy levels
- 5Background: Handle background location updates carefully
- 6Testing: Test location features thoroughly
- 7Fallback: Provide fallback for denied permissions
- 8UX: Show user location on maps
Location services enable powerful location-based features in your apps!