diff --git a/Sources/MapLibreSwiftDSL/MapControls.swift b/Sources/MapLibreSwiftDSL/MapControls.swift new file mode 100644 index 0000000..6593b6b --- /dev/null +++ b/Sources/MapLibreSwiftDSL/MapControls.swift @@ -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 ?? [] + } +} diff --git a/Sources/MapLibreSwiftUI/Examples/User Location.swift b/Sources/MapLibreSwiftUI/Examples/User Location.swift index d76da44..3bafb2f 100644 --- a/Sources/MapLibreSwiftUI/Examples/User Location.swift +++ b/Sources/MapLibreSwiftUI/Examples/User Location.swift @@ -1,4 +1,5 @@ import CoreLocation +import MapLibreSwiftDSL import SwiftUI @MainActor @@ -29,6 +30,8 @@ private let locationManager = StaticLocationManager(initialLocation: CLLocation( locationManager: locationManager ) .mapViewContentInset(.init(top: 450, left: 0, bottom: 0, right: 0)) - .hideCompassView() + .mapControls { + LogoView() + } .ignoresSafeArea(.all) } diff --git a/Sources/MapLibreSwiftUI/MapView.swift b/Sources/MapLibreSwiftUI/MapView.swift index 22ee397..44f92ae 100644 --- a/Sources/MapLibreSwiftUI/MapView.swift +++ b/Sources/MapLibreSwiftUI/MapView.swift @@ -20,6 +20,11 @@ public struct MapView: UIViewRepresentable { /// See ``unsafeMapViewModifier(_:)`` var unsafeMapViewModifier: ((MLNMapView) -> Void)? + var controls: [MapControl] = [ + CompassView(), + LogoView(), + ] + private var locationManager: MLNLocationManager? public init( @@ -92,8 +97,14 @@ public struct MapView: UIViewRepresentable { @MainActor private func applyModifiers(_ mapView: MLNMapView, runUnsafe: Bool) { mapView.contentInset = mapViewContentInset - mapView.logoView.isHidden = isLogoViewHidden - mapView.compassView.isHidden = isCompassViewHidden + // Assume all controls are hidden by default (so that an empty list returns a map with no controls) + mapView.logoView.isHidden = true + mapView.compassView.isHidden = true + + // Apply each control configuration + for control in controls { + control.configureMapView(mapView) + } if runUnsafe { unsafeMapViewModifier?(mapView) @@ -101,32 +112,6 @@ public struct MapView: UIViewRepresentable { } } -public extension MapView { - func mapViewContentInset(_ inset: UIEdgeInsets) -> Self { - var result = self - - result.mapViewContentInset = inset - - return result - } - - func hideLogoView() -> Self { - var result = self - - result.isLogoViewHidden = true - - return result - } - - func hideCompassView() -> Self { - var result = self - - result.isCompassViewHidden = true - - return result - } -} - #Preview { MapView(styleURL: demoTilesURL) .ignoresSafeArea(.all) diff --git a/Sources/MapLibreSwiftUI/MapViewModifiers.swift b/Sources/MapLibreSwiftUI/MapViewModifiers.swift index bc8aa96..7f4641a 100644 --- a/Sources/MapLibreSwiftUI/MapViewModifiers.swift +++ b/Sources/MapLibreSwiftUI/MapViewModifiers.swift @@ -1,5 +1,6 @@ import Foundation import MapLibre +import MapLibreSwiftDSL import SwiftUI public extension MapView { @@ -82,4 +83,20 @@ public extension MapView { return newMapView } + + func mapViewContentInset(_ inset: UIEdgeInsets) -> Self { + var result = self + + result.mapViewContentInset = inset + + return result + } + + func mapControls(@MapControlsBuilder _ buildControls: () -> [MapControl]) -> Self { + var result = self + + result.controls = buildControls() + + return result + } } diff --git a/Tests/MapLibreSwiftUITests/Examples/MapControlsTests.swift b/Tests/MapLibreSwiftUITests/Examples/MapControlsTests.swift new file mode 100644 index 0000000..c127a87 --- /dev/null +++ b/Tests/MapLibreSwiftUITests/Examples/MapControlsTests.swift @@ -0,0 +1,8 @@ +// +// File.swift +// +// +// Created by Ian Wagner on 2024-03-18. +// + +import Foundation