Greg
Greg

Reputation: 34818

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()
}

Upvotes: 1

Views: 589

Answers (1)

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
            ))
        }
    }
}
 

Upvotes: 2

Related Questions