swiftswiftuimapkitcore-location

Creating MapCameraPosition from CLLocationManager initialiser/self error when trying to tie them


Trying to use new Swift @Observable to monitor GPS position within SwiftUI content view. But how do I tie the latest locations to the SwiftUI Map's mapCameraPosition?

Well ideally the answer could cover:

  1. How to fix this error - So get map tracking along with the User Position, but also
  2. How to include facility to turn on/off the map moving to track the user position (which I'll need to do next). So could be tracking, then disable, move map around and have a look at things, then click button to start syncing the mapcameraposition to the GPS location again

Refer to error I'm embedded in the code below.

import SwiftUI
import MapKit

@Observable
final class NewLocationManager : NSObject, CLLocationManagerDelegate {
    var location: CLLocation? = nil
    private let locationManager = CLLocationManager()

    func startCurrentLocationUpdates() async throws {
        if locationManager.authorizationStatus == .notDetermined {
            locationManager.requestWhenInUseAuthorization()
        }
        for try await locationUpdate in CLLocationUpdate.liveUpdates() {
            guard let location = locationUpdate.location else { return }
            self.location = location
        }
    }
   
}

struct ContentView: View {
    var newlocationManager = NewLocationManager()
    
    @State private var cameraPosition: MapCameraPosition = .region(MKCoordinateRegion(
        center: newlocationManager.location?.coordinate ?? <#default value#>,
        span: MKCoordinateSpan(latitudeDelta: 0.25, longitudeDelta: 0.25)
    ))
    // GET ERROR: Cannot use instance member 'newlocationManager' within property initializer; property initializers run before 'self' is available

    var body: some View {
        ZStack {
            Map(position: $cameraPosition)
            Text("New location manager: \(newlocationManager.location?.description ?? "NIL" )") // works
        }
        .task {
            try? await newlocationManager.startCurrentLocationUpdates()
        }
        
    }
}

#Preview {
    ContentView()
}

Solution

  • As the error says, you cannot make use of newlocationManager before the initialization of the view is complete.

    Try using .onAppear{...} as shown in the example code.

    struct ContentView: View {
        @State var newlocationManager = NewLocationManager() // <--- here
        
        @State private var cameraPosition: MapCameraPosition = .region(MKCoordinateRegion(
            center: CLLocationCoordinate2D(latitude: 35.68, longitude: 139.75), // <--- here, adjust
            span: MKCoordinateSpan(latitudeDelta: 0.25, longitudeDelta: 0.25)
        ))
        
        var body: some View {
            ZStack {
                Map(position: $cameraPosition)
                Text("New location manager: \(newlocationManager.location?.description ?? "NIL" )") // works
            }
            .task {
                try? await newlocationManager.startCurrentLocationUpdates()
            }
            .onAppear { // <--- here
                cameraPosition = .region(MKCoordinateRegion(
                    center: newlocationManager.location?.coordinate 
                    ?? CLLocationCoordinate2D(latitude: 35.68, longitude: 139.75),
                    span: MKCoordinateSpan(latitudeDelta: 0.25, longitudeDelta: 0.25)
                ))
            }
            
        }
    }
    

    EDIT-3:

    @Observable
    final class NewLocationManager: NSObject, CLLocationManagerDelegate {
        var location: CLLocation? = nil
        private let locationManager = CLLocationManager()
    
        func startCurrentLocationUpdates() async throws {
            if locationManager.authorizationStatus == .notDetermined {
                locationManager.requestWhenInUseAuthorization()
            }
            for try await locationUpdate in CLLocationUpdate.liveUpdates() {
                guard let location = locationUpdate.location else { return }
                DispatchQueue.main.async { // <--- here
                    self.location = location
                }
            }
        }
       
    }
    
    struct ContentView: View {
        @State var newlocationManager = NewLocationManager()
        
        @State var cameraPosition: MapCameraPosition = .automatic
        
        var body: some View {
            ZStack {
                Map(position: $cameraPosition)
                Text("\(newlocationManager.location?.description ?? "NIL" )")
                    .foregroundStyle(.red)
            }
            .task {
                try? await newlocationManager.startCurrentLocationUpdates()
            }
            // --- here
            .onReceive(newlocationManager.location.publisher) { location in
                cameraPosition = .region(MKCoordinateRegion(
                    center: location.coordinate,
                    span: MKCoordinateSpan(latitudeDelta: 0.0025, longitudeDelta: 0.0025) // <--- here
                ))
            }
        }
    }