Skip to content

Commit

Permalink
Merge pull request #14 from hactar/main
Browse files Browse the repository at this point in the history
Add mapViewModifier + Improve ShapeDataBuilder
  • Loading branch information
ianthetechie committed Jan 23, 2024
2 parents 39e1d89 + e49b998 commit a8a2792
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 4 deletions.
30 changes: 27 additions & 3 deletions Sources/MapLibreSwiftDSL/Data Sources.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,33 @@ public struct ShapeSource: Source {

@resultBuilder
public enum ShapeDataBuilder {
public static func buildBlock(_ components: MLNShape...) -> ShapeData {
let features = components.compactMap({ $0 as? MLNShape & MLNFeature })

// Handle a single MLNShape element
public static func buildExpression(_ expression: MLNShape) -> [MLNShape] {
return [expression]
}

public static func buildExpression(_ expression: [MLNShape]) -> [MLNShape] {
return expression
}

// Combine elements into an array
public static func buildBlock(_ components: [MLNShape]...) -> [MLNShape] {
return components.flatMap { $0 }
}

// Handle an array of MLNShape (if you want to directly pass arrays)
public static func buildArray(_ components: [MLNShape]) -> [MLNShape] {
return components
}

// Handle for in of MLNShape
public static func buildArray(_ components: [[MLNShape]]) -> [MLNShape] {
return components.flatMap { $0 }
}

// Convert the collected MLNShape array to ShapeData
public static func buildFinalResult(_ components: [MLNShape]) -> ShapeData {
let features = components.compactMap { $0 as? MLNShape & MLNFeature }
if features.count == components.count {
return .features(features)
} else {
Expand Down
40 changes: 40 additions & 0 deletions Sources/MapLibreSwiftUI/Examples/Other.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import CoreLocation
import MapLibre
import MapLibreSwiftDSL
import SwiftUI

struct Other_Previews: PreviewProvider {
static var previews: some View {
let demoTilesURL = URL(string: "https://demotiles.maplibre.org/style.json")!

// A collection of points with various
// attributes
let pointSource = ShapeSource(identifier: "points") {
// Uses the DSL to quickly construct point features inline
MLNPointFeature(coordinate: CLLocationCoordinate2D(latitude: 51.47778, longitude: -0.00139))

MLNPointFeature(coordinate: CLLocationCoordinate2D(latitude: 0, longitude: 0)) { feature in
feature.attributes["icon"] = "missing"
feature.attributes["heading"] = 45
}

MLNPointFeature(coordinate: CLLocationCoordinate2D(latitude: 39.02001, longitude: 1.482148)) { feature in
feature.attributes["icon"] = "club"
feature.attributes["heading"] = 145
}
}

MapView(styleURL: demoTilesURL) {
// Demonstrates how to use the unsafeMapModifier to set MLNMapView properties that have not been exposed as modifiers yet.
SymbolStyleLayer(identifier: "simple-symbols", source: pointSource)
.iconImage(constant: UIImage(systemName: "mappin")!)
}
.unsafeMapViewModifier({ mapView in
// Not all properties have modifiers yet. Until they do, you can use this 'escape hatch' to the underlying MLNMapView. Be careful: if you modify properties that the DSL controls already, they may be overridden. This modifier is a "hack", not a final function.
mapView.logoView.isHidden = false
mapView.compassViewPosition = .topLeft
})
.ignoresSafeArea(.all)
.previewDisplayName("Unsafe MapView Modifier")
}
}
39 changes: 38 additions & 1 deletion Sources/MapLibreSwiftUI/MapView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ public struct MapView: UIViewRepresentable {

let styleSource: MapStyleSource
let userLayers: [StyleLayerDefinition]

/// 'Escape hatch' to MLNMapView until we have more modifiers.
/// See ``unsafeMapViewModifier(_:)``
private var unsafeMapViewModifier: ((MLNMapView) -> Void)?

public init(
styleURL: URL,
Expand All @@ -28,6 +32,36 @@ public struct MapView: UIViewRepresentable {
) {
self.init(styleURL: styleURL, camera: .constant(initialCamera), makeMapContent)
}

/// Allows you to set properties of the underlying MLNMapView directly
/// in cases where these have not been ported to DSL yet.
/// Use this function to modify various properties of the MLNMapView instance.
/// For example, you can enable the display of the user's location on the map by setting `showUserLocation` to true.
///
/// This is an 'escape hatch' back to the non-DSL world
/// of MapLibre for features that have not been ported to DSL yet.
/// Be careful not to use this to modify properties that are
/// already ported to the DSL, like the camera for example, as your
/// modifications here may break updates that occur with modifiers.
/// In particular, this modifier is potentially dangerous as it runs on
/// EVERY call to `updateUIView`.
///
/// - Parameter modifier: A closure that provides you with an MLNMapView so you can set properties.
/// - Returns: A MapView with the modifications applied.
///
/// Example:
/// ```swift
/// MapView()
/// .mapViewModifier { mapView in
/// mapView.showUserLocation = true
/// }
/// ```
///
public func unsafeMapViewModifier(_ modifier: @escaping (MLNMapView) -> Void) -> MapView {
var newMapView = self
newMapView.unsafeMapViewModifier = modifier
return newMapView
}

public class Coordinator: NSObject, MLNMapViewDelegate {
var parent: MapView
Expand Down Expand Up @@ -192,11 +226,14 @@ public struct MapView: UIViewRepresentable {

public func updateUIView(_ mapView: MLNMapView, context: Context) {
context.coordinator.parent = self

unsafeMapViewModifier?(mapView)

// 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.
// FIXME: This isn't exactly telling us if the *map* is loaded, and the docs for setCenter say it needs to be.
let isStyleLoaded = mapView.style != nil

context.coordinator.updateCamera(mapView: mapView,
Expand Down
20 changes: 20 additions & 0 deletions Tests/MapLibreSwiftDSLTests/ShapeSourceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,24 @@ final class ShapeSourceTests: XCTestCase {
XCTFail("Expected a feature source")
}
}

func testForInAndCombinationFeatureBuilder() throws {
// ShapeSource now accepts 'for in' building, arrays, and combinations of them
let shapeSource = ShapeSource(identifier: "foo") {
for coordinates in samplePedestrianWaypoints {
MLNPointFeature(coordinate: coordinates)
}
MLNPointFeature(coordinate: CLLocationCoordinate2D(latitude: 48.2082, longitude: 16.3719))
}

XCTAssertEqual(shapeSource.identifier, "foo")

switch shapeSource.data {
case .features(let features):
XCTAssertEqual(features.count, 48)
default:
XCTFail("Expected a feature source")
}
}

}

0 comments on commit a8a2792

Please sign in to comment.