Skip to content

Commit

Permalink
Added map camera, layer mods, and more
Browse files Browse the repository at this point in the history
  • Loading branch information
Archdoog committed Dec 17, 2023
1 parent e3656f9 commit 99504f6
Show file tree
Hide file tree
Showing 11 changed files with 195 additions and 47 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1500"
LastUpgradeVersion = "1510"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1500"
LastUpgradeVersion = "1510"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1500"
LastUpgradeVersion = "1510"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ let package = Package(
dependencies: [.target(name: "MapLibreSwiftMacrosImpl")]
),
.binaryTarget(name: "MapLibre",
url: "https://github.com/maplibre/maplibre-native/releases/download/ios-v6.0.0-preda45706601c7ccc6d922a8fcddfc62ff7c8f480d/MapLibre.dynamic.xcframework.zip",
checksum: "37e621c0c7c1f589f0a125816155ba443000d78b80649d85a9b8b3d19144836c"),
url: "https://github.com/maplibre/maplibre-native/releases/download/ios-v6.0.0-pre9fbcb031f019048f21fdfcb57b80f4451cdecfd9/MapLibre.dynamic.xcframework.zip",
checksum: "929bbc3f24740df360bf447acf08bd102cf4baf12a6bc099f42e5fb8b5130cf4"),
.target(
name: "MapLibreSwiftUI",
dependencies: [
Expand Down
29 changes: 13 additions & 16 deletions Sources/MapLibreSwiftUI/Examples/Camera.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,29 @@ import SwiftUI
private let switzerland = CLLocationCoordinate2D(latitude: 46.801111, longitude: 8.226667)

struct CameraDirectManipulationPreview: View {
@State private var camera = MapView.Camera.centerAndZoom(switzerland, 4)
@State private var camera = MapViewCamera.center(switzerland, zoom: 4)

let styleURL: URL

var body: some View {
MapView(styleURL: styleURL, camera: $camera)
.overlay(alignment: .bottom, content: {
switch camera {
case .centerAndZoom(let coord, let zoom):
Text("\(coord.latitude), \(coord.longitude) z \(zoom ?? 0)")
.padding()
.background(
in: .rect(cornerRadii: .init(
topLeading: 8,
bottomLeading: 8,
bottomTrailing: 8,
topTrailing: 8)),
fillStyle: .init()
)
.padding(.bottom, 42)
}
Text("\(camera.coordinate.latitude), \(camera.coordinate.longitude) z \(camera.zoom ?? 0)")
.padding()
.background(
in: .rect(cornerRadii: .init(
topLeading: 8,
bottomLeading: 8,
bottomTrailing: 8,
topTrailing: 8)),
fillStyle: .init()
)
.padding(.bottom, 42)
})
.task {
try! await Task.sleep(for: .seconds(3))

camera = MapView.Camera.centerAndZoom(switzerland, 6)
camera = MapViewCamera.center(switzerland, zoom: 6)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/MapLibreSwiftUI/Examples/Polyline.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ struct PolylinePreview: View {
let styleURL: URL

var body: some View {
MapView(styleURL: styleURL, initialCamera: MapView.Camera.centerAndZoom(samplePedestrianWaypoints.first!, 14)) {
MapView(styleURL: styleURL, initialCamera: MapViewCamera.center(samplePedestrianWaypoints.first!, zoom: 14)) {
// Note: This line does not add the source to the style as if it
// were a statement in an imperative programming language.
// The source is added automatically if a layer references it.
Expand Down
53 changes: 28 additions & 25 deletions Sources/MapLibreSwiftUI/MapView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,32 @@ import SwiftUI
import InternalUtils
import MapLibre
import MapLibreSwiftDSL

import MapLibreSwiftUI

public struct MapView: UIViewRepresentable {
// TODO: Support MLNStyle as well; having a DSL for that would be nice
enum MapStyleSource {
case url(URL)
}

public enum Camera {
case centerAndZoom(CLLocationCoordinate2D, Double?)
}

@State private var lastCamera: MapViewCamera?
public private(set) var camera: Binding<MapViewCamera>?

var camera: Binding<Camera>?

let styleSource: MapStyleSource
public let styleSource: MapStyleSource
let userLayers: [StyleLayerDefinition]

public init(styleURL: URL, camera: Binding<Camera>? = nil, @MapViewContentBuilder _ makeMapContent: () -> [StyleLayerDefinition] = { [] }) {
public init(
styleURL: URL,
camera: Binding<MapViewCamera>? = nil,
@MapViewContentBuilder _ makeMapContent: () -> [StyleLayerDefinition] = { [] }
) {
self.styleSource = .url(styleURL)
self.camera = camera

userLayers = makeMapContent()
}

public init(styleURL: URL, initialCamera: Camera, @MapViewContentBuilder _ makeMapContent: () -> [StyleLayerDefinition] = { [] }) {
public init(
styleURL: URL,
initialCamera: MapViewCamera,
@MapViewContentBuilder _ makeMapContent: () -> [StyleLayerDefinition] = { [] }
) {
self.init(styleURL: styleURL, camera: .constant(initialCamera), makeMapContent)
}

Expand Down Expand Up @@ -58,7 +59,8 @@ public struct MapView: UIViewRepresentable {

public func mapView(_ mapView: MLNMapView, regionDidChangeAnimated animated: Bool) {
DispatchQueue.main.async {
self.parent.camera?.wrappedValue = .centerAndZoom(mapView.centerCoordinate, mapView.zoomLevel)
self.parent.camera?.wrappedValue = .center(mapView.centerCoordinate,
zoom: mapView.zoomLevel)
}
}

Expand Down Expand Up @@ -186,17 +188,18 @@ public struct MapView: UIViewRepresentable {
}

private func updateMapCamera(_ mapView: MLNMapView, animated: Bool) {
if let camera = self.camera {
switch camera.wrappedValue {
case .centerAndZoom(let center, let zoom):
// TODO: Determine if MapLibre is smart enough to keep animating to the same place multiple times; if not, add a check here to prevent suprious updates.
if let z = zoom {
mapView.setCenter(center, zoomLevel: z, animated: animated)
} else {
mapView.setCenter(center, animated: animated)
}
}
guard let newCamera = self.camera?.wrappedValue,
lastCamera != newCamera else {
// Exit early - the camera has not changed.
return
}

mapView.setCenter(newCamera.coordinate,
zoomLevel: newCamera.zoom,
direction: newCamera.course,
animated: animated)

self.lastCamera = newCamera
}
}

Expand Down
44 changes: 44 additions & 0 deletions Sources/MapLibreSwiftUI/Models/MapCamera/CameraState.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// File.swift
//
//
// Created by Jacob Fielding on 12/16/23.
//

import Foundation
import MapLibre

/// The CameraState is used to understand the current context of the MapView's camera.
public enum CameraState {

/// Centered on a coordinate
case centered

/// The camera is currently following a location provider.
case userLocation

/// Centered on a bounding box/rectangle.
case rect

/// Showcasing a GeoJSON/Polygon
case showcase
}

extension CameraState: Equatable {

public static func ==(lhs: CameraState, rhs: CameraState) -> Bool {
switch (lhs, rhs) {

case (.centered, .centered):
return true
case (.userLocation, .userLocation):
return true
case (.rect, .rect):
return true
case (.showcase, .showcase):
return true
default:
return false
}
}
}
69 changes: 69 additions & 0 deletions Sources/MapLibreSwiftUI/Models/MapCamera/MapViewCamera.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import Foundation
import CoreLocation

public struct MapViewCamera {

public var state: CameraState
public var coordinate: CLLocationCoordinate2D
public var zoom: Double
public var pitch: Double
public var course: CLLocationDirection

/// A backup 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 {
return MapViewCamera(state: .centered,
coordinate: CLLocationCoordinate2D(latitude: 0, longitude: 0),
zoom: 10,
pitch: 90,
course: 0)
}

/// Center the map on a specific location.
///
/// - Parameters:
/// - coordinate: The coordinate to center the map on.
/// - zoom: The zoom level.
/// - pitch: The camera pitch. Default is 90 (straight down).
/// - course: The course. Default is 0 (North).
/// - Returns: The constructed MapViewCamera.
public static func center(_ coordinate: CLLocationCoordinate2D,
zoom: Double,
pitch: Double = 90.0,
course: Double = 0) -> MapViewCamera {

return MapViewCamera(state: .centered,
coordinate: coordinate,
zoom: zoom,
pitch: pitch,
course: course)
}

public static func userLocation(_ location: CLLocation,
zoom: Double,
pitch: Double = 90.0) -> MapViewCamera {

return MapViewCamera(state: .userLocation,
coordinate: location.coordinate,
zoom: zoom,
pitch: pitch,
course: location.course)
}

// TODO: Create init methods for other camera states once supporting materials are understood (e.g. BoundingBox)
}

extension MapViewCamera: Equatable {

public static func ==(lhs: MapViewCamera, rhs: MapViewCamera) -> Bool {
return lhs.state == rhs.state
&& lhs.coordinate.latitude == rhs.coordinate.latitude
&& lhs.coordinate.longitude == rhs.coordinate.longitude
&& lhs.zoom == rhs.zoom
&& lhs.pitch == rhs.pitch
&& lhs.course == rhs.course
}
}
13 changes: 13 additions & 0 deletions Sources/MapLibreSwiftUI/Models/MapStyleSource/MapStyleSource.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// File.swift
//
//
// Created by Jacob Fielding on 12/16/23.
//

import Foundation

// TODO: Support MLNStyle as well; having a DSL for that would be nice
public enum MapStyleSource {
case url(URL)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// File.swift
//
//
// Created by Jacob Fielding on 12/16/23.
//

import SwiftUI
import MapLibreSwiftDSL

extension MapView {

public func mapContent(@MapViewContentBuilder _ makeMapContent: () -> [StyleLayerDefinition]) -> MapView {
switch self.styleSource {

case .url(let styleUrl):
return MapView(styleURL: styleUrl,
camera: self.camera,
makeMapContent)
}
}
}

0 comments on commit 99504f6

Please sign in to comment.