Skip to content

Commit

Permalink
Allow implementors to add custom TapGestures
Browse files Browse the repository at this point in the history
The current MLNMapView supports implementers adding their own TapGesture,
and even documents how:

```swift
let mapTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(myCustomFunction))
for recognizer in mapView.gestureRecognizers! where recognizer is UITapGestureRecognizer {
  mapTapGestureRecognizer.require(toFail: recognizer)
}
mapView.addGestureRecognizer(mapTapGestureRecognizer)
```

Unfortunately this doesn't work with NavigationMapView, which has a
catch-all tap gesture the greedily consumes all taps.

With this change, we will fail the gesture unless the NavigationMapView
intends to handle it.

Unfortunately this presents some redundant work, but I've rearranged
things so that the redundant work only occurs in the case that the user
has successfully selected a Route or WayPoint.
  • Loading branch information
michaelkirk committed Jun 12, 2024
1 parent 0d7cd71 commit ef73edf
Showing 1 changed file with 51 additions and 10 deletions.
61 changes: 51 additions & 10 deletions MapboxNavigation/NavigationMapView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -224,8 +224,8 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate {
self.resumeNotifications()

let gestures = gestureRecognizers ?? []
let mapTapGesture = mapTapGesture
mapTapGesture.requireFailure(of: gestures)
mapTapGesture.delegate = self
addGestureRecognizer(mapTapGesture)
}

Expand Down Expand Up @@ -362,20 +362,61 @@ open class NavigationMapView: MLNMapView, UIGestureRecognizerDelegate {
}

// MARK: - Gesture Recognizers


// If we aren't going to handle mapTapGesture, we should fail it, so that gesture recognizers
// added by the user can succeed.
//
// This logic needs to be kept in sync with `didRecieveTap(sender:)`
override open func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
guard gestureRecognizer == self.mapTapGesture else {
return super.gestureRecognizerShouldBegin(gestureRecognizer)
}

guard let routes,
let tapPoint = gestureRecognizer.point,
let navigationMapDelegate = self.navigationMapDelegate else {
return false
}

// `as` casting is needed to disambiguate the overloaded didSelect: methods
if (navigationMapDelegate.navigationMapView(_:didSelect:) as ((NavigationMapView, Waypoint) -> Void)?) != nil,
self.waypoints(on: routes, closeTo: tapPoint)?.first != nil {
return true
// `as` casting is needed to disambiguate the overloaded didSelect: methods
} else if (navigationMapDelegate.navigationMapView(_:didSelect:) as ((NavigationMapView, Route) -> Void)?) != nil,
self.routes(closeTo: tapPoint)?.first != nil {
return true
} else {
return false
}
}

/**
Fired when NavigationMapView detects a tap not handled elsewhere by other gesture recognizers.
*/
@objc func didRecieveTap(sender: UITapGestureRecognizer) {
guard let routes, let tapPoint = sender.point else { return }

let waypointTest = self.waypoints(on: routes, closeTo: tapPoint) // are there waypoints near the tapped location?
if let selected = waypointTest?.first { // test passes
self.navigationMapDelegate?.navigationMapView?(self, didSelect: selected)
assert(sender == self.mapTapGesture)
guard let routes,
let tapPoint = sender.point,
let navigationMapDelegate = self.navigationMapDelegate else {
assertionFailure("Gesture shouldn't trigger for no-op. Does gestureRecognizerShouldBegin need to be updated?")
return
} else if let routes = self.routes(closeTo: tapPoint) {
guard let selectedRoute = routes.first else { return }
self.navigationMapDelegate?.navigationMapView?(self, didSelect: selectedRoute)
}

if let tappedWaypoint = self.waypoints(on: routes, closeTo: tapPoint)?.first {
guard let didSelectWaypoint: (NavigationMapView, Waypoint) -> Void = navigationMapDelegate.navigationMapView(_:didSelect:) else {
assertionFailure("Gesture shouldn't trigger for no-op. Does gestureRecognizerShouldBegin need to be updated?")
return
}
didSelectWaypoint(self, tappedWaypoint)
} else if let tappedRoute = self.routes(closeTo: tapPoint)?.first {
guard let didSelectRoute: (NavigationMapView, Route) -> Void = navigationMapDelegate.navigationMapView(_:didSelect:) else {
assertionFailure("Gesture shouldn't trigger for no-op. Does gestureRecognizerShouldBegin need to be updated?")
return
}
didSelectRoute(self, tappedRoute)
} else {
assertionFailure("Gesture shouldn't trigger for no-op. Does gestureRecognizerShouldBegin need to be updated?")
}
}

Expand Down

0 comments on commit ef73edf

Please sign in to comment.