Skip to content
All essays
iOSMarch 27, 202514 min

Swift Location Services: GPS and Mapping

Master location services in iOS apps. Core Location, MapKit, and geofencing.

Ü
Ümit Uz
Mobile & Full Stack Developer

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

  1. 1Privacy: Always respect user privacy
  2. 2Permissions: Request permissions appropriately
  3. 3Battery: Optimize for battery life
  4. 4Accuracy: Use appropriate accuracy levels
  5. 5Background: Handle background location updates carefully
  6. 6Testing: Test location features thoroughly
  7. 7Fallback: Provide fallback for denied permissions
  8. 8UX: Show user location on maps

Location services enable powerful location-based features in your apps!

Related essays

Next essay
Core ML Complete Guide: Machine Learning in iOS