diff --git a/CHANGELOG.md b/CHANGELOG.md index 49186fb..dc38807 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Version 0.7.0 - 2025-02-02 + +### Added + +- Adds support for a `belowSymbols` layer order. The `renderBelowSymbols` modifier on layers will insert the new layer below the first symbol layer in the style. This allows for rendering below labels and icons. +- BREAKING: Refactored the layer position modifiers to accept enum variants to enable better extensibility. + +### Fixed + +- Moved modifiers on `StyleLayer` to `StyleLayerDefinition`. The previous extension of `StyleLayer` was a mistake, since `StyleLayerDefinition` is the supertype, and none of the behavior was specific to `StyleLayer`. + ## Version 0.6.0 - 2025-01-14 - Potentially BREAKING: Upgrades Mockable to 0.2.0. If you're using mockable in your project, this may require you to upgrade there as well. diff --git a/Sources/MapLibreSwiftDSL/Style Layers/Background.swift b/Sources/MapLibreSwiftDSL/Style Layers/Background.swift index e472fa3..0f9e104 100644 --- a/Sources/MapLibreSwiftDSL/Style Layers/Background.swift +++ b/Sources/MapLibreSwiftDSL/Style Layers/Background.swift @@ -7,7 +7,7 @@ import MapLibreSwiftMacros @MLNStyleProperty("backgroundOpacity", supportsInterpolation: true) public struct BackgroundLayer: StyleLayer { public let identifier: String - public var insertionPosition: LayerInsertionPosition = .belowOthers + public var insertionPosition: LayerInsertionPosition = .below(.all) public var isVisible: Bool = true public var maximumZoomLevel: Float? = nil public var minimumZoomLevel: Float? = nil diff --git a/Sources/MapLibreSwiftDSL/Style Layers/Circle.swift b/Sources/MapLibreSwiftDSL/Style Layers/Circle.swift index 040065e..6026212 100644 --- a/Sources/MapLibreSwiftDSL/Style Layers/Circle.swift +++ b/Sources/MapLibreSwiftDSL/Style Layers/Circle.swift @@ -10,7 +10,7 @@ import MapLibreSwiftMacros public struct CircleStyleLayer: SourceBoundVectorStyleLayerDefinition { public let identifier: String public let sourceLayerIdentifier: String? - public var insertionPosition: LayerInsertionPosition = .aboveOthers + public var insertionPosition: LayerInsertionPosition = .above(.all) public var isVisible: Bool = true public var maximumZoomLevel: Float? = nil public var minimumZoomLevel: Float? = nil diff --git a/Sources/MapLibreSwiftDSL/Style Layers/FillStyleLayer.swift b/Sources/MapLibreSwiftDSL/Style Layers/FillStyleLayer.swift index 018723f..f610c27 100644 --- a/Sources/MapLibreSwiftDSL/Style Layers/FillStyleLayer.swift +++ b/Sources/MapLibreSwiftDSL/Style Layers/FillStyleLayer.swift @@ -10,7 +10,7 @@ import MapLibreSwiftMacros public struct FillStyleLayer: SourceBoundVectorStyleLayerDefinition { public let identifier: String public let sourceLayerIdentifier: String? - public var insertionPosition: LayerInsertionPosition = .aboveOthers + public var insertionPosition: LayerInsertionPosition = .above(.all) public var isVisible: Bool = true public var maximumZoomLevel: Float? = nil public var minimumZoomLevel: Float? = nil diff --git a/Sources/MapLibreSwiftDSL/Style Layers/Line.swift b/Sources/MapLibreSwiftDSL/Style Layers/Line.swift index 1c10508..768523d 100644 --- a/Sources/MapLibreSwiftDSL/Style Layers/Line.swift +++ b/Sources/MapLibreSwiftDSL/Style Layers/Line.swift @@ -11,7 +11,7 @@ import MapLibreSwiftMacros public struct LineStyleLayer: SourceBoundVectorStyleLayerDefinition { public let identifier: String public let sourceLayerIdentifier: String? - public var insertionPosition: LayerInsertionPosition = .aboveOthers + public var insertionPosition: LayerInsertionPosition = .above(.all) public var isVisible: Bool = true public var maximumZoomLevel: Float? = nil public var minimumZoomLevel: Float? = nil diff --git a/Sources/MapLibreSwiftDSL/Style Layers/Style Layer.swift b/Sources/MapLibreSwiftDSL/Style Layers/Style Layer.swift index 435d562..7a72f62 100644 --- a/Sources/MapLibreSwiftDSL/Style Layers/Style Layer.swift +++ b/Sources/MapLibreSwiftDSL/Style Layers/Style Layer.swift @@ -1,20 +1,34 @@ import InternalUtils import MapLibre +/// A layer reference specifying which layer we should insert a new layer above. +public enum LayerReferenceAbove: Equatable { + /// A specific layer, referenced by ID. + case layer(layerId: String) + /// The group of all layers currently in the style. + case all +} + +/// A layer reference specifying which layer we should insert a new layer below. +public enum LayerReferenceBelow: Equatable { + /// A specific layer, referenced by ID. + case layer(layerId: String) + /// The group of all layers currently in the style. + case all + /// The group of symbol layers currently in the style. + case symbols +} + /// Specifies a preference for where the layer should be inserted in the hierarchy. public enum LayerInsertionPosition: Equatable { /// The layer should be inserted above the layer with ID ``layerID``. /// /// If no such layer exists, the layer will be added above others and an error will be logged. - case above(layerID: String) + case above(LayerReferenceAbove) /// The layer should be inserted below the layer with ID ``layerID``. /// /// If no such layer exists, the layer will be added above others and an error will be logged. - case below(layerID: String) - /// The layer should be inserted above other existing layers. - case aboveOthers - /// The layer should be inserted below other existing layers. - case belowOthers + case below(LayerReferenceBelow) } /// Internal style enum that wraps a source reference. @@ -132,7 +146,7 @@ public extension StyleLayer { } } -public extension StyleLayer { +public extension StyleLayerDefinition { // MARK: - Common modifiers func visible(_ value: Bool) -> Self { @@ -147,29 +161,11 @@ public extension StyleLayer { modified(self) { $0.maximumZoomLevel = value } } - func renderAbove(_ layerID: String) -> Self { - modified(self) { $0.insertionPosition = .above(layerID: layerID) } - } - - func renderBelow(_ layerID: String) -> Self { - modified(self) { $0.insertionPosition = .below(layerID: layerID) } - } - - func renderAboveOthers() -> Self { - modified(self) { $0.insertionPosition = .aboveOthers } + func renderAbove(_ layerReference: LayerReferenceAbove) -> Self { + modified(self) { $0.insertionPosition = .above(layerReference) } } - func renderBelowOthers() -> Self { - modified(self) { $0.insertionPosition = .belowOthers } - } -} - -public extension StyleLayerDefinition { - func minimumZoomLevel(_ value: Float) -> Self { - modified(self) { $0.minimumZoomLevel = value } - } - - func maximumZoomLevel(_ value: Float) -> Self { - modified(self) { $0.maximumZoomLevel = value } + func renderBelow(_ layerReference: LayerReferenceBelow) -> Self { + modified(self) { $0.insertionPosition = .below(layerReference) } } } diff --git a/Sources/MapLibreSwiftDSL/Style Layers/Symbol.swift b/Sources/MapLibreSwiftDSL/Style Layers/Symbol.swift index 24b17a7..a3e322c 100644 --- a/Sources/MapLibreSwiftDSL/Style Layers/Symbol.swift +++ b/Sources/MapLibreSwiftDSL/Style Layers/Symbol.swift @@ -23,7 +23,7 @@ import MapLibreSwiftMacros public struct SymbolStyleLayer: SourceBoundVectorStyleLayerDefinition { public let identifier: String public let sourceLayerIdentifier: String? - public var insertionPosition: LayerInsertionPosition = .aboveOthers + public var insertionPosition: LayerInsertionPosition = .above(.all) public var isVisible: Bool = true public var maximumZoomLevel: Float? = nil public var minimumZoomLevel: Float? = nil diff --git a/Sources/MapLibreSwiftUI/Examples/Layers.swift b/Sources/MapLibreSwiftUI/Examples/Layers.swift index 281670c..f57205e 100644 --- a/Sources/MapLibreSwiftUI/Examples/Layers.swift +++ b/Sources/MapLibreSwiftUI/Examples/Layers.swift @@ -38,7 +38,7 @@ let clustered = ShapeSource(identifier: "points", options: [.clustered: true, .c // Silly example: a background layer on top of everything to create a tint effect BackgroundLayer(identifier: "rose-colored-glasses") .backgroundColor(.systemPink.withAlphaComponent(0.3)) - .renderAboveOthers() + .renderAbove(.all) } .ignoresSafeArea(.all) } diff --git a/Sources/MapLibreSwiftUI/Examples/Polyline.swift b/Sources/MapLibreSwiftUI/Examples/Polyline.swift index 3b5b6fa..f80e840 100644 --- a/Sources/MapLibreSwiftUI/Examples/Polyline.swift +++ b/Sources/MapLibreSwiftUI/Examples/Polyline.swift @@ -26,6 +26,7 @@ struct PolylineMapView: View { curveType: .exponential, parameters: NSExpression(forConstantValue: 1.5), stops: NSExpression(forConstantValue: [14: 6, 18: 24])) + .renderBelow(.symbols) // Add an inner (blue) polyline LineStyleLayer(identifier: "polyline-inner", source: polylineSource) @@ -36,6 +37,7 @@ struct PolylineMapView: View { curveType: .exponential, parameters: NSExpression(forConstantValue: 1.5), stops: NSExpression(forConstantValue: [14: 3, 18: 16])) + .renderBelow(.symbols) } } } diff --git a/Sources/MapLibreSwiftUI/MapViewCoordinator.swift b/Sources/MapLibreSwiftUI/MapViewCoordinator.swift index e629871..0157bc0 100644 --- a/Sources/MapLibreSwiftUI/MapViewCoordinator.swift +++ b/Sources/MapLibreSwiftUI/MapViewCoordinator.swift @@ -268,6 +268,10 @@ public class MapViewCoordinator: NSObject, MLNMapV } func addLayers(to mglStyle: MLNStyle) { + let firstSymbolLayer = mglStyle.layers.first { layer in + layer is MLNSymbolStyleLayer + } + for layerSpec in parent.userLayers { // DISCUSS: What preventions should we try to put in place against the user accidentally adding the same layer twice? let newLayer = layerSpec.makeStyleLayer(style: mglStyle).makeMLNStyleLayer() @@ -284,24 +288,30 @@ public class MapViewCoordinator: NSObject, MLNMapV } switch layerSpec.insertionPosition { - case let .above(layerID: id): + case let .above(.layer(layerId: id)): if let layer = mglStyle.layer(withIdentifier: id) { mglStyle.insertLayer(newLayer, above: layer) } else { NSLog("Failed to find layer with ID \(id). Adding layer on top.") mglStyle.addLayer(newLayer) } - case let .below(layerID: id): + case .above(.all): + mglStyle.addLayer(newLayer) + case let .below(.layer(layerId: id)): if let layer = mglStyle.layer(withIdentifier: id) { mglStyle.insertLayer(newLayer, below: layer) } else { NSLog("Failed to find layer with ID \(id). Adding layer on top.") mglStyle.addLayer(newLayer) } - case .aboveOthers: - mglStyle.addLayer(newLayer) - case .belowOthers: + case .below(.all): mglStyle.insertLayer(newLayer, at: 0) + case .below(.symbols): + if let firstSymbolLayer { + mglStyle.insertLayer(newLayer, below: firstSymbolLayer) + } else { + mglStyle.addLayer(newLayer) + } } } } diff --git a/Tests/MapLibreSwiftUITests/Examples/LayerPreviewTests.swift b/Tests/MapLibreSwiftUITests/Examples/LayerPreviewTests.swift index b1e98cb..6e0c816 100644 --- a/Tests/MapLibreSwiftUITests/Examples/LayerPreviewTests.swift +++ b/Tests/MapLibreSwiftUITests/Examples/LayerPreviewTests.swift @@ -30,7 +30,7 @@ final class LayerPreviewTests: XCTestCase { // Silly example: a background layer on top of everything to create a tint effect BackgroundLayer(identifier: "rose-colored-glasses") .backgroundColor(.systemPink.withAlphaComponent(0.3)) - .renderAboveOthers() + .renderAbove(.all) } } }