Skip to content

Commit

Permalink
Merge pull request #30 from hactar/enhancement/pat-gestures
Browse files Browse the repository at this point in the history
Feature Gestures
  • Loading branch information
ianthetechie authored Apr 11, 2024
2 parents a5a329a + ca182dc commit 6e0cf32
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 14 deletions.
32 changes: 32 additions & 0 deletions Sources/MapLibreSwiftUI/Examples/Gestures.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import CoreLocation
import MapLibre
import MapLibreSwiftDSL
import SwiftUI

#Preview("Tappable Circles") {
let tappableID = "simple-circles"
return MapView(styleURL: demoTilesURL) {
// Simple symbol layer demonstration with an icon
CircleStyleLayer(identifier: tappableID, source: pointSource)
.radius(16)
.color(.systemRed)
.strokeWidth(2)
.strokeColor(.white)

SymbolStyleLayer(identifier: "simple-symbols", source: pointSource)
.iconImage(UIImage(systemName: "mappin")!.withRenderingMode(.alwaysTemplate))
.iconColor(.white)
}
.onTapMapGesture(on: [tappableID], onTapChanged: { _, features in
print("Tapped on \(features.first)")
})
.ignoresSafeArea(.all)
}

#Preview("Tappable Countries") {
MapView(styleURL: demoTilesURL)
.onTapMapGesture(on: ["countries-fill"], onTapChanged: { _, features in
print("Tapped on \(features.first)")
})
.ignoresSafeArea(.all)
}
20 changes: 19 additions & 1 deletion Sources/MapLibreSwiftUI/Extensions/MapView/MapViewGestures.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@ extension MapView {
let gestureRecognizer = UITapGestureRecognizer(target: context.coordinator,
action: #selector(context.coordinator.captureGesture(_:)))
gestureRecognizer.numberOfTapsRequired = numberOfTaps
if numberOfTaps == 1 {
// If a user double taps to zoom via the built in gesture, a normal
// tap should not be triggered.
if let doubleTapRecognizer = mapView.gestureRecognizers?
.first(where: {
$0 is UITapGestureRecognizer && ($0 as! UITapGestureRecognizer).numberOfTapsRequired == 2
})
{
gestureRecognizer.require(toFail: doubleTapRecognizer)
}
}
mapView.addGestureRecognizer(gestureRecognizer)
gesture.gestureRecognizer = gestureRecognizer

Expand Down Expand Up @@ -50,7 +61,14 @@ extension MapView {
// 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).
gesture.onChange(context)
switch gesture.onChange {
case let .context(action):
action(context)
case let .feature(action, layers):
let point = sender.location(in: sender.view)
let features = mapView.visibleFeatures(at: point, styleLayerIdentifiers: layers)
action(context, features)
}
}

/// Convert the sender data into a MapGestureContext
Expand Down
1 change: 1 addition & 0 deletions Sources/MapLibreSwiftUI/MapView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public struct MapView: UIViewRepresentable {
let userLayers: [StyleLayerDefinition]

var gestures = [MapGesture]()

var onStyleLoaded: ((MLNStyle) -> Void)?

public var mapViewContentInset: UIEdgeInsets = .zero
Expand Down
30 changes: 27 additions & 3 deletions Sources/MapLibreSwiftUI/MapViewModifiers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ public extension MapView {
///
/// - Parameters:
/// - count: The number of taps required to run the gesture.
/// - onTapChanged: Emits the context whenever the gesture changes (e.g. began, ended, etc).
/// - onTapChanged: Emits the context whenever the gesture changes (e.g. began, ended, etc), that also contains
/// information like the latitude and longitude of the tap.
/// - Returns: The modified map view.
func onTapMapGesture(count: Int = 1,
onTapChanged: @escaping (MapGestureContext) -> Void) -> MapView
Expand All @@ -59,7 +60,30 @@ public extension MapView {

// Build the gesture and link it to the map view.
let gesture = MapGesture(method: .tap(numberOfTaps: count),
onChange: onTapChanged)
onChange: .context(onTapChanged))
newMapView.gestures.append(gesture)

return newMapView
}

/// Add an tap gesture handler to the MapView that returns any visible map features that were tapped.
///
/// - Parameters:
/// - count: The number of taps required to run the gesture.
/// - on layers: The set of layer ids that you would like to check for visible features that were tapped. If no
/// set is provided, all map layers are checked.
/// - onTapChanged: Emits the context whenever the gesture changes (e.g. began, ended, etc), that also contains
/// information like the latitude and longitude of the tap. Also emits an array of map features that were tapped.
/// Returns an empty array when nothing was tapped on the "on" layer ids that were provided.
/// - Returns: The modified map view.
func onTapMapGesture(count: Int = 1, on layers: Set<String>?,
onTapChanged: @escaping (MapGestureContext, [any MLNFeature]) -> Void) -> MapView
{
var newMapView = self

// Build the gesture and link it to the map view.
let gesture = MapGesture(method: .tap(numberOfTaps: count),
onChange: .feature(onTapChanged, layers: layers))
newMapView.gestures.append(gesture)

return newMapView
Expand All @@ -78,7 +102,7 @@ public extension MapView {

// Build the gesture and link it to the map view.
let gesture = MapGesture(method: .longPress(minimumDuration: minimumDuration),
onChange: onPressChanged)
onChange: .context(onPressChanged))
newMapView.gestures.append(gesture)

return newMapView
Expand Down
10 changes: 8 additions & 2 deletions Sources/MapLibreSwiftUI/Models/Gesture/MapGesture.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import MapLibre
import UIKit

public class MapGesture: NSObject {
Expand All @@ -19,7 +20,7 @@ public class MapGesture: NSObject {
let method: Method

/// The onChange action that runs when the gesture changes on the map view.
let onChange: (MapGestureContext) -> Void
let onChange: GestureAction

/// The underlying gesture recognizer
weak var gestureRecognizer: UIGestureRecognizer?
Expand All @@ -29,8 +30,13 @@ public class MapGesture: NSObject {
/// - Parameters:
/// - method: The gesture recognizer method
/// - onChange: The action to perform when the gesture is changed
init(method: Method, onChange: @escaping (MapGestureContext) -> Void) {
init(method: Method, onChange: GestureAction) {
self.method = method
self.onChange = onChange
}
}

public enum GestureAction {
case context((MapGestureContext) -> Void)
case feature((MapGestureContext, [any MLNFeature]) -> Void, layers: Set<String>?)
}
8 changes: 4 additions & 4 deletions Tests/MapLibreSwiftUITests/MapView/MapViewGestureTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ final class MapViewGestureTests: XCTestCase {
// MARK: Gesture Processing

@MainActor func testTapGesture() {
let gesture = MapGesture(method: .tap(numberOfTaps: 2)) { _ in
let gesture = MapGesture(method: .tap(numberOfTaps: 2), onChange: .context { _ in
// Do nothing
}
})

let mockTapGesture = MockUIGestureRecognizing()

Expand All @@ -53,9 +53,9 @@ final class MapViewGestureTests: XCTestCase {
}

@MainActor func testLongPressGesture() {
let gesture = MapGesture(method: .longPress(minimumDuration: 1)) { _ in
let gesture = MapGesture(method: .longPress(minimumDuration: 1), onChange: .context { _ in
// Do nothing
}
})

let mockTapGesture = MockUIGestureRecognizing()

Expand Down
16 changes: 12 additions & 4 deletions Tests/MapLibreSwiftUITests/Models/Gesture/MapGestureTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,39 @@ import XCTest
final class MapGestureTests: XCTestCase {
func testTapGestureDefaults() {
let gesture = MapGesture(method: .tap(),
onChange: { _ in })
onChange: .context { _ in

})

XCTAssertEqual(gesture.method, .tap())
XCTAssertNil(gesture.gestureRecognizer)
}

func testTapGesture() {
let gesture = MapGesture(method: .tap(numberOfTaps: 3),
onChange: { _ in })
onChange: .context { _ in

})

XCTAssertEqual(gesture.method, .tap(numberOfTaps: 3))
XCTAssertNil(gesture.gestureRecognizer)
}

func testLongPressGestureDefaults() {
let gesture = MapGesture(method: .longPress(),
onChange: { _ in })
onChange: .context { _ in

})

XCTAssertEqual(gesture.method, .longPress())
XCTAssertNil(gesture.gestureRecognizer)
}

func testLongPressGesture() {
let gesture = MapGesture(method: .longPress(minimumDuration: 3),
onChange: { _ in })
onChange: .context { _ in

})

XCTAssertEqual(gesture.method, .longPress(minimumDuration: 3))
XCTAssertNil(gesture.gestureRecognizer)
Expand Down

0 comments on commit 6e0cf32

Please sign in to comment.