diff --git a/Sources/MapLibreSwiftUI/Examples/Layers.swift b/Sources/MapLibreSwiftUI/Examples/Layers.swift index 05b7013..27e09bd 100644 --- a/Sources/MapLibreSwiftUI/Examples/Layers.swift +++ b/Sources/MapLibreSwiftUI/Examples/Layers.swift @@ -123,6 +123,13 @@ let clustered = ShapeSource(identifier: "points", options: [.clustered: true, .c .iconColor(.white) .predicate(NSPredicate(format: "cluster != YES")) } + .onTapMapGesture(on: ["simple-circles-non-clusters"], onTapChanged: { _, features in + print("Tapped on \(features.first)") + }) + .expandClustersOnTapping(clusteredLayers: [ClusterLayer( + layerIdentifier: "simple-circles-clusters", + sourceIdentifier: "points" + )]) .ignoresSafeArea(.all) } diff --git a/Sources/MapLibreSwiftUI/Extensions/MapView/MapViewGestures.swift b/Sources/MapLibreSwiftUI/Extensions/MapView/MapViewGestures.swift index 3c46bf0..18b321c 100644 --- a/Sources/MapLibreSwiftUI/Extensions/MapView/MapViewGestures.swift +++ b/Sources/MapLibreSwiftUI/Extensions/MapView/MapViewGestures.swift @@ -58,6 +58,29 @@ extension MapView { return } + if let clusteredLayers { + if let gestureRecognizer = sender as? UITapGestureRecognizer, gestureRecognizer.numberOfTouches == 1 { + let point = gestureRecognizer.location(in: sender.view) + for clusteredLayer in clusteredLayers { + let features = mapView.visibleFeatures( + at: point, + styleLayerIdentifiers: [clusteredLayer.layerIdentifier] + ) + if let cluster = features.first as? MLNPointFeatureCluster, + let source = mapView.style? + .source(withIdentifier: clusteredLayer.sourceIdentifier) as? MLNShapeSource + { + let zoomLevel = source.zoomLevel(forExpanding: cluster) + + if zoomLevel > 0 { + mapView.setCenter(cluster.coordinate, zoomLevel: zoomLevel, animated: true) + break // since we can only zoom on one thing, we can abort the for loop here + } + } + } + } + } + // Process the gesture into a context response. let context = processContextFromGesture(mapView, gesture: gesture, sender: sender) // Run the context through the gesture held on the MapView (emitting to the MapView modifier). @@ -97,3 +120,14 @@ extension MapView { coordinate: mapView.convert(point, toCoordinateFrom: mapView)) } } + +/// Provides the layer identifier and it's source identifier. +public struct ClusterLayer { + public let layerIdentifier: String + public let sourceIdentifier: String + + public init(layerIdentifier: String, sourceIdentifier: String) { + self.layerIdentifier = layerIdentifier + self.sourceIdentifier = sourceIdentifier + } +} diff --git a/Sources/MapLibreSwiftUI/MapView.swift b/Sources/MapLibreSwiftUI/MapView.swift index b98e164..7dc92b4 100644 --- a/Sources/MapLibreSwiftUI/MapView.swift +++ b/Sources/MapLibreSwiftUI/MapView.swift @@ -28,6 +28,8 @@ public struct MapView: UIViewRepresentable { private var locationManager: MLNLocationManager? + var clusteredLayers: [ClusterLayer]? + public init( styleURL: URL, camera: Binding = .constant(.default()), diff --git a/Sources/MapLibreSwiftUI/MapViewModifiers.swift b/Sources/MapLibreSwiftUI/MapViewModifiers.swift index 63c093f..8b82d14 100644 --- a/Sources/MapLibreSwiftUI/MapViewModifiers.swift +++ b/Sources/MapLibreSwiftUI/MapViewModifiers.swift @@ -108,6 +108,18 @@ public extension MapView { return newMapView } + /// Add a default implementation for tapping clustered features. When tapped, the map zooms so that the cluster is + /// expanded. + /// - Parameter clusteredLayers: An array of layers to monitor that can contain clustered features. + /// - Returns: The modified MapView + func expandClustersOnTapping(clusteredLayers: [ClusterLayer]) -> MapView { + var newMapView = self + + newMapView.clusteredLayers = clusteredLayers + + return newMapView + } + func mapViewContentInset(_ inset: UIEdgeInsets) -> Self { var result = self