Skip to content

Commit 1ed6af6

Browse files
persidskiygithub-actions[bot]
authored andcommitted
Fix issue when trigger map tap below view annotations (#13924)
Fixes bug: when user taps on view annotaiton, the map view is also receives the tap and closes annotation Fixes https://mapbox.atlassian.net/browse/MAPSIOS-2202 Introduced in mapbox/mapbox-sdk@e1ef22e GitOrigin-RevId: bf99a8fa45a6c325f81aa0b620325210c204b03b
1 parent 3b77282 commit 1ed6af6

5 files changed

Lines changed: 86 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ Mapbox welcomes participation and contributions from everyone.
44

55
## main
66

7+
### Bug fixes 🐞
8+
* Fix bug when tap on a view annotation triggered tap handler on Map view itself.
9+
710
## 11.24.2 - 20 May, 2026
811

912
## 11.24.1 - 19 May, 2026

Sources/MapboxMaps/Gestures/GestureHandlers/DoubleTapToZoomInGestureHandler.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,6 @@ extension DoubleTapToZoomInGestureHandler: UIGestureRecognizerDelegate {
4646
shouldReceive touch: UITouch
4747
) -> Bool {
4848
assert(self.gestureRecognizer == gestureRecognizer)
49-
return gestureRecognizer.shouldAllowMapGesture(for: touch)
49+
return gestureRecognizer.attachedToSameView(as: touch)
5050
}
5151
}

Sources/MapboxMaps/Gestures/GestureHandlers/QuickZoomGestureHandler.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,6 @@ extension QuickZoomGestureHandler: UIGestureRecognizerDelegate {
5656
shouldReceive touch: UITouch
5757
) -> Bool {
5858
assert(self.gestureRecognizer == gestureRecognizer)
59-
return gestureRecognizer.shouldAllowMapGesture(for: touch)
59+
return gestureRecognizer.attachedToSameView(as: touch)
6060
}
6161
}

Sources/MapboxMaps/Gestures/GestureHandlers/SingleTapGestureHandler.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,6 @@ extension SingleTapGestureHandler: UIGestureRecognizerDelegate {
4545
shouldReceive touch: UITouch
4646
) -> Bool {
4747
assert(self.gestureRecognizer == gestureRecognizer)
48-
return gestureRecognizer.shouldAllowMapGesture(for: touch)
48+
return gestureRecognizer.attachedToSameView(as: touch)
4949
}
5050
}

Tests/GestureTests/SingleTapGesture.test.swift

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,84 @@ final class SingleTapGestureTestCase: GestureTestCase {
2424

2525
await fulfillment(of: [setupExpectation], timeout: 10)
2626
}
27+
28+
/// Regression test for the SwiftUI `Map.onMapTapGesture` firing when a user taps
29+
/// a `MapViewAnnotation`. ViewAnnotations must be transparent to drag-like gestures
30+
/// (pan/pinch/rotate) but opaque to single taps.
31+
func testSingleTapOnViewAnnotationDoesNotPropagateToMap() async throws {
32+
mapView.mapboxMap.loadStyle(.standard)
33+
34+
let setupExpectation = expectation(description: "Map setup")
35+
didBecomeIdle = { [weak self] mapView in
36+
guard let self else { return }
37+
// Prevent re-entry on subsequent idle events.
38+
didBecomeIdle = nil
39+
40+
let annotationView = UIView(frame: CGRect(x: 0, y: 0, width: 80, height: 80))
41+
annotationView.backgroundColor = .red
42+
let annotation = ViewAnnotation(coordinate: camera.center, view: annotationView)
43+
annotation.allowOverlap = true
44+
45+
let visibleExpectation = expectation(description: "View annotation became visible")
46+
annotation.onVisibilityChanged = { visible in
47+
if visible { visibleExpectation.fulfill() }
48+
}
49+
mapView.viewAnnotations.add(annotation)
50+
wait(for: [visibleExpectation], timeout: 5)
51+
52+
let onAnnotationTap = expectation(
53+
description: "map tap must NOT fire when the tap targets a view annotation"
54+
)
55+
onAnnotationTap.isInverted = true
56+
let offMapTap = expectation(
57+
description: "map tap must fire when the tap lands outside the view annotation"
58+
)
59+
var didTapAnnotation = false
60+
mapView.mapboxMap.addInteraction(TapInteraction { _ in
61+
if didTapAnnotation {
62+
offMapTap.fulfill()
63+
} else {
64+
onAnnotationTap.fulfill()
65+
}
66+
return true
67+
})
68+
69+
try! eventGenerator.fingerTap(.rightIndex, at: annotationView)
70+
wait(for: [onAnnotationTap], timeout: 2)
71+
72+
didTapAnnotation = true
73+
// Tap well clear of the 80x80 annotation centered on the screen.
74+
try! eventGenerator.fingerTap(.rightIndex, at: OffsetLocation(location: annotationView, x: 120, y: 0))
75+
wait(for: [offMapTap], timeout: 5)
76+
77+
setupExpectation.fulfill()
78+
}
79+
80+
await fulfillment(of: [setupExpectation], timeout: 30)
81+
}
82+
83+
/// Sanity check: tapping the bare map (no annotation in the way) still fires `onMapTap`.
84+
/// Guards against an over-correction that would silence single taps entirely.
85+
func testSingleTapOnBareMapFiresOnMapTap() async throws {
86+
mapView.mapboxMap.loadStyle(.standard)
87+
88+
let setupExpectation = expectation(description: "Map setup")
89+
didBecomeIdle = { [weak self] mapView in
90+
guard let self else { return }
91+
didBecomeIdle = nil
92+
93+
let mapTapExpectation = expectation(description: "map tap fires on bare map tap")
94+
mapView.mapboxMap.addInteraction(TapInteraction { _ in
95+
mapTapExpectation.fulfill()
96+
return true
97+
})
98+
99+
try! eventGenerator.fingerTap(.rightIndex)
100+
101+
wait(for: [mapTapExpectation], timeout: 5)
102+
setupExpectation.fulfill()
103+
}
104+
105+
await fulfillment(of: [setupExpectation], timeout: 30)
106+
}
27107
}

0 commit comments

Comments
 (0)