diff --git a/Apps/Examples/Examples/All Examples/Custom2DPuckExample.swift b/Apps/Examples/Examples/All Examples/Custom2DPuckExample.swift index bb24cb92b331..da0081b7a866 100644 --- a/Apps/Examples/Examples/All Examples/Custom2DPuckExample.swift +++ b/Apps/Examples/Examples/All Examples/Custom2DPuckExample.swift @@ -4,7 +4,13 @@ import MapboxMaps @objc(Custom2DPuckExample) public class Custom2DPuckExample: UIViewController, ExampleProtocol { + internal let toggleAccuracyRadiusButton: UIButton = UIButton(frame: .zero) internal var mapView: MapView! + internal var showsAccuracyRing: Bool = false { + didSet { + syncPuckAndButton() + } + } override public func viewDidLoad() { super.viewDidLoad() @@ -13,10 +19,29 @@ public class Custom2DPuckExample: UIViewController, ExampleProtocol { mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight] view.addSubview(mapView) + // Setup and create button for toggling accuracy ring + setupToggleShowAccuracyButton() + // Granularly configure the location puck with a `Puck2DConfiguration` let configuration = Puck2DConfiguration(topImage: UIImage(named: "star")) mapView.location.options.puckType = .puck2D(configuration) mapView.location.options.puckBearingSource = .course + + // Center map over the user's current location + mapView.mapboxMap.onNext(.mapLoaded, handler: { [weak self] _ in + guard let self = self else { return } + + if let currentLocation = self.mapView.location.latestLocation { + let cameraOptions = CameraOptions(center: currentLocation.coordinate, zoom: 20.0) + self.mapView.camera.ease(to: cameraOptions, duration: 2.0) + } + }) + + // Accuracy ring is only shown when zoom is greater than or equal to 18 + mapView.mapboxMap.onEvery(.cameraChanged, handler: { [weak self] _ in + guard let self = self else { return } + self.toggleAccuracyRadiusButton.isHidden = self.mapView.cameraState.zoom < 18.0 + }) } override public func viewDidAppear(_ animated: Bool) { @@ -24,4 +49,35 @@ public class Custom2DPuckExample: UIViewController, ExampleProtocol { // The below line is used for internal testing purposes only. finish() } + + @objc func showHideAccuracyRadius() { + showsAccuracyRing.toggle() + } + + func syncPuckAndButton() { + // Update puck config + var configuration = Puck2DConfiguration(topImage: UIImage(named: "star")) + configuration.showsAccuracyRing = showsAccuracyRing + mapView.location.options.puckType = .puck2D(configuration) + + // Update button title + let title: String = showsAccuracyRing ? "Disable Accuracy Radius" : "Enable Accuracy Radius" + toggleAccuracyRadiusButton.setTitle(title, for: .normal) + } + + private func setupToggleShowAccuracyButton() { + // Styling + toggleAccuracyRadiusButton.backgroundColor = .systemBlue + toggleAccuracyRadiusButton.addTarget(self, action: #selector(showHideAccuracyRadius), for: .touchUpInside) + toggleAccuracyRadiusButton.setTitleColor(.white, for: .normal) + toggleAccuracyRadiusButton.isHidden = true + syncPuckAndButton() + toggleAccuracyRadiusButton.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(toggleAccuracyRadiusButton) + + // Constraints + toggleAccuracyRadiusButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20.0).isActive = true + toggleAccuracyRadiusButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20.0).isActive = true + toggleAccuracyRadiusButton.topAnchor.constraint(equalTo: view.topAnchor, constant: 650.0).isActive = true + } } diff --git a/CHANGELOG.md b/CHANGELOG.md index 76853f613c6c..069aeb9377da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ Mapbox welcomes participation and contributions from everyone. * Added a public, failable, component-wise initializer to `StyleColor` ([#650](https://github.com/mapbox/mapbox-maps-ios/pull/650)) * Updated `StyleColor`'s `Decodable` support to be able to handle rgba color strings as well as rgba expressions ([#650](https://github.com/mapbox/mapbox-maps-ios/pull/650)) * Made generated enums conform to `CaseIterable` ([#650](https://github.com/mapbox/mapbox-maps-ios/pull/650)) +* Location puck can now hide the accuracy ring. The default value is to hide the accuracy ring. In order to enable the ring, set the `showAccuracyRing` property in `Puck2DConfiguration` to `true`. [#629](https://github.com/mapbox/mapbox-maps-ios/pull/629) ### Bug fixes 🐞 diff --git a/Sources/MapboxMaps/Location/Pucks/Puck2D.swift b/Sources/MapboxMaps/Location/Pucks/Puck2D.swift index 1e269d3f504e..3308cfb4bf1f 100644 --- a/Sources/MapboxMaps/Location/Pucks/Puck2D.swift +++ b/Sources/MapboxMaps/Location/Pucks/Puck2D.swift @@ -17,14 +17,19 @@ public struct Puck2DConfiguration: Equatable { /// The size of the images, as a scale factor applied to the size of the specified image. public var scale: Value? + /// Flag determining if the horizontal accuracy ring should be shown arround the Puck. default value is false + public var showsAccuracyRing: Bool + public init(topImage: UIImage? = nil, bearingImage: UIImage? = nil, shadowImage: UIImage? = nil, - scale: Value? = nil) { + scale: Value? = nil, + showsAccuracyRing: Bool = false) { self.topImage = topImage self.bearingImage = bearingImage self.shadowImage = shadowImage self.scale = scale + self.showsAccuracyRing = showsAccuracyRing } internal var resolvedTopImage: UIImage? { @@ -181,11 +186,15 @@ internal extension Puck2D { layer.topImageSize = configuration.resolvedScale layer.bearingImageSize = configuration.resolvedScale layer.shadowImageSize = configuration.resolvedScale - layer.accuracyRadius = .constant(location.horizontalAccuracy) layer.emphasisCircleRadiusTransition = StyleTransition(duration: 0, delay: 0) layer.bearingTransition = StyleTransition(duration: 0, delay: 0) - layer.accuracyRadiusColor = .constant(StyleColor(UIColor(red: 0.537, green: 0.812, blue: 0.941, alpha: 0.3))) - layer.accuracyRadiusBorderColor = .constant(StyleColor(.lightGray)) + + // Horizontal accuracy ring is an optional visual for the 2D Puck + if configuration.showsAccuracyRing { + layer.accuracyRadius = .constant(location.horizontalAccuracy) + layer.accuracyRadiusColor = .constant(StyleColor(UIColor(red: 0.537, green: 0.812, blue: 0.941, alpha: 0.3))) + layer.accuracyRadiusBorderColor = .constant(StyleColor(.lightGray)) + } // Add layer to style try style._addPersistentLayer(layer) diff --git a/Tests/MapboxMapsTests/Location/Pucks/Puck2DIntegrationTests.swift b/Tests/MapboxMapsTests/Location/Pucks/Puck2DIntegrationTests.swift index e42459fed288..57a5d8fa8c11 100644 --- a/Tests/MapboxMapsTests/Location/Pucks/Puck2DIntegrationTests.swift +++ b/Tests/MapboxMapsTests/Location/Pucks/Puck2DIntegrationTests.swift @@ -129,6 +129,56 @@ class Puck2DIntegrationTests: MapViewIntegrationTestCase { wait(for: [addedPrecisePuckExpectation, removedPrecisePuckExpectation, addedApproximatePuckExpectation], timeout: 5) } + func testAccuracyRadiusIsHidden() throws { + let style = try XCTUnwrap(self.style) + style.uri = .streets + + let location = Location(with: CLLocation(latitude: 1, longitude: 1)) + let accuracyRingIsHiddenExpectation = XCTestExpectation(description: "Layer does not contain an accuracy ring") + + didFinishLoadingStyle = { _ in + let puck = Puck2D(puckStyle: .precise, + puckBearingSource: .heading, + style: style, + configuration: Puck2DConfiguration()) + do { + try puck.createPreciseLocationIndicatorLayer(location: location) + let layer = try style.layer(withId: "puck") as LocationIndicatorLayer + XCTAssertNil(layer.accuracyRadius) + } catch { + XCTFail("Failed to create a precise location indicator layer.") + } + + accuracyRingIsHiddenExpectation.fulfill() + } + wait(for: [accuracyRingIsHiddenExpectation], timeout: 5) + } + + func testAccuracyRadiusIsShown() throws { + let style = try XCTUnwrap(self.style) + style.uri = .streets + + let location = Location(with: CLLocation(latitude: 1, longitude: 1)) + let accuracyRingIsVisibleExpectation = XCTestExpectation(description: "Layer contains an accuracy ring") + + didFinishLoadingStyle = { _ in + let puck = Puck2D(puckStyle: .precise, + puckBearingSource: .heading, + style: style, + configuration: Puck2DConfiguration(showsAccuracyRing: true)) + do { + try puck.createPreciseLocationIndicatorLayer(location: location) + let layer = try style.layer(withId: "puck") as LocationIndicatorLayer + XCTAssertNotNil(layer.accuracyRadius) + } catch { + XCTFail("Failed to create a precise location indicator layer.") + } + + accuracyRingIsVisibleExpectation.fulfill() + } + wait(for: [accuracyRingIsVisibleExpectation], timeout: 5) + } + func testLayerPersistence() throws { let style = try XCTUnwrap(self.style)