Skip to content

Commit

Permalink
Merge pull request #28 from stadiamaps/content-insets-and-more-modifiers
Browse files Browse the repository at this point in the history
Content insets and more modifiers
  • Loading branch information
ianthetechie authored Mar 19, 2024
2 parents 497ab5f + 879bd06 commit d330665
Show file tree
Hide file tree
Showing 38 changed files with 612 additions and 187 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
run: brew install swiftformat

- name: Checkout maplibre-swiftui-dsl-playground
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Check format
run: swiftformat . --lint
Expand All @@ -38,10 +38,10 @@ jobs:

- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '15.0'
xcode-version: '15.2'

- name: Checkout maplibre-swiftui-dsl-playground
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Test ${{ matrix.scheme }} on ${{ matrix.destination }}
run: xcodebuild -scheme ${{ matrix.scheme }} test -skipMacroValidation -destination '${{ matrix.destination }}' | xcbeautify && exit ${PIPESTATUS[0]}
9 changes: 8 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ let package = Package(
],
swiftSettings: [
.define("MOCKING", .when(configuration: .debug)),
.enableExperimentalFeature("StrictConcurrency"),
]
),
.target(
Expand All @@ -46,10 +47,16 @@ let package = Package(
.target(name: "InternalUtils"),
.product(name: "MapLibre", package: "maplibre-gl-native-distribution"),
.product(name: "MapLibreSwiftMacros", package: "maplibre-swift-macros"),
],
swiftSettings: [
.enableExperimentalFeature("StrictConcurrency"),
]
),
.target(
name: "InternalUtils"
name: "InternalUtils",
swiftSettings: [
.enableExperimentalFeature("StrictConcurrency"),
]
),

// MARK: Tests
Expand Down
137 changes: 137 additions & 0 deletions Sources/MapLibreSwiftDSL/MapControls.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import Foundation
import MapLibre

public protocol MapControl {
/// Overrides the position of the control. Default values are control-specfiic.
var position: MLNOrnamentPosition? { get set }
/// Overrides the offset of the control.
var margins: CGPoint? { get set }
/// Overrides whether the control is hidden.
var isHidden: Bool { get set }

@MainActor func configureMapView(_ mapView: MLNMapView)
}

public extension MapControl {
/// Sets position of the control.
func position(_ position: MLNOrnamentPosition) -> Self {
var result = self

result.position = position

return result
}

/// Sets the position offset of the control.
func margins(_ margins: CGPoint) -> Self {
var result = self

result.margins = margins

return result
}

/// Hides the control.
func hidden(_: Bool) -> Self {
var result = self

result.isHidden = true

return result
}
}

public struct CompassView: MapControl {
public var position: MLNOrnamentPosition?
public var margins: CGPoint?
public var isHidden: Bool = false

public func configureMapView(_ mapView: MLNMapView) {
if let position {
mapView.compassViewPosition = position
}

if let margins {
mapView.compassViewMargins = margins
}

mapView.compassView.isHidden = isHidden
}

public init() {}
}

public struct LogoView: MapControl {
public var position: MLNOrnamentPosition?
public var margins: CGPoint?
public var isHidden: Bool = false
public var image: UIImage?

public init() {}

public func configureMapView(_ mapView: MLNMapView) {
if let position {
mapView.logoViewPosition = position
}

if let margins {
mapView.logoViewMargins = margins
}

mapView.logoView.isHidden = isHidden

if let image {
mapView.logoView.image = image
}
}
}

public extension LogoView {
/// Sets the logo image (defaults to the MapLibre logo).
func image(_ image: UIImage?) -> Self {
var result = self

result.image = image

return result
}
}

@resultBuilder
public enum MapControlsBuilder: DefaultResultBuilder {
public static func buildExpression(_ expression: MapControl) -> [MapControl] {
[expression]
}

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

public static func buildExpression(_: Void) -> [MapControl] {
[]
}

public static func buildBlock(_ components: [MapControl]...) -> [MapControl] {
components.flatMap { $0 }
}

public static func buildArray(_ components: [MapControl]) -> [MapControl] {
components
}

public static func buildArray(_ components: [[MapControl]]) -> [MapControl] {
components.flatMap { $0 }
}

public static func buildEither(first components: [MapControl]) -> [MapControl] {
components
}

public static func buildEither(second components: [MapControl]) -> [MapControl] {
components
}

public static func buildOptional(_ components: [MapControl]?) -> [MapControl] {
components ?? []
}
}
17 changes: 9 additions & 8 deletions Sources/MapLibreSwiftUI/Examples/Camera.swift
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
import CoreLocation
import SwiftUI

