Anyone able to see how to use the new SwiftUI Map and WWDC @Observable concept to dynamically update my SwiftUI Map position and rotation based on the dynamic changes it picks up from my @Observable object.
Note the updates are coming through as the Text labels show this. But how to get the Map position referencing the same values and updating them? The "onAppear" approach doesn't seem to work.
import SwiftUI
import MapKit
@Observable
final class NewLocationManager : NSObject, CLLocationManagerDelegate {
var location: CLLocation? = nil
var direction: CLLocationDirection = 0
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 }
print("NewLocationManager: \(location.coordinate.latitude), \(location.coordinate.longitude)")
self.location = location
self.direction = self.direction + 1
}
}
}
struct ContentView: View {
var locationMgr = NewLocationManager()
@State private var mapCamPos: MapCameraPosition = .automatic
private let bigBen = CLLocationCoordinate2D(latitude: 51.500685, longitude: -0.124570)
var body: some View {
ZStack {
Map(position: $mapCamPos)
.onAppear { // Does NOT work - how to get position/direction updates working to Map (map should be moving/rotating)
mapCamPos = .camera(MapCamera(
centerCoordinate: self.locationMgr.location?.coordinate ?? bigBen,
distance: 800,
heading: self.locationMgr.direction
))
}
VStack (alignment: .leading) {
Text("Location from observable: \(locationMgr.location?.description ?? "NIL")") // This works (they get updates regularly)
Text("Direction from observable: \(locationMgr.direction)") // This works (they get updates regularly)
Spacer()
}
}
.task {
try? await locationMgr.startCurrentLocationUpdates()
}
}
}
#Preview {
ContentView()
}
Tag: wwdc2023-10043
Here you go
//
// ContentView.swift
// ForumMapsQuestion
//
// Created by SB on 2024-01-05.
//
import SwiftUI
import MapKit
struct ContentView: View {
@StateObject var locationMgr = NewLocationManager()
@State private var mapCamPos: MapCameraPosition = .automatic
var body: some View {
ZStack {
Map(position: $mapCamPos)
.onReceive(locationMgr.$direction) { direction in
mapCamPos = .camera(MapCamera(
centerCoordinate: self.locationMgr.location.coordinate,
distance: 800,
heading: direction
))
}
.onReceive(locationMgr.$location) { location in
mapCamPos = .camera(MapCamera(
centerCoordinate: location.coordinate,
distance: 800,
heading: self.locationMgr.direction
))
}
VStack (alignment: .leading) {
VStack {
Text("Location from observable: \(locationMgr.location.description)")
Text("Direction from observable: \(locationMgr.direction)")
}
.padding()
.background(.gray)
.clipShape(RoundedRectangle(cornerSize: CGSize(width: 20, height: 10)))
.opacity(0.7)
Spacer()
}
}
}
}
#Preview {
ContentView()
}
final class NewLocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
@Published var location: CLLocation = CLLocation(coordinate: CLLocationCoordinate2D(latitude: 51.500685, longitude: -0.124570), altitude: .zero, horizontalAccuracy: .zero, verticalAccuracy: .zero, timestamp: Date.now)
@Published var direction: CLLocationDirection = .zero
private let locationManager = CLLocationManager()
override init() {
super.init()
locationManager.startUpdatingLocation()
locationManager.delegate = self
Task { [weak self] in
try? await self?.requestAuthorization()
}
}
func requestAuthorization() async throws {
if locationManager.authorizationStatus == .notDetermined {
locationManager.requestWhenInUseAuthorization()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
locations.forEach { [weak self] location in
Task { @MainActor [weak self] in
self?.location = location
}
}
}
func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
Task { @MainActor [weak self] in
self?.direction = newHeading.trueHeading
}
}
}