Skip to content

Commit e4f30f2

Browse files
authored
Add support for custom tappable area in Interaction (#2364)
1 parent d061b47 commit e4f30f2

File tree

24 files changed

+482
-56
lines changed

24 files changed

+482
-56
lines changed

Apps/Examples/Examples.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@
103103
A6389C28B8AAC39878591AD0 /* PitchAndDistanceExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A5C0A3C44715B96D646ACB7 /* PitchAndDistanceExample.swift */; platformFilters = (ios, ); };
104104
A6A68B4ED674A924ACBD8FA2 /* UIColor+Random.swift in Sources */ = {isa = PBXBuildFile; fileRef = F000C4D3B6FC70FA9607E3A3 /* UIColor+Random.swift */; platformFilters = (ios, ); };
105105
A972D3306BC53DEC9798C60D /* ExternalVectorSourceExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 133E4EABC7540ED460F08B8F /* ExternalVectorSourceExample.swift */; platformFilters = (ios, ); };
106+
AD0922FA7F69AEE4C23F2351 /* InteractionsPlayground.swift in Sources */ = {isa = PBXBuildFile; fileRef = 388932B3A65BB7E9B59FDBE0 /* InteractionsPlayground.swift */; };
106107
AE51E276DCD8CF89AB339224 /* LongTapAnimationExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DB76F486D80FED88678B04D /* LongTapAnimationExample.swift */; platformFilters = (ios, ); };
107108
AE6E90DB7B6DA4580C2DAB59 /* FrameViewAnnotationsExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FC0F09FF1EF1A88BC1C6545 /* FrameViewAnnotationsExample.swift */; platformFilters = (ios, ); };
108109
B304BACFCD08802A740E8919 /* PuckPlayground.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0CC67084BA1191D0B179A94 /* PuckPlayground.swift */; };
@@ -204,6 +205,7 @@
204205
3595A6E8FB1FD9F41DEB5C6F /* AnnotationsOrderTestExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnnotationsOrderTestExample.swift; sourceTree = "<group>"; };
205206
370DFCA52EB6C7F119BF81DA /* MapEventsExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapEventsExample.swift; sourceTree = "<group>"; };
206207
384FD8FC97B9F5011AF4BD61 /* GlobeFlyToExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobeFlyToExample.swift; sourceTree = "<group>"; };
208+
388932B3A65BB7E9B59FDBE0 /* InteractionsPlayground.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InteractionsPlayground.swift; sourceTree = "<group>"; };
207209
3BCB1CC4577300FEF4DE017B /* InsetMapExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsetMapExample.swift; sourceTree = "<group>"; };
208210
3BDE7738CA55957F3FAC3ECE /* LineAnnotationExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineAnnotationExample.swift; sourceTree = "<group>"; };
209211
3E2F68B22AFF73A71F86CABC /* ExamplesUITests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = ExamplesUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -341,6 +343,7 @@
341343
children = (
342344
3595A6E8FB1FD9F41DEB5C6F /* AnnotationsOrderTestExample.swift */,
343345
7A77AEDBF679F223D4412FEE /* AttributionDialogueExamples.swift */,
346+
388932B3A65BB7E9B59FDBE0 /* InteractionsPlayground.swift */,
344347
65535FB9F190778001AB847A /* MapScrollExample.swift */,
345348
E6E875420B5C674C8CCAB9B1 /* MapSettingsExample.swift */,
346349
C0CC67084BA1191D0B179A94 /* PuckPlayground.swift */,
@@ -877,6 +880,7 @@
877880
B9B1EE72E6203358F2785916 /* IconSizeChangeExample.swift in Sources */,
878881
423A42B555DD0B3AD4856FCF /* InsetMapExample.swift in Sources */,
879882
94DB7E8C829041DC5F5B2300 /* InstrumentClusterCarPlaySceneDelegate.swift in Sources */,
883+
AD0922FA7F69AEE4C23F2351 /* InteractionsPlayground.swift in Sources */,
880884
8B4085733CCABE3BE3D16F7E /* LayerPositionExample.swift in Sources */,
881885
918F4BDCC25819DD68BC9518 /* LayerSlotExample.swift in Sources */,
882886
C04160BF66055F7DE9315395 /* Lights3DExample.swift in Sources */,

Apps/Examples/Examples/SwiftUI Examples/SwiftUIRoot.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ struct SwiftUIRoot: View {
4141

4242
Section {
4343
ExampleLink("Map settings", note: "Showcase of the most possible map configurations.", destination: MapSettingsExample())
44+
ExampleLink("Interactions playground", note: "Interactions edge cases", destination: InteractionsPlayground())
4445
ExampleLink("Viewport Playground", note: "Showcase of the possible viewport states.", destination: ViewportPlayground())
4546
ExampleLink("Puck playground", note: "Display user location using puck.", destination: PuckPlayground())
4647
ExampleLink("Annotation Order", destination: AnnotationsOrderTestExample())
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
@_spi(Experimental) import MapboxMaps
2+
import SwiftUI
3+
4+
@available(iOS 14.0, *)
5+
struct InteractionsPlayground: View {
6+
@State private var text: String?
7+
@State private var tap: Tap?
8+
@State private var mapTap: Tap?
9+
@State private var disableTerrain: Bool = false
10+
11+
@State private var routes = [
12+
Route(line: LineString(route1), isActive: true),
13+
Route(line: LineString(route2), isActive: false)
14+
]
15+
16+
var body: some View {
17+
ZStack {
18+
let cameraCenter = CLLocationCoordinate2D(latitude: 60.1718, longitude: 24.9453)
19+
Map(initialViewport: .camera(center: cameraCenter, zoom: 16.35, bearing: 49.92, pitch: 40)) {
20+
21+
let polygon = Polygon(center: .helsinki, radius: 200, vertices: 30)
22+
PolygonAnnotationGroup {
23+
PolygonAnnotation(polygon: polygon)
24+
.fillColor(.green)
25+
.fillOpacity(0.2)
26+
.onTapGesture { _ in
27+
text = "Polygon tap"
28+
return true
29+
}
30+
}
31+
.layerId("polygon")
32+
.slot("bottom")
33+
34+
PolylineAnnotationGroup(routes) { route in
35+
PolylineAnnotation(lineString: route.line)
36+
.lineColor(route.isActive ? "#57A9FB" : "gray")
37+
.lineBorderColor(route.isActive ? "#327AC2" : "black")
38+
.lineSortKey(route.isActive ? 1 : 0)
39+
.onTapGesture { ctx in
40+
text = "Tap route"
41+
tap = Tap(pos: ctx.point, radius: 22)
42+
43+
let id = route.id
44+
routes = routes.map { route in
45+
var r = route
46+
r.isActive = route.id == id
47+
return r
48+
}
49+
return true
50+
}
51+
}
52+
.tapRadius(22)
53+
.lineWidth(10)
54+
.lineBorderWidth(2)
55+
.lineCap(.round)
56+
.slot("middle")
57+
58+
TapInteraction(.standardPoi, radius: 0) { feature, ctx in
59+
text = "Tap poi \(feature.name ?? "-"), r: 0"
60+
tap = Tap(pos: ctx.point)
61+
return true
62+
}
63+
64+
TapInteraction(.standardPoi, radius: 8) { feature, ctx in
65+
text = "Tap poi \(feature.name ?? "-"), r: 8"
66+
tap = Tap(pos: ctx.point, radius: 8)
67+
return true
68+
}
69+
70+
TapInteraction(.standardPlaceLabels, radius: 10) { feature, ctx in
71+
text = "Tap place \(feature.name ?? "-"), r: 10"
72+
tap = Tap(pos: ctx.point, radius: 10)
73+
return true
74+
}
75+
76+
if disableTerrain {
77+
Terrain(sourceId: "fake")
78+
}
79+
TapInteraction { ctx in
80+
mapTap = Tap(pos: ctx.point)
81+
text = nil
82+
tap = nil
83+
return true
84+
}
85+
}
86+
.debugOptions(.collision)
87+
.mapStyle(.standardExperimental)
88+
89+
GeometryReader { _ in
90+
if let mapTap {
91+
TapView(tap: mapTap, color: .blue)
92+
.id(mapTap.id)
93+
}
94+
if let tap {
95+
TapView(tap: tap, color: .red)
96+
.id(tap.id)
97+
}
98+
}
99+
}
100+
.ignoresSafeArea()
101+
.safeOverlay(alignment: .bottom) {
102+
VStack {
103+
if let text {
104+
Text(text)
105+
.floating()
106+
}
107+
HStack {
108+
Text("Disable Terrain")
109+
Toggle("dis", isOn: $disableTerrain)
110+
}.floating()
111+
}
112+
}
113+
114+
}
115+
}
116+
117+
private struct Route: Identifiable {
118+
var id = UUID()
119+
var line: LineString
120+
var isActive: Bool
121+
}
122+
123+
private let route1 = [
124+
CLLocationCoordinate2D(latitude: 60.17047709327494, longitude: 24.94189274671095),
125+
CLLocationCoordinate2D(latitude: 60.17057890370404, longitude: 24.944958457828335),
126+
CLLocationCoordinate2D(latitude: 60.17190499730512, longitude: 24.945178540794018),
127+
CLLocationCoordinate2D(latitude: 60.172111309514946, longitude: 24.9469168488161)
128+
]
129+
130+
private let route2 = [
131+
CLLocationCoordinate2D(latitude: 60.17048155574875, longitude: 24.941910494113273),
132+
CLLocationCoordinate2D(latitude: 60.170578004847926, longitude: 24.9449704567908),
133+
CLLocationCoordinate2D(latitude: 60.17134054556911, longitude: 24.94735783361179),
134+
CLLocationCoordinate2D(latitude: 60.17159974510375, longitude: 24.947563850901645)
135+
]
136+
137+
private struct Tap {
138+
var id = UUID()
139+
var pos: CGPoint
140+
var radius: Double?
141+
}
142+
143+
@available(iOS 14.0, *)
144+
private struct TapView: View {
145+
var tap: Tap
146+
var color: Color
147+
@State var opacity = 1.0
148+
var body: some View {
149+
let r = tap.radius ?? 3
150+
ZStack {
151+
if tap.radius != nil {
152+
Rectangle()
153+
.stroke(color, lineWidth: 2) // Set border color and width
154+
}
155+
156+
Circle()
157+
.fill(color)
158+
.frame(width: 6, height: 6)
159+
}
160+
.frame(width: r * 2, height: r * 2)
161+
.offset(x: tap.pos.x - r, y: tap.pos.y - r)
162+
.opacity(opacity)
163+
.onAppear {
164+
withAnimation(.easeInOut(duration: 2)) {
165+
opacity = 0
166+
}
167+
}
168+
}
169+
}

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ In order to continue use them use the following import `@_spi(Experimental) impo
1616
* Fix a crash on calling `LocationIndicatorLayer/location(coordinate:) function` due to missing 0 altitude value.
1717
* Add a new Expression initializer `init(_ operator: Operator, _ arguments: ExpressionArgumentConvertible...)` to simplify the creation of expressions with multiple arguments.
1818
That initializer doesn't require to wrap arguments in `Argument` cases. For example, `Exp(.eq, Exp(.get, "extrude"), "true")`.
19+
* Add new experimental `radius` parameter to `TapInteraction`, `LongPressInteraction` and interaction managers to control the radius of a tappable area.
1920

2021
## 11.8.0 - 11 November, 2024
2122

MapboxMaps.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Pod::Spec.new do |m|
2121
m.source_files = 'Sources/MapboxMaps/**/*.{swift,h}'
2222
m.resource_bundles = { 'MapboxMapsResources' => ['Sources/**/*.{xcassets,strings}', 'Sources/MapboxMaps/MapboxMaps.json', 'Sources/MapboxMaps/PrivacyInfo.xcprivacy'] }
2323

24-
m.dependency 'MapboxCoreMaps', '11.9.0-SNAPSHOT.1112T1941Z.8130abf'
24+
m.dependency 'MapboxCoreMaps', '11.9.0-SNAPSHOT.1122T1014Z.b9d442a'
2525
m.dependency 'MapboxCommon', '24.9.0-SNAPSHOT.1112T0225Z.a361369'
2626
m.dependency 'Turf', '4.0.0-beta.1'
2727

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import PackageDescription
55
import Foundation
66

7-
let coreMaps = MapsDependency.coreMaps(version: "11.9.0-SNAPSHOT.1112T1941Z.8130abf", checksum: "0ef6877a48fbd153ab2d4f5605234d44b344d4089e5190f0d216bc79e8b55339")
7+
let coreMaps = MapsDependency.coreMaps(version: "11.9.0-SNAPSHOT.1122T1014Z.b9d442a", checksum: "ec2407a32601b0fca1c36fffb10707214473091e8bab2992f74fd2185a480f83")
88
let common = MapsDependency.common(version: "24.9.0-SNAPSHOT.1112T0225Z.a361369", checksum: "e828f210cc591daf087206066d5117c0d4c10e8ec07c233ab1cd8d800a834a63")
99

1010
let mapboxMapsPath: String? = nil

Sources/MapboxMaps/Annotations/AnnotationManagerImpl.swift

Lines changed: 48 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -339,48 +339,62 @@ final class AnnotationManagerImpl<AnnotationType: Annotation & AnnotationInterna
339339
var dragTokens: TokenPair?
340340
var clusterTokens: TokenPair?
341341

342+
var tapRadius: CGFloat? {
343+
didSet { updateTapHandlers(force: tapRadius != oldValue) }
344+
}
345+
346+
var longPressRadius: CGFloat? {
347+
didSet { updateLongPressHandlers(force: longPressRadius != oldValue) }
348+
}
349+
342350
private var handlesTaps = false {
343-
didSet {
344-
if handlesTaps {
345-
if tapTokens == nil {
346-
tapTokens = (
347-
mapboxMap.addInteraction(tapInteraction(layerId: id)).erased,
348-
mapboxMap.addInteraction(tapInteraction(layerId: dragId)).erased
349-
)
350-
}
351-
} else {
352-
tapTokens = nil
351+
didSet { updateTapHandlers() }
352+
}
353+
354+
private var handlesLongPress = false {
355+
didSet { updateLongPressHandlers() }
356+
}
357+
358+
private var handlesDrag = false {
359+
didSet { updateDragHandlers() }
360+
}
361+
362+
private func updateTapHandlers(force: Bool = false) {
363+
if handlesTaps {
364+
if tapTokens == nil || force {
365+
tapTokens = (
366+
mapboxMap.addInteraction(tapInteraction(layerId: id)).erased,
367+
mapboxMap.addInteraction(tapInteraction(layerId: dragId)).erased
368+
)
353369
}
370+
} else {
371+
tapTokens = nil
354372
}
355373
}
356374

357-
private var handlesLongPress = false {
358-
didSet {
359-
if handlesLongPress {
360-
if longPressTokens == nil {
361-
longPressTokens = (
362-
mapboxMap.addInteraction(longPressInteraction(layerId: id)).erased,
363-
mapboxMap.addInteraction(longPressInteraction(layerId: dragId)).erased
364-
)
365-
}
366-
} else {
367-
longPressTokens = nil
375+
private func updateLongPressHandlers(force: Bool = false) {
376+
if handlesLongPress {
377+
if longPressTokens == nil || force {
378+
longPressTokens = (
379+
mapboxMap.addInteraction(longPressInteraction(layerId: id)).erased,
380+
mapboxMap.addInteraction(longPressInteraction(layerId: dragId)).erased
381+
)
368382
}
383+
} else {
384+
longPressTokens = nil
369385
}
370386
}
371387

372-
private var handlesDrag = false {
373-
didSet {
374-
if handlesDrag {
375-
if dragTokens == nil {
376-
dragTokens = (
377-
mapboxMap.addInteraction(dragInteraction(layerId: id)).erased,
378-
mapboxMap.addInteraction(dragInteraction(layerId: dragId)).erased
379-
)
380-
}
381-
} else {
382-
dragTokens = nil
388+
private func updateDragHandlers(force: Bool = false) {
389+
if handlesDrag {
390+
if dragTokens == nil || force {
391+
dragTokens = (
392+
mapboxMap.addInteraction(dragInteraction(layerId: id)).erased,
393+
mapboxMap.addInteraction(dragInteraction(layerId: dragId)).erased
394+
)
383395
}
396+
} else {
397+
dragTokens = nil
384398
}
385399
}
386400

@@ -406,7 +420,7 @@ final class AnnotationManagerImpl<AnnotationType: Annotation & AnnotationInterna
406420
}
407421

408422
private func tapInteraction(layerId: String) -> TapInteraction {
409-
return TapInteraction(.layer(layerId)) { [weak self] feature, context in
423+
return TapInteraction(.layer(layerId), radius: tapRadius) { [weak self] feature, context in
410424
guard
411425
let self,
412426
let featureId = feature.id?.id else { return false }
@@ -430,7 +444,7 @@ final class AnnotationManagerImpl<AnnotationType: Annotation & AnnotationInterna
430444
}
431445

432446
private func longPressInteraction(layerId: String) -> LongPressInteraction {
433-
LongPressInteraction(.layer(layerId)) { [weak self] feature, context in
447+
LongPressInteraction(.layer(layerId), radius: longPressRadius) { [weak self] feature, context in
434448
self?.annotations.first { $0.id == feature.id?.id }?.longPressHandler?(context) ?? false
435449
}
436450
}

Sources/MapboxMaps/Annotations/Generated/CircleAnnotationManager.swift

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Sources/MapboxMaps/Annotations/Generated/PointAnnotationManager.swift

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)