private let switzerland = CLLocationCoordinate2D(latitude: 46.801111, longitude: 8.226667)

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

let styleURL: URL
var onStyleLoaded: (() -> Void)? = nil
var targetCameraAfterDelay: MapViewCamera? = nil

var body: some View {
MapView(styleURL: styleURL, camera: $camera)
.onStyleLoaded { _ in
print("Style is loaded")
onStyleLoaded?()
}
.overlay(alignment: .bottom, content: {
Text("\(String(describing: camera.state)) z \(camera.zoom)")
Text("\(String(describing: camera.state))")
.padding()
.foregroundColor(.white)
.background(
Expand All @@ -27,16 +25,19 @@ struct CameraDirectManipulationPreview: View {
.padding(.bottom, 42)
})
.task {
try? await Task.sleep(nanoseconds: 3 * NSEC_PER_SEC)
if let targetCameraAfterDelay {
try? await Task.sleep(nanoseconds: 3 * NSEC_PER_SEC)

camera = MapViewCamera.center(switzerland, zoom: 6)
camera = targetCameraAfterDelay
}
}
}
}

#Preview("Camera Preview") {
#Preview("Camera Zoom after delay") {
CameraDirectManipulationPreview(
styleURL: URL(string: "https://demotiles.maplibre.org/style.json")!
styleURL: demoTilesURL,
targetCameraAfterDelay: .center(switzerland, zoom: 6)
)
.ignoresSafeArea(.all)
}
3 changes: 1 addition & 2 deletions Sources/MapLibreSwiftUI/Examples/Layers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ import MapLibre
import MapLibreSwiftDSL
import SwiftUI

let demoTilesURL = URL(string: "https://demotiles.maplibre.org/style.json")!

