diff --git a/Sources/MapLibreSwiftUI/Examples/Camera.swift b/Sources/MapLibreSwiftUI/Examples/Camera.swift index b16540f..b628747 100644 --- a/Sources/MapLibreSwiftUI/Examples/Camera.swift +++ b/Sources/MapLibreSwiftUI/Examples/Camera.swift @@ -11,8 +11,9 @@ struct CameraDirectManipulationPreview: View { var body: some View { MapView(styleURL: styleURL, camera: $camera) .overlay(alignment: .bottom, content: { - Text("\(camera.coordinate.latitude), \(camera.coordinate.longitude) z \(camera.zoom ?? 0)") + Text("\(camera.coordinate.latitude), \(camera.coordinate.longitude) z \(camera.zoom)") .padding() + .foregroundColor(.white) .background( Rectangle() .foregroundColor(.black) diff --git a/Sources/MapLibreSwiftUI/MapView.swift b/Sources/MapLibreSwiftUI/MapView.swift index b376a1f..3611320 100644 --- a/Sources/MapLibreSwiftUI/MapView.swift +++ b/Sources/MapLibreSwiftUI/MapView.swift @@ -2,18 +2,17 @@ import SwiftUI import InternalUtils import MapLibre import MapLibreSwiftDSL -import MapLibreSwiftUI public struct MapView: UIViewRepresentable { - public private(set) var camera: Binding? + public private(set) var camera: Binding - public let styleSource: MapStyleSource - public let userLayers: [StyleLayerDefinition] + let styleSource: MapStyleSource + let userLayers: [StyleLayerDefinition] public init( styleURL: URL, - camera: Binding? = nil, + camera: Binding = .constant(.default()), @MapViewContentBuilder _ makeMapContent: () -> [StyleLayerDefinition] = { [] } ) { self.styleSource = .url(styleURL) @@ -36,7 +35,7 @@ public struct MapView: UIViewRepresentable { // Storage of variables as they were previously; these are snapshot // every update cycle so we can avoid unnecessary updates private var snapshotUserLayers: [StyleLayerDefinition] = [] - var snapshotCamera: MapViewCamera? + private var snapshotCamera: MapViewCamera? init(parent: MapView) { self.parent = parent @@ -59,13 +58,27 @@ public struct MapView: UIViewRepresentable { public func mapView(_ mapView: MLNMapView, regionDidChangeAnimated animated: Bool) { DispatchQueue.main.async { - self.parent.camera?.wrappedValue = .center(mapView.centerCoordinate, - zoom: mapView.zoomLevel) + self.parent.camera.wrappedValue = .center(mapView.centerCoordinate, + zoom: mapView.zoomLevel) } } // MARK: - Coordinator API + func updateCamera(mapView: MLNMapView, camera: MapViewCamera, animated: Bool) { + guard camera != snapshotCamera else { + // No action - camera has not changed. + return + } + + mapView.setCenter(camera.coordinate, + zoomLevel: camera.zoom, + direction: camera.course, + animated: animated) + + snapshotCamera = camera + } + func updateLayers(mapView: MLNMapView) { // TODO: Figure out how to selectively update layers when only specific props changed. New function in addition to makeMLNStyleLayer? @@ -163,8 +176,10 @@ public struct MapView: UIViewRepresentable { mapView.styleURL = styleURL } - updateMapCamera(mapView, context: context, animated: false) - + context.coordinator.updateCamera(mapView: mapView, + camera: camera.wrappedValue, + animated: false) + // TODO: Make this settable via a modifier mapView.logoView.isHidden = true @@ -180,26 +195,13 @@ public struct MapView: UIViewRepresentable { // FIXME: This should be a more selective update context.coordinator.updateStyleSource(styleSource, mapView: mapView) context.coordinator.updateLayers(mapView: mapView) - + // FIXME: This isn't exactly telling us if the *map* is loaded, and the docs for setCenter say it needs t obe. let isStyleLoaded = mapView.style != nil - updateMapCamera(mapView, context: context, animated: isStyleLoaded) - } - - private func updateMapCamera(_ mapView: MLNMapView, context: Context, animated: Bool) { - guard let newCamera = self.camera?.wrappedValue, - context.coordinator.snapshotCamera != newCamera else { - // Exit early - the camera has not changed. - return - } - - mapView.setCenter(newCamera.coordinate, - zoomLevel: newCamera.zoom, - direction: newCamera.course, - animated: animated) - - context.coordinator.snapshotCamera = newCamera + context.coordinator.updateCamera(mapView: mapView, + camera: camera.wrappedValue, + animated: isStyleLoaded) } } diff --git a/Sources/MapLibreSwiftUI/Models/MapCamera/CameraState.swift b/Sources/MapLibreSwiftUI/Models/MapCamera/CameraState.swift index 0aadb64..3860508 100644 --- a/Sources/MapLibreSwiftUI/Models/MapCamera/CameraState.swift +++ b/Sources/MapLibreSwiftUI/Models/MapCamera/CameraState.swift @@ -8,7 +8,7 @@ public enum CameraState { case centered /// The camera is currently following a location provider. - case userLocation + case trackingUserLocation /// Centered on a bounding box/rectangle. case rect @@ -24,7 +24,7 @@ extension CameraState: Equatable { case (.centered, .centered): return true - case (.userLocation, .userLocation): + case (.trackingUserLocation, .trackingUserLocation): return true case (.rect, .rect): return true diff --git a/Sources/MapLibreSwiftUI/Models/MapCamera/MapViewCamera.swift b/Sources/MapLibreSwiftUI/Models/MapCamera/MapViewCamera.swift index 8e977da..0787544 100644 --- a/Sources/MapLibreSwiftUI/Models/MapCamera/MapViewCamera.swift +++ b/Sources/MapLibreSwiftUI/Models/MapCamera/MapViewCamera.swift @@ -9,12 +9,12 @@ public struct MapViewCamera { public var pitch: Double public var course: CLLocationDirection - /// A backup camera centered at 0.0, 0.0. This is typically used as a backup, + /// A camera centered at 0.0, 0.0. This is typically used as a backup, /// pre-load for an expected camera update (e.g. before a location provider produces /// it's first location). /// /// - Returns: The constructed MapViewCamera. - public static func backup() -> MapViewCamera { + public static func `default`() -> MapViewCamera { return MapViewCamera(state: .centered, coordinate: CLLocationCoordinate2D(latitude: 0, longitude: 0), zoom: 10, @@ -42,11 +42,11 @@ public struct MapViewCamera { course: course) } - public static func userLocation(_ location: CLLocation, - zoom: Double, - pitch: Double = 90.0) -> MapViewCamera { + public static func trackUserLocation(_ location: CLLocation, + zoom: Double, + pitch: Double = 90.0) -> MapViewCamera { - return MapViewCamera(state: .userLocation, + return MapViewCamera(state: .trackingUserLocation, coordinate: location.coordinate, zoom: zoom, pitch: pitch,