diff --git a/Sources/MapboxMaps/Annotations/AnnotationOrchestrator.swift b/Sources/MapboxMaps/Annotations/AnnotationOrchestrator.swift index 1c487fbaae46..daca195eaf58 100644 --- a/Sources/MapboxMaps/Annotations/AnnotationOrchestrator.swift +++ b/Sources/MapboxMaps/Annotations/AnnotationOrchestrator.swift @@ -39,7 +39,7 @@ public protocol AnnotationInteractionDelegate: AnyObject { public class AnnotationOrchestrator { - private weak var view: UIView? + private weak var singleTapGestureRecognizer: UIGestureRecognizer? private let style: Style @@ -47,8 +47,11 @@ public class AnnotationOrchestrator { private weak var displayLinkCoordinator: DisplayLinkCoordinator? - internal init(view: UIView, mapFeatureQueryable: MapFeatureQueryable, style: Style, displayLinkCoordinator: DisplayLinkCoordinator) { - self.view = view + internal init(singleTapGestureRecognizer: UIGestureRecognizer, + mapFeatureQueryable: MapFeatureQueryable, + style: Style, + displayLinkCoordinator: DisplayLinkCoordinator) { + self.singleTapGestureRecognizer = singleTapGestureRecognizer self.mapFeatureQueryable = mapFeatureQueryable self.style = style self.displayLinkCoordinator = displayLinkCoordinator @@ -62,14 +65,14 @@ public class AnnotationOrchestrator { public func makePointAnnotationManager(id: String = String(UUID().uuidString.prefix(5)), layerPosition: LayerPosition? = nil) -> PointAnnotationManager { - guard let view = view, + guard let singleTapGestureRecognizer = singleTapGestureRecognizer, let displayLinkCoordinator = displayLinkCoordinator else { - fatalError("View and displayLinkCoordinator must be present when creating an annotation manager") + fatalError("SingleTapGestureRecognizer and displayLinkCoordinator must be present when creating an annotation manager") } return PointAnnotationManager(id: id, style: style, - view: view, + singleTapGestureRecognizer: singleTapGestureRecognizer, mapFeatureQueryable: mapFeatureQueryable, shouldPersist: true, layerPosition: layerPosition, @@ -84,14 +87,14 @@ public class AnnotationOrchestrator { public func makePolygonAnnotationManager(id: String = String(UUID().uuidString.prefix(5)), layerPosition: LayerPosition? = nil) -> PolygonAnnotationManager { - guard let view = view, + guard let singleTapGestureRecognizer = singleTapGestureRecognizer, let displayLinkCoordinator = displayLinkCoordinator else { - fatalError("View and displayLinkCoordinator must be present when creating an annotation manager") + fatalError("SingleTapGestureRecognizer and displayLinkCoordinator must be present when creating an annotation manager") } return PolygonAnnotationManager(id: id, style: style, - view: view, + singleTapGestureRecognizer: singleTapGestureRecognizer, mapFeatureQueryable: mapFeatureQueryable, shouldPersist: true, layerPosition: layerPosition, @@ -106,14 +109,14 @@ public class AnnotationOrchestrator { public func makePolylineAnnotationManager(id: String = String(UUID().uuidString.prefix(5)), layerPosition: LayerPosition? = nil) -> PolylineAnnotationManager { - guard let view = view, + guard let singleTapGestureRecognizer = singleTapGestureRecognizer, let displayLinkCoordinator = displayLinkCoordinator else { - fatalError("View and displayLinkCoordinator must be present when creating an annotation manager") + fatalError("SingleTapGestureRecognizer and displayLinkCoordinator must be present when creating an annotation manager") } return PolylineAnnotationManager(id: id, style: style, - view: view, + singleTapGestureRecognizer: singleTapGestureRecognizer, mapFeatureQueryable: mapFeatureQueryable, shouldPersist: true, layerPosition: layerPosition, @@ -128,14 +131,14 @@ public class AnnotationOrchestrator { public func makeCircleAnnotationManager(id: String = String(UUID().uuidString.prefix(5)), layerPosition: LayerPosition? = nil) -> CircleAnnotationManager { - guard let view = view, + guard let singleTapGestureRecognizer = singleTapGestureRecognizer, let displayLinkCoordinator = displayLinkCoordinator else { - fatalError("View and displayLinkCoordinator must be present when creating an annotation manager") + fatalError("SingleTapGestureRecognizer and displayLinkCoordinator must be present when creating an annotation manager") } return CircleAnnotationManager(id: id, style: style, - view: view, + singleTapGestureRecognizer: singleTapGestureRecognizer, mapFeatureQueryable: mapFeatureQueryable, shouldPersist: true, layerPosition: layerPosition, diff --git a/Sources/MapboxMaps/Annotations/Generated/CircleAnnotationManager.swift b/Sources/MapboxMaps/Annotations/Generated/CircleAnnotationManager.swift index 174f4a13dd0c..9f5dd71a9dda 100644 --- a/Sources/MapboxMaps/Annotations/Generated/CircleAnnotationManager.swift +++ b/Sources/MapboxMaps/Annotations/Generated/CircleAnnotationManager.swift @@ -33,7 +33,7 @@ public class CircleAnnotationManager: AnnotationManager { private let mapFeatureQueryable: MapFeatureQueryable /// Dependency required to add gesture recognizer to the MapView - private weak var view: UIView? + private weak var singleTapGestureRecognizer: UIGestureRecognizer? /// Storage for common layer properties private var layerProperties: [String: Any] = [:] { @@ -54,7 +54,7 @@ public class CircleAnnotationManager: AnnotationManager { internal init(id: String, style: Style, - view: UIView, + singleTapGestureRecognizer: UIGestureRecognizer, mapFeatureQueryable: MapFeatureQueryable, shouldPersist: Bool, layerPosition: LayerPosition?, @@ -63,7 +63,7 @@ public class CircleAnnotationManager: AnnotationManager { self.style = style self.sourceId = id + "-source" self.layerId = id + "-layer" - self.view = view + self.singleTapGestureRecognizer = singleTapGestureRecognizer self.mapFeatureQueryable = mapFeatureQueryable self.shouldPersist = shouldPersist @@ -206,36 +206,23 @@ public class CircleAnnotationManager: AnnotationManager { } } - // MARK: - Selection Handling - + // MARK: - Tap Handling - /// Set this delegate in order to be called back if a tap occurs on an annotation being managed by this manager. public weak var delegate: AnnotationInteractionDelegate? { didSet { - if delegate != nil { - setupTapRecognizer() - } else { - guard let view = view, let recognizer = tapGestureRecognizer else { return } - view.removeGestureRecognizer(recognizer) - tapGestureRecognizer = nil + if delegate != nil && oldValue == nil { + singleTapGestureRecognizer?.addTarget(self, action: #selector(handleTap(_:))) + } else if delegate == nil && oldValue != nil { + singleTapGestureRecognizer?.removeTarget(self, action: #selector(handleTap(_:))) } } } - /// The `UITapGestureRecognizer` that's listening to touch events on the map for the annotations present in this manager - public var tapGestureRecognizer: UITapGestureRecognizer? - - internal func setupTapRecognizer() { - let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:))) - tapRecognizer.numberOfTapsRequired = 1 - tapRecognizer.numberOfTouchesRequired = 1 - view?.addGestureRecognizer(tapRecognizer) - tapGestureRecognizer = tapRecognizer - } - @objc internal func handleTap(_ tap: UITapGestureRecognizer) { let options = RenderedQueryOptions(layerIds: [layerId], filter: nil) mapFeatureQueryable.queryRenderedFeatures( - at: tap.location(in: view), + at: tap.location(in: tap.view), options: options) { [weak self] (result) in guard let self = self else { return } diff --git a/Sources/MapboxMaps/Annotations/Generated/PointAnnotationManager.swift b/Sources/MapboxMaps/Annotations/Generated/PointAnnotationManager.swift index a3a514e33e43..48f673a1d5cc 100644 --- a/Sources/MapboxMaps/Annotations/Generated/PointAnnotationManager.swift +++ b/Sources/MapboxMaps/Annotations/Generated/PointAnnotationManager.swift @@ -33,7 +33,7 @@ public class PointAnnotationManager: AnnotationManager { private let mapFeatureQueryable: MapFeatureQueryable /// Dependency required to add gesture recognizer to the MapView - private weak var view: UIView? + private weak var singleTapGestureRecognizer: UIGestureRecognizer? /// Storage for common layer properties private var layerProperties: [String: Any] = [:] { @@ -54,7 +54,7 @@ public class PointAnnotationManager: AnnotationManager { internal init(id: String, style: Style, - view: UIView, + singleTapGestureRecognizer: UIGestureRecognizer, mapFeatureQueryable: MapFeatureQueryable, shouldPersist: Bool, layerPosition: LayerPosition?, @@ -63,7 +63,7 @@ public class PointAnnotationManager: AnnotationManager { self.style = style self.sourceId = id + "-source" self.layerId = id + "-layer" - self.view = view + self.singleTapGestureRecognizer = singleTapGestureRecognizer self.mapFeatureQueryable = mapFeatureQueryable self.shouldPersist = shouldPersist @@ -464,36 +464,23 @@ public class PointAnnotationManager: AnnotationManager { } } - // MARK: - Selection Handling - + // MARK: - Tap Handling - /// Set this delegate in order to be called back if a tap occurs on an annotation being managed by this manager. public weak var delegate: AnnotationInteractionDelegate? { didSet { - if delegate != nil { - setupTapRecognizer() - } else { - guard let view = view, let recognizer = tapGestureRecognizer else { return } - view.removeGestureRecognizer(recognizer) - tapGestureRecognizer = nil + if delegate != nil && oldValue == nil { + singleTapGestureRecognizer?.addTarget(self, action: #selector(handleTap(_:))) + } else if delegate == nil && oldValue != nil { + singleTapGestureRecognizer?.removeTarget(self, action: #selector(handleTap(_:))) } } } - /// The `UITapGestureRecognizer` that's listening to touch events on the map for the annotations present in this manager - public var tapGestureRecognizer: UITapGestureRecognizer? - - internal func setupTapRecognizer() { - let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:))) - tapRecognizer.numberOfTapsRequired = 1 - tapRecognizer.numberOfTouchesRequired = 1 - view?.addGestureRecognizer(tapRecognizer) - tapGestureRecognizer = tapRecognizer - } - @objc internal func handleTap(_ tap: UITapGestureRecognizer) { let options = RenderedQueryOptions(layerIds: [layerId], filter: nil) mapFeatureQueryable.queryRenderedFeatures( - at: tap.location(in: view), + at: tap.location(in: tap.view), options: options) { [weak self] (result) in guard let self = self else { return } diff --git a/Sources/MapboxMaps/Annotations/Generated/PolygonAnnotationManager.swift b/Sources/MapboxMaps/Annotations/Generated/PolygonAnnotationManager.swift index f50f5c077eb0..63dea2327b34 100644 --- a/Sources/MapboxMaps/Annotations/Generated/PolygonAnnotationManager.swift +++ b/Sources/MapboxMaps/Annotations/Generated/PolygonAnnotationManager.swift @@ -33,7 +33,7 @@ public class PolygonAnnotationManager: AnnotationManager { private let mapFeatureQueryable: MapFeatureQueryable /// Dependency required to add gesture recognizer to the MapView - private weak var view: UIView? + private weak var singleTapGestureRecognizer: UIGestureRecognizer? /// Storage for common layer properties private var layerProperties: [String: Any] = [:] { @@ -54,7 +54,7 @@ public class PolygonAnnotationManager: AnnotationManager { internal init(id: String, style: Style, - view: UIView, + singleTapGestureRecognizer: UIGestureRecognizer, mapFeatureQueryable: MapFeatureQueryable, shouldPersist: Bool, layerPosition: LayerPosition?, @@ -63,7 +63,7 @@ public class PolygonAnnotationManager: AnnotationManager { self.style = style self.sourceId = id + "-source" self.layerId = id + "-layer" - self.view = view + self.singleTapGestureRecognizer = singleTapGestureRecognizer self.mapFeatureQueryable = mapFeatureQueryable self.shouldPersist = shouldPersist @@ -196,36 +196,23 @@ public class PolygonAnnotationManager: AnnotationManager { } } - // MARK: - Selection Handling - + // MARK: - Tap Handling - /// Set this delegate in order to be called back if a tap occurs on an annotation being managed by this manager. public weak var delegate: AnnotationInteractionDelegate? { didSet { - if delegate != nil { - setupTapRecognizer() - } else { - guard let view = view, let recognizer = tapGestureRecognizer else { return } - view.removeGestureRecognizer(recognizer) - tapGestureRecognizer = nil + if delegate != nil && oldValue == nil { + singleTapGestureRecognizer?.addTarget(self, action: #selector(handleTap(_:))) + } else if delegate == nil && oldValue != nil { + singleTapGestureRecognizer?.removeTarget(self, action: #selector(handleTap(_:))) } } } - /// The `UITapGestureRecognizer` that's listening to touch events on the map for the annotations present in this manager - public var tapGestureRecognizer: UITapGestureRecognizer? - - internal func setupTapRecognizer() { - let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:))) - tapRecognizer.numberOfTapsRequired = 1 - tapRecognizer.numberOfTouchesRequired = 1 - view?.addGestureRecognizer(tapRecognizer) - tapGestureRecognizer = tapRecognizer - } - @objc internal func handleTap(_ tap: UITapGestureRecognizer) { let options = RenderedQueryOptions(layerIds: [layerId], filter: nil) mapFeatureQueryable.queryRenderedFeatures( - at: tap.location(in: view), + at: tap.location(in: tap.view), options: options) { [weak self] (result) in guard let self = self else { return } diff --git a/Sources/MapboxMaps/Annotations/Generated/PolylineAnnotationManager.swift b/Sources/MapboxMaps/Annotations/Generated/PolylineAnnotationManager.swift index 53be49702fbf..6d6e0a233591 100644 --- a/Sources/MapboxMaps/Annotations/Generated/PolylineAnnotationManager.swift +++ b/Sources/MapboxMaps/Annotations/Generated/PolylineAnnotationManager.swift @@ -33,7 +33,7 @@ public class PolylineAnnotationManager: AnnotationManager { private let mapFeatureQueryable: MapFeatureQueryable /// Dependency required to add gesture recognizer to the MapView - private weak var view: UIView? + private weak var singleTapGestureRecognizer: UIGestureRecognizer? /// Storage for common layer properties private var layerProperties: [String: Any] = [:] { @@ -54,7 +54,7 @@ public class PolylineAnnotationManager: AnnotationManager { internal init(id: String, style: Style, - view: UIView, + singleTapGestureRecognizer: UIGestureRecognizer, mapFeatureQueryable: MapFeatureQueryable, shouldPersist: Bool, layerPosition: LayerPosition?, @@ -63,7 +63,7 @@ public class PolylineAnnotationManager: AnnotationManager { self.style = style self.sourceId = id + "-source" self.layerId = id + "-layer" - self.view = view + self.singleTapGestureRecognizer = singleTapGestureRecognizer self.mapFeatureQueryable = mapFeatureQueryable self.shouldPersist = shouldPersist @@ -226,36 +226,23 @@ public class PolylineAnnotationManager: AnnotationManager { } } - // MARK: - Selection Handling - + // MARK: - Tap Handling - /// Set this delegate in order to be called back if a tap occurs on an annotation being managed by this manager. public weak var delegate: AnnotationInteractionDelegate? { didSet { - if delegate != nil { - setupTapRecognizer() - } else { - guard let view = view, let recognizer = tapGestureRecognizer else { return } - view.removeGestureRecognizer(recognizer) - tapGestureRecognizer = nil + if delegate != nil && oldValue == nil { + singleTapGestureRecognizer?.addTarget(self, action: #selector(handleTap(_:))) + } else if delegate == nil && oldValue != nil { + singleTapGestureRecognizer?.removeTarget(self, action: #selector(handleTap(_:))) } } } - /// The `UITapGestureRecognizer` that's listening to touch events on the map for the annotations present in this manager - public var tapGestureRecognizer: UITapGestureRecognizer? - - internal func setupTapRecognizer() { - let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:))) - tapRecognizer.numberOfTapsRequired = 1 - tapRecognizer.numberOfTouchesRequired = 1 - view?.addGestureRecognizer(tapRecognizer) - tapGestureRecognizer = tapRecognizer - } - @objc internal func handleTap(_ tap: UITapGestureRecognizer) { let options = RenderedQueryOptions(layerIds: [layerId], filter: nil) mapFeatureQueryable.queryRenderedFeatures( - at: tap.location(in: view), + at: tap.location(in: tap.view), options: options) { [weak self] (result) in guard let self = self else { return } diff --git a/Sources/MapboxMaps/Foundation/MapView.swift b/Sources/MapboxMaps/Foundation/MapView.swift index 47be4f7ec53f..11c09f8d8e88 100644 --- a/Sources/MapboxMaps/Foundation/MapView.swift +++ b/Sources/MapboxMaps/Foundation/MapView.swift @@ -234,7 +234,11 @@ open class MapView: UIView { location = LocationManager(style: mapboxMap.style) // Initialize/Configure annotations orchestrator - annotations = AnnotationOrchestrator(view: self, mapFeatureQueryable: mapboxMap, style: mapboxMap.style, displayLinkCoordinator: self) + annotations = AnnotationOrchestrator( + singleTapGestureRecognizer: gestures.singleTapGestureRecognizer, + mapFeatureQueryable: mapboxMap, + style: mapboxMap.style, + displayLinkCoordinator: self) } private func checkForMetalSupport() { diff --git a/Tests/MapboxMapsTests/Foundation/Mocks/MockMapViewDependencyProvider.swift b/Tests/MapboxMapsTests/Foundation/Mocks/MockMapViewDependencyProvider.swift index d1e3b7ffc3ac..5e24b75ef80a 100644 --- a/Tests/MapboxMapsTests/Foundation/Mocks/MockMapViewDependencyProvider.swift +++ b/Tests/MapboxMapsTests/Foundation/Mocks/MockMapViewDependencyProvider.swift @@ -42,7 +42,8 @@ final class MockMapViewDependencyProvider: MapViewDependencyProviderProtocol { pitchGestureHandler: makeGestureHandler(), doubleTapToZoomInGestureHandler: makeGestureHandler(), doubleTouchToZoomOutGestureHandler: makeGestureHandler(), - quickZoomGestureHandler: makeGestureHandler()) + quickZoomGestureHandler: makeGestureHandler(), + singleTapGestureHandler: makeGestureHandler()) } func makeGestureHandler() -> GestureHandler { diff --git a/Tests/MapboxMapsTests/Gestures/GestureHandlers/SingleTapGestureHandlerTests.swift b/Tests/MapboxMapsTests/Gestures/GestureHandlers/SingleTapGestureHandlerTests.swift new file mode 100644 index 000000000000..5dcc4fda0838 --- /dev/null +++ b/Tests/MapboxMapsTests/Gestures/GestureHandlers/SingleTapGestureHandlerTests.swift @@ -0,0 +1,48 @@ +import XCTest +@testable import MapboxMaps + +final class SingleTapGestureHandlerTests: XCTestCase { + + var gestureRecognizer: MockTapGestureRecognizer! + var cameraAnimationsManager: MockCameraAnimationsManager! + var mapboxMap: MockMapboxMap! + var gestureHandler: SingleTapGestureHandler! + // swiftlint:disable:next weak_delegate + var delegate: MockGestureHandlerDelegate! + + override func setUp() { + super.setUp() + gestureRecognizer = MockTapGestureRecognizer() + cameraAnimationsManager = MockCameraAnimationsManager() + mapboxMap = MockMapboxMap() + gestureHandler = SingleTapGestureHandler( + gestureRecognizer: gestureRecognizer, + mapboxMap: mapboxMap, + cameraAnimationsManager: cameraAnimationsManager) + delegate = MockGestureHandlerDelegate() + gestureHandler.delegate = delegate + } + + override func tearDown() { + delegate = nil + gestureHandler = nil + mapboxMap = nil + cameraAnimationsManager = nil + gestureRecognizer = nil + super.tearDown() + } + + func testInitialization() { + XCTAssertTrue(gestureRecognizer === gestureHandler.gestureRecognizer) + XCTAssertEqual(gestureRecognizer.numberOfTapsRequired, 1) + XCTAssertEqual(gestureRecognizer.numberOfTouchesRequired, 1) + } + + func testHandler() { + gestureRecognizer.getStateStub.defaultReturnValue = .recognized + gestureRecognizer.sendActions() + + XCTAssertEqual(delegate.gestureBeganStub.parameters, [.singleTap]) + XCTAssertEqual(cameraAnimationsManager.cancelAnimationsStub.invocations.count, 1) + } +} diff --git a/Tests/MapboxMapsTests/Gestures/GestureManagerTests.swift b/Tests/MapboxMapsTests/Gestures/GestureManagerTests.swift index 7b871fbcf85e..66d4575a9d72 100644 --- a/Tests/MapboxMapsTests/Gestures/GestureManagerTests.swift +++ b/Tests/MapboxMapsTests/Gestures/GestureManagerTests.swift @@ -11,6 +11,7 @@ final class GestureManagerTests: XCTestCase { var doubleTapToZoomInGestureHandler: GestureHandler! var doubleTouchToZoomOutGestureHandler: GestureHandler! var quickZoomGestureHandler: GestureHandler! + var singleTapGestureHandler: GestureHandler! var gestureManager: GestureManager! // swiftlint:disable:next weak_delegate var delegate: MockGestureManagerDelegate! @@ -28,13 +29,15 @@ final class GestureManagerTests: XCTestCase { doubleTapToZoomInGestureHandler = makeGestureHandler() doubleTouchToZoomOutGestureHandler = makeGestureHandler() quickZoomGestureHandler = makeGestureHandler() + singleTapGestureHandler = makeGestureHandler() gestureManager = GestureManager( panGestureHandler: panGestureHandler, pinchGestureHandler: pinchGestureHandler, pitchGestureHandler: pitchGestureHandler, doubleTapToZoomInGestureHandler: doubleTapToZoomInGestureHandler, doubleTouchToZoomOutGestureHandler: doubleTouchToZoomOutGestureHandler, - quickZoomGestureHandler: quickZoomGestureHandler) + quickZoomGestureHandler: quickZoomGestureHandler, + singleTapGestureHandler: singleTapGestureHandler) delegate = MockGestureManagerDelegate() gestureManager.delegate = delegate } @@ -47,6 +50,7 @@ final class GestureManagerTests: XCTestCase { doubleTapToZoomInGestureHandler = nil pitchGestureHandler = nil pinchGestureHandler = nil + singleTapGestureHandler = nil panGestureHandler = nil cameraAnimationsManager = nil mapboxMap = nil @@ -71,6 +75,10 @@ final class GestureManagerTests: XCTestCase { func testPitchGestureRecognizer() { XCTAssertTrue(gestureManager.pitchGestureRecognizer === pitchGestureHandler.gestureRecognizer) } + + func testSingleTapGestureRecognizer() { + XCTAssertTrue(gestureManager.singleTapGestureRecognizer === singleTapGestureHandler.gestureRecognizer) + } func testDoubleTapToZoomInGestureRecognizer() { XCTAssertTrue(gestureManager.doubleTapToZoomInGestureRecognizer