// A collection of points with various
// attributes
@MainActor
let pointSource = ShapeSource(identifier: "points") {
// Uses the DSL to quickly construct point features inline
MLNPointFeature(coordinate: CLLocationCoordinate2D(latitude: 51.47778, longitude: -0.00139))
Expand Down
4 changes: 1 addition & 3 deletions Sources/MapLibreSwiftUI/Examples/Polyline.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ struct PolylinePreview: View {

var body: some View {
MapView(styleURL: styleURL,
constantCamera: .center(samplePedestrianWaypoints.first!, zoom: 14))
camera: .constant(.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.
Expand Down Expand Up @@ -43,8 +43,6 @@ struct PolylinePreview: View {

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

PolylinePreview(styleURL: demoTilesURL)
.ignoresSafeArea(.all)
}
Expand Down
6 changes: 6 additions & 0 deletions Sources/MapLibreSwiftUI/Examples/Preview Helpers.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// This file contains helpers that are used in the SwiftUI preview examples
import CoreLocation

let switzerland = CLLocationCoordinate2D(latitude: 47.03041, longitude: 8.29470)
let demoTilesURL =
URL(string: "https://demotiles.maplibre.org/style.json")!
37 changes: 37 additions & 0 deletions Sources/MapLibreSwiftUI/Examples/User Location.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import CoreLocation
import MapLibreSwiftDSL
import SwiftUI

@MainActor
private let locationManager = StaticLocationManager(initialLocation: CLLocation(
coordinate: switzerland,
altitude: 0,
horizontalAccuracy: 1,
verticalAccuracy: 1,
course: 8,
speed: 28,
timestamp: Date()
))

#Preview("Track user location") {
MapView(
styleURL: demoTilesURL,
camera: .constant(.trackUserLocation(zoom: 4, pitch: .fixed(45))),
locationManager: locationManager
)
.mapViewContentInset(.init(top: 450, left: 0, bottom: 0, right: 0))
.ignoresSafeArea(.all)
}

#Preview("Track user location with Course") {
MapView(
styleURL: demoTilesURL,
camera: .constant(.trackUserLocationWithCourse(zoom: 4, pitch: .fixed(45))),
locationManager: locationManager
)
.mapViewContentInset(.init(top: 450, left: 0, bottom: 0, right: 0))
.mapControls {
LogoView()
}
.ignoresSafeArea(.all)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,18 @@ import Foundation
import MapLibre
import Mockable

// NOTE: We should eventually mark the entire protocol @MainActor, but Mockable generates some unsafe code at the moment
@Mockable
protocol MLNMapViewCameraUpdating: AnyObject {
var userTrackingMode: MLNUserTrackingMode { get set }
var minimumPitch: CGFloat { get set }
var maximumPitch: CGFloat { get set }
func setCenter(_ coordinate: CLLocationCoordinate2D,
zoomLevel: Double,
direction: CLLocationDirection,
animated: Bool)
func setZoomLevel(_ zoomLevel: Double, animated: Bool)
func setVisibleCoordinateBounds(
@MainActor var userTrackingMode: MLNUserTrackingMode { get set }
@MainActor var minimumPitch: CGFloat { get set }
@MainActor var maximumPitch: CGFloat { get set }
@MainActor func setCenter(_ coordinate: CLLocationCoordinate2D,
zoomLevel: Double,
direction: CLLocationDirection,
animated: Bool)
@MainActor func setZoomLevel(_ zoomLevel: Double, animated: Bool)
@MainActor func setVisibleCoordinateBounds(
_ bounds: MLNCoordinateBounds,
edgePadding: UIEdgeInsets,
animated: Bool,
Expand Down
10 changes: 5 additions & 5 deletions Sources/MapLibreSwiftUI/Extensions/MapView/MapViewGestures.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ extension MapView {
/// - mapView: The MLNMapView that will host the gesture itself.
/// - context: The UIViewRepresentable context that will orchestrate the response sender
/// - gesture: The gesture definition.
func registerGesture(_ mapView: MLNMapView, _ context: Context, gesture: MapGesture) {
@MainActor func registerGesture(_ mapView: MLNMapView, _ context: Context, gesture: MapGesture) {
switch gesture.method {
case let .tap(numberOfTaps: numberOfTaps):
let gestureRecognizer = UITapGestureRecognizer(target: context.coordinator,
Expand Down Expand Up @@ -41,7 +41,7 @@ extension MapView {
/// - mapView: The MapView emitting the gesture. This is used to calculate the point and coordinate of the
/// gesture.
/// - sender: The UIGestureRecognizer
func processGesture(_ mapView: MLNMapView, _ sender: UIGestureRecognizer) {
@MainActor func processGesture(_ mapView: MLNMapView, _ sender: UIGestureRecognizer) {
guard let gesture = gestures.first(where: { $0.gestureRecognizer == sender }) else {
assertionFailure("\(sender) is not a registered UIGestureRecongizer on the MapView")
return
Expand All @@ -60,11 +60,11 @@ extension MapView {
/// - gesture: The gesture definition for this event.
/// - sender: The UIKit gesture emitting from the map view.
/// - Returns: The calculated context from the sending UIKit gesture
func processContextFromGesture(_ mapView: MLNMapView, gesture: MapGesture,
sender: UIGestureRecognizing) -> MapGestureContext
@MainActor func processContextFromGesture(_ mapView: MLNMapView, gesture: MapGesture,
sender: UIGestureRecognizing) -> MapGestureContext
{
// Build the context of the gesture's event.
var point: CGPoint = switch gesture.method {
let point: CGPoint = switch gesture.method {
case let .tap(numberOfTaps: numberOfTaps):
// Calculate the CGPoint of the last gesture tap
sender.location(ofTouch: numberOfTaps - 1, in: mapView)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import UIKit

@Mockable
protocol UIGestureRecognizing: AnyObject {
var state: UIGestureRecognizer.State { get }
func location(in view: UIView?) -> CGPoint
func location(ofTouch touchIndex: Int, in view: UIView?) -> CGPoint
@MainActor var state: UIGestureRecognizer.State { get }
@MainActor func location(in view: UIView?) -> CGPoint
@MainActor func location(ofTouch touchIndex: Int, in view: UIView?) -> CGPoint
}

extension UIGestureRecognizer: UIGestureRecognizing {
Expand Down
Loading

0 comments on commit d330665

Please sign in to comment.