From ea4aaaa38b1a4888cc7e03ec50d73591afbdfb0a Mon Sep 17 00:00:00 2001 From: Nishant Karajgikar Date: Tue, 20 Apr 2021 18:06:00 -0400 Subject: [PATCH 01/40] Introduce short-lived camera view; update flyTo for better performance; expose easeTo --- .../All Examples/CameraAnimatorsExample.swift | 28 +- .../Examples/All Examples/FlyToExample.swift | 4 +- .../All Examples/GeoJSONSourceExample.swift | 5 +- .../MapboxMaps/Foundation/BaseMapView.swift | 97 +++---- .../Camera/CameraAnimationDelegate.swift | 24 +- .../Foundation/Camera/CameraAnimator.swift | 136 +++++++-- ...CameraManager+CameraAnimatorDelegate.swift | 69 ++++- .../Foundation/Camera/CameraManager.swift | 159 +++++------ .../Foundation/Camera/CameraTransition.swift | 108 +++++++ .../Foundation/Camera/CameraView.swift | 209 ++------------ .../Foundation/Camera/FlyToAnimator.swift | 114 ++++++++ .../Extensions/Core/MBXEdgeInsets.swift | 9 +- .../Foundation/Extensions/CoreGraphics.swift | 8 +- .../Foundation/Extensions/CoreLocation.swift | 8 +- ...estureManager+GestureHandlerDelegate.swift | 10 +- .../MapView/MapView+Supportable.swift | 19 +- .../Camera/CameraAnimatorDelegateMock.swift | 45 ++- .../Camera/CameraAnimatorTests.swift | 75 ++++- .../Camera/CameraTransitionTests.swift | 75 +++++ .../Foundation/Camera/CameraViewMock.swift | 24 ++ .../Camera/MapboxMapsCameraTests.swift | 266 +++++------------- .../Camera/UIViewPropertyAnimatorMock.swift | 59 ++++ .../Gestures/GestureManagerTests.swift | 8 - .../ExampleIntegrationTest.swift | 3 - .../Map/DidIdleFailureIntegrationTest.swift | 3 - 25 files changed, 913 insertions(+), 652 deletions(-) create mode 100644 Sources/MapboxMaps/Foundation/Camera/CameraTransition.swift create mode 100644 Sources/MapboxMaps/Foundation/Camera/FlyToAnimator.swift create mode 100644 Tests/MapboxMapsTests/Foundation/Camera/CameraTransitionTests.swift create mode 100644 Tests/MapboxMapsTests/Foundation/Camera/CameraViewMock.swift create mode 100644 Tests/MapboxMapsTests/Foundation/Camera/UIViewPropertyAnimatorMock.swift diff --git a/Apps/Examples/Examples/All Examples/CameraAnimatorsExample.swift b/Apps/Examples/Examples/All Examples/CameraAnimatorsExample.swift index 8adbbf15579..92bd9968177 100644 --- a/Apps/Examples/Examples/All Examples/CameraAnimatorsExample.swift +++ b/Apps/Examples/Examples/All Examples/CameraAnimatorsExample.swift @@ -7,13 +7,17 @@ public class CameraAnimatorsExample: UIViewController, ExampleProtocol { internal var mapView: MapView! + // Coordinate in New York City + let newYork = CLLocationCoordinate2D(latitude: 40.7128, longitude: -74.0060) + // Store the CameraAnimators so that the do not fall out of scope. lazy var zoomAnimator: CameraAnimator = { - let animator = mapView.camera.makeCameraAnimator(duration: 4, curve: .easeInOut) { [unowned self] in - self.mapView.zoom = 14 + let animator = mapView.camera.makeCameraAnimator(duration: 4, curve: .easeInOut) { (transition) in + transition.zoom.toValue = 14 } animator.addCompletion { [unowned self] (_) in + print("Animating camera pitch from 0 degrees -> 55 degrees") self.pitchAnimator.startAnimation() } @@ -21,11 +25,12 @@ public class CameraAnimatorsExample: UIViewController, ExampleProtocol { }() lazy var pitchAnimator: CameraAnimator = { - let animator = mapView.camera.makeCameraAnimator(duration: 2, curve: .easeInOut) { [unowned self] in - self.mapView.pitch = 55 + let animator = mapView.camera.makeCameraAnimator(duration: 2, curve: .easeInOut) { (transition) in + transition.pitch.toValue = 55 } animator.addCompletion { [unowned self] (_) in + print("Animating camera bearing from 0 degrees -> 45 degrees") self.bearingAnimator.startAnimation() } @@ -33,8 +38,8 @@ public class CameraAnimatorsExample: UIViewController, ExampleProtocol { }() lazy var bearingAnimator: CameraAnimator = { - let animator = mapView.camera.makeCameraAnimator(duration: 4, curve: .easeInOut) { [unowned self] in - self.mapView.bearing = -45 + let animator = mapView.camera.makeCameraAnimator(duration: 4, curve: .easeInOut) { (transition) in + transition.bearing.toValue = -45 } animator.addCompletion { (_) in @@ -51,13 +56,18 @@ public class CameraAnimatorsExample: UIViewController, ExampleProtocol { mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight] view.addSubview(mapView) - // Center the map over New York City. - let newYork = CLLocationCoordinate2D(latitude: 40.7128, longitude: -74.0060) - mapView.camera.setCamera(to: CameraOptions(center: newYork)) + + mapView.on(.styleLoaded) { [weak self] _ in + guard let self = self else { return } + // Center the map over New York City. + self.mapView.camera.setCamera(to: CameraOptions(center: self.newYork)) + } // Allows the delegate to receive information about map events. mapView.on(.mapLoaded) { [weak self] _ in guard let self = self else { return } + + print("Animating zoom from zoom lvl 3 -> zoom lvl 14") self.zoomAnimator.startAnimation(afterDelay: 1) self.finish() } diff --git a/Apps/Examples/Examples/All Examples/FlyToExample.swift b/Apps/Examples/Examples/All Examples/FlyToExample.swift index 0e2c3a85c4e..4962c664c83 100644 --- a/Apps/Examples/Examples/All Examples/FlyToExample.swift +++ b/Apps/Examples/Examples/All Examples/FlyToExample.swift @@ -6,7 +6,6 @@ import MapboxMaps public class FlyToExample: UIViewController, ExampleProtocol { internal var mapView: MapView! - internal var flyToAnimator: CameraAnimator? override public func viewDidLoad() { super.viewDidLoad() @@ -35,10 +34,9 @@ public class FlyToExample: UIViewController, ExampleProtocol { bearing: 180, pitch: 50) - flyToAnimator = self.mapView.camera.fly(to: end) { [weak self] _ in + mapView.camera.fly(to: end) { [weak self] _ in print("Camera fly-to finished") // The below line is used for internal testing purposes only. - self?.flyToAnimator = nil self?.finish() } diff --git a/Apps/Examples/Examples/All Examples/GeoJSONSourceExample.swift b/Apps/Examples/Examples/All Examples/GeoJSONSourceExample.swift index 6b80b67f8b1..3e72edcd672 100644 --- a/Apps/Examples/Examples/All Examples/GeoJSONSourceExample.swift +++ b/Apps/Examples/Examples/All Examples/GeoJSONSourceExample.swift @@ -17,8 +17,9 @@ public class GeoJSONSourceExample: UIViewController, ExampleProtocol { // Set the center coordinate and zoom level. let centerCoordinate = CLLocationCoordinate2D(latitude: 18.239785, longitude: -66.302490) - mapView.centerCoordinate = centerCoordinate - mapView.zoom = 6.9 + + mapView.camera.setCamera(to: CameraOptions(center: centerCoordinate, + zoom: 6.9)) // Allow the view controller to receive information about map events. mapView.on(.mapLoaded) { [weak self] _ in diff --git a/Sources/MapboxMaps/Foundation/BaseMapView.swift b/Sources/MapboxMaps/Foundation/BaseMapView.swift index 9d72a9abd33..0630612ea66 100644 --- a/Sources/MapboxMaps/Foundation/BaseMapView.swift +++ b/Sources/MapboxMaps/Foundation/BaseMapView.swift @@ -4,11 +4,9 @@ import UIKit import Turf // swiftlint:disable file_length - internal typealias PendingAnimationCompletion = (completion: AnimationCompletion, animatingPosition: UIViewAnimatingPosition) -// swiftlint:disable:next type_body_length -open class BaseMapView: UIView, CameraViewDelegate { +open class BaseMapView: UIView { // mapbox map depends on MapInitOptions, which is not available until // awakeFromNib() when instantiating BaseMapView from a xib or storyboard. @@ -32,6 +30,22 @@ open class BaseMapView: UIView, CameraViewDelegate { /// List of completion blocks that need to be completed by the displayLink internal var pendingAnimatorCompletionBlocks: [PendingAnimationCompletion] = [] + /// Pointer HashTable for holding camera animators + internal var cameraAnimatorsHashTable = NSHashTable.weakObjects() + + /// List of animators currently alive + public var cameraAnimators: [CameraAnimator] { + + var animators: [CameraAnimator] = [] + cameraAnimatorsHashTable.allObjects.forEach { (animator) in + if let animator = animator as? CameraAnimator { + animators.append(animator) + } + } + + return animators + } + /// Map of event types to subscribed event handlers private var eventHandlers: [String: [(MapboxCoreMaps.Event) -> Void]] = [:] @@ -52,87 +66,46 @@ open class BaseMapView: UIView, CameraViewDelegate { } } - /// Returns the camera view managed by this object. - internal private(set) var cameraView: CameraView! - /// The map's current camera public var cameraOptions: CameraOptions { - get { - return mapboxMap.cameraOptions - } set { - cameraView.camera = newValue - } + return mapboxMap.cameraOptions } /// The map's current center coordinate. public var centerCoordinate: CLLocationCoordinate2D { - get { - guard let center = cameraOptions.center else { - fatalError("Center is nil in camera options") - } - return center - } set { - cameraView.centerCoordinate = newValue + guard let center = cameraOptions.center else { + fatalError("Center is nil in camera options") } + return center } /// The map's zoom level. public var zoom: CGFloat { - get { - guard let zoom = cameraOptions.zoom else { - fatalError("Zoom is nil in camera options") - } - return CGFloat(zoom) - } set { - cameraView.zoom = newValue + guard let zoom = cameraOptions.zoom else { + fatalError("Zoom is nil in camera options") } + return CGFloat(zoom) } /// The map's bearing, measured clockwise from 0° north. public var bearing: CLLocationDirection { - get { - guard let bearing = cameraOptions.bearing else { - fatalError("Bearing is nil in camera options") - } - return CLLocationDirection(bearing) - } set { - cameraView.bearing = CGFloat(newValue) + guard let bearing = cameraOptions.bearing else { + fatalError("Bearing is nil in camera options") } + return CLLocationDirection(bearing) } /// The map's pitch, falling within a range of 0 to 60. public var pitch: CGFloat { - get { - guard let pitch = cameraOptions.pitch else { - fatalError("Pitch is nil in camera options") - } - - return pitch - } set { - cameraView.pitch = newValue + guard let pitch = cameraOptions.pitch else { + fatalError("Pitch is nil in camera options") } + return pitch } /// The map's camera padding public var padding: UIEdgeInsets { - get { - return cameraOptions.padding ?? .zero - } set { - cameraView.padding = newValue - } - } - - public var anchor: CGPoint { - get { - // TODO: Evaluate whether we should get the anchor from CameraView or not - return cameraView.anchor - } set { - cameraView.anchor = newValue - } - } - - func jumpTo(camera: CameraOptions) { - mapboxMap.updateCamera(with: camera) + return cameraOptions.padding ?? .zero } // MARK: Init @@ -173,9 +146,6 @@ open class BaseMapView: UIView, CameraViewDelegate { let events = MapEvents.EventKind.allCases.map({ $0.rawValue }) mapboxMap.__map.subscribe(for: observer, events: events) - self.cameraView = CameraView(delegate: self) - self.addSubview(cameraView) - NotificationCenter.default.addObserver(self, selector: #selector(willTerminate), name: UIApplication.willTerminateNotification, @@ -256,7 +226,10 @@ open class BaseMapView: UIView, CameraViewDelegate { if needsDisplayRefresh { needsDisplayRefresh = false - self.cameraView.update() + + for animator in cameraAnimatorsHashTable.allObjects { + animator.update() + } /// This executes the series of scheduled animation completion blocks and also removes them from the list while !pendingAnimatorCompletionBlocks.isEmpty { diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraAnimationDelegate.swift b/Sources/MapboxMaps/Foundation/Camera/CameraAnimationDelegate.swift index 9993040d28e..0140de9a32a 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraAnimationDelegate.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraAnimationDelegate.swift @@ -6,14 +6,24 @@ public typealias AnimationCompletion = (UIViewAnimatingPosition) -> Void // MARK: CameraAnimatorDelegate Protocol internal protocol CameraAnimatorDelegate: class { - /** - This delegate function notifies that the completion block needs to be scheduled + /// The current camera of the map + var camera: CameraOptions { get } - - Parameter animator: The current animator that this delegate function is being called from - - Parameter completion: The completion block that needs to be scheduled - - Parameter animatingPosition The position of the animation needed for the closure - */ - func schedulePendingCompletion(forAnimator animator: CameraAnimator, + /// Ask the map to set camera to new camera + func jumpTo(camera: CameraOptions) + + /// Adds the view to the MapView's subviews + func addViewToViewHeirarchy(_ view: CameraView) + + /// Calculates the anchor after taking padding into consideration + func anchorAfterPadding() -> CGPoint + + /// This delegate function notifies that the completion block needs to be scheduled on the next tick of the displaylink + /// - Parameters: + /// - animator: The current animator that this delegate function is being called from + /// - completion: The completion block that needs to be scheduled + /// - animatingPosition: The position of the animation needed for the closure + func schedulePendingCompletion(forAnimator animator: CameraAnimatorProtocol, completion: @escaping AnimationCompletion, animatingPosition: UIViewAnimatingPosition) } diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift b/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift index 1de6d45d5c6..6de32fb6778 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift @@ -1,20 +1,38 @@ import UIKit +import CoreLocation -// MARK: CameraAnimator Class -public class CameraAnimator: NSObject { +@objc public protocol CameraAnimatorProtocol: AnyObject { + + func stopAnimation() + + var state: UIViewAnimatingState { get } - // MARK: Stored Properties + func update() + +} + +public typealias CameraAnimation = (inout CameraTransition) -> Void + +// MARK: CameraAnimator Class +public class CameraAnimator: NSObject, CameraAnimatorProtocol { /// Instance of the property animator that will run animations. - private var propertyAnimator: UIViewPropertyAnimator + internal var propertyAnimator: UIViewPropertyAnimator /// Delegate that conforms to `CameraAnimatorDelegate`. - private weak var delegate: CameraAnimatorDelegate? + internal weak var delegate: CameraAnimatorDelegate? /// The ID of the owner of this `CameraAnimator`. internal var owner: AnimationOwner - // MARK: Computed Properties + /// The `CameraView` owned by this animator + internal var cameraView: CameraView + + /// Represents the animation that this animator is attempting to execute + internal var animation: CameraAnimation? + + /// Defines the transition that will occur to the `CameraOptions` of the renderer due to this animator + internal var transition: CameraTransition? /// The state from of the animator. public var state: UIViewAnimatingState { return propertyAnimator.state } @@ -40,27 +58,58 @@ public class CameraAnimator: NSObject { // MARK: Initializer internal init(delegate: CameraAnimatorDelegate, propertyAnimator: UIViewPropertyAnimator, - owner: AnimationOwner) { + owner: AnimationOwner, + cameraView: CameraView = CameraView()) { self.delegate = delegate self.propertyAnimator = propertyAnimator self.owner = owner + + // Set up the short lived camera view + self.cameraView = cameraView + delegate.addViewToViewHeirarchy(cameraView) } deinit { propertyAnimator.stopAnimation(false) propertyAnimator.finishAnimation(at: .current) + cameraView.removeFromSuperview() } - // MARK: Functions - - /// Starts the animation. + /// Starts the animation if this animator is in `inactive` state. Also used to resume a "paused" animation. public func startAnimation() { + + if self.state != .active { + + guard let delegate = delegate else { + fatalError("CameraAnimator delegate cannot be nil when starting an animation") + } + + guard let animation = animation else { + fatalError("Animation cannot be nil when starting an animation") + } + + var cameraTransition = CameraTransition(with: delegate.camera, initialAnchor: delegate.anchorAfterPadding()) + animation(&cameraTransition) + + propertyAnimator.addAnimations { [weak self] in + guard let self = self else { return } + self.cameraView.syncLayer(to: cameraTransition.toCameraOptions) // Set up the "to" values for the interpolation + } + + cameraView.syncLayer(to: cameraTransition.fromCameraOptions) // Set up the "from" values for the interpoloation + transition = cameraTransition // Store the mutated camera transition + } + propertyAnimator.startAnimation() } - /// Starts the animation after a `delay` which is of type `TimeInterval`. + /// Starts the animation after a delay + /// - Parameter delay: Delay (in seconds) after which the animation should start public func startAnimation(afterDelay delay: TimeInterval) { - propertyAnimator.startAnimation(afterDelay: delay) + DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in + guard let self = self else { return } + self.startAnimation() + } } /// Pauses the animation. @@ -74,27 +123,66 @@ public class CameraAnimator: NSObject { propertyAnimator.finishAnimation(at: .current) } - /// Add animations block to the animator with a `delayFactor`. - public func addAnimations(_ animations: @escaping () -> Void, delayFactor: Double) { - // if this cameraAnimator is not in the list of CameraAnimators held by the `CameraManager` then add it to that list - propertyAnimator.addAnimations(animations, delayFactor: CGFloat(delayFactor)) - } - /// Add animations block to the animator. - public func addAnimations(_ animations: @escaping () -> Void) { - propertyAnimator.addAnimations(animations) + internal func addAnimations(_ animations: @escaping CameraAnimation) { + animation = animations } /// Add a completion block to the animator. public func addCompletion(_ completion: @escaping AnimationCompletion) { - propertyAnimator.addCompletion({ [weak self] animatingPosition in - guard let self = self else { return } - self.delegate?.schedulePendingCompletion(forAnimator: self, completion: completion, animatingPosition: animatingPosition) - }) + let wrappedCompletion = wrapCompletion(completion) + propertyAnimator.addCompletion(wrappedCompletion) + } + + internal func wrapCompletion(_ completion: @escaping AnimationCompletion) -> (UIViewAnimatingPosition) -> Void { + return { [weak self] animationPosition in + guard let self = self, let delegate = self.delegate else { return } + self.transition = nil // Clear out the set maintaining the properties being animated by this animator -- since the animation is complete if we are here. + delegate.schedulePendingCompletion(forAnimator: self, completion: completion, animatingPosition: animationPosition) + } } /// Continue the animation with a timing parameter (`UITimingCurveProvider`) and duration factor (`CGFloat`). public func continueAnimation(withTimingParameters parameters: UITimingCurveProvider?, durationFactor: Double) { propertyAnimator.continueAnimation(withTimingParameters: parameters, durationFactor: CGFloat(durationFactor)) } + + public func update() { + + // Only call jumpTo if this animator is currently "active" and there are known changes to animate. + guard propertyAnimator.state == .active, + let transition = transition, + let delegate = delegate else { + return + } + + var cameraOptions = CameraOptions() + let interpolatedCamera = cameraView.localCamera + + if transition.center.toValue != nil { + cameraOptions.center = interpolatedCamera.center?.wrap() // Wraps to [-180, +180] + } + + if transition.bearing.toValue != nil { + cameraOptions.bearing = interpolatedCamera.bearing + } + + if transition.anchor.toValue != nil { + cameraOptions.anchor = interpolatedCamera.anchor + } + + if transition.padding.toValue != nil { + cameraOptions.padding = interpolatedCamera.padding + } + + if transition.zoom.toValue != nil { + cameraOptions.zoom = interpolatedCamera.zoom + } + + if transition.pitch.toValue != nil { + cameraOptions.pitch = interpolatedCamera.pitch + } + + delegate.jumpTo(camera: cameraOptions) + } } diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraManager+CameraAnimatorDelegate.swift b/Sources/MapboxMaps/Foundation/Camera/CameraManager+CameraAnimatorDelegate.swift index d155e5be882..7f2b53b6ba8 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraManager+CameraAnimatorDelegate.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraManager+CameraAnimatorDelegate.swift @@ -17,10 +17,12 @@ extension CameraManager: CameraAnimatorDelegate { /// - Returns: A class that represents an animator with the provided configuration. public func makeCameraAnimator(duration: TimeInterval, timingParameters parameters: UITimingCurveProvider, - animationOwner: AnimationOwner = .unspecified) -> CameraAnimator { + animationOwner: AnimationOwner = .unspecified, + animations: @escaping CameraAnimation) -> CameraAnimator { let propertyAnimator = UIViewPropertyAnimator(duration: duration, timingParameters: parameters) let cameraAnimator = CameraAnimator(delegate: self, propertyAnimator: propertyAnimator, owner: animationOwner) - cameraAnimators.add(cameraAnimator) + cameraAnimator.addAnimations(animations) + mapView?.cameraAnimatorsHashTable.add(cameraAnimator) return cameraAnimator } @@ -40,10 +42,11 @@ extension CameraManager: CameraAnimatorDelegate { public func makeCameraAnimator(duration: TimeInterval, curve: UIView.AnimationCurve, animationOwner: AnimationOwner = .unspecified, - animations: (() -> Void)? = nil) -> CameraAnimator { - let propertyAnimator = UIViewPropertyAnimator(duration: duration, curve: curve, animations: animations) + animations: @escaping CameraAnimation) -> CameraAnimator { + let propertyAnimator = UIViewPropertyAnimator(duration: duration, curve: curve) let cameraAnimator = CameraAnimator(delegate: self, propertyAnimator: propertyAnimator, owner: animationOwner) - cameraAnimators.add(cameraAnimator) + cameraAnimator.addAnimations(animations) + mapView?.cameraAnimatorsHashTable.add(cameraAnimator) return cameraAnimator } @@ -65,10 +68,11 @@ extension CameraManager: CameraAnimatorDelegate { controlPoint1 point1: CGPoint, controlPoint2 point2: CGPoint, animationOwner: AnimationOwner = .unspecified, - animations: (() -> Void)? = nil) -> CameraAnimator { - let propertyAnimator = UIViewPropertyAnimator(duration: duration, controlPoint1: point1, controlPoint2: point2, animations: animations) + animations: @escaping CameraAnimation) -> CameraAnimator { + let propertyAnimator = UIViewPropertyAnimator(duration: duration, controlPoint1: point1, controlPoint2: point2) let cameraAnimator = CameraAnimator(delegate: self, propertyAnimator: propertyAnimator, owner: animationOwner) - cameraAnimators.add(cameraAnimator) + cameraAnimator.addAnimations(animations) + mapView?.cameraAnimatorsHashTable.add(cameraAnimator) return cameraAnimator } @@ -89,16 +93,57 @@ extension CameraManager: CameraAnimatorDelegate { public func makeCameraAnimator(duration: TimeInterval, dampingRatio ratio: CGFloat, animationOwner: AnimationOwner = .unspecified, - animations: (() -> Void)? = nil) -> CameraAnimator { - let propertyAnimator = UIViewPropertyAnimator(duration: duration, dampingRatio: ratio, animations: animations) + animations: @escaping CameraAnimation) -> CameraAnimator { + let propertyAnimator = UIViewPropertyAnimator(duration: duration, dampingRatio: ratio) let cameraAnimator = CameraAnimator(delegate: self, propertyAnimator: propertyAnimator, owner: animationOwner) - cameraAnimators.add(cameraAnimator) + cameraAnimator.addAnimations(animations) + mapView?.cameraAnimatorsHashTable.add(cameraAnimator) return cameraAnimator } // MARK: CameraAnimatorDelegate functions - func schedulePendingCompletion(forAnimator animator: CameraAnimator, completion: @escaping AnimationCompletion, animatingPosition: UIViewAnimatingPosition) { + func schedulePendingCompletion(forAnimator animator: CameraAnimatorProtocol, completion: @escaping AnimationCompletion, animatingPosition: UIViewAnimatingPosition) { guard let mapView = mapView else { return } mapView.pendingAnimatorCompletionBlocks.append((completion, animatingPosition)) } + + var camera: CameraOptions { + guard let validMapView = mapView else { + fatalError("MapView cannot be nil.") + } + + return validMapView.cameraOptions + } + + func jumpTo(camera: CameraOptions) { + guard let validMapView = mapView else { + fatalError("MapView cannot be nil.") + } + + let mbxCameraOptions = MapboxCoreMaps.CameraOptions(camera) + validMapView.mapboxMap.__map.setCameraFor(mbxCameraOptions) + } + + func addViewToViewHeirarchy(_ view: CameraView) { + + guard let validMapView = mapView else { + fatalError("MapView cannot be nil.") + } + + validMapView.addSubview(view) + + } + + func anchorAfterPadding() -> CGPoint { + + guard let validMapView = mapView else { + fatalError("MapView cannot be nil.") + } + let paddding = validMapView.padding + let xAfterPadding = validMapView.center.x + paddding.left - paddding.right + let yAfterPadding = validMapView.center.y + paddding.top - paddding.bottom + let anchor = CGPoint(x: xAfterPadding, y: yAfterPadding) + + return anchor + } } diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift b/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift index 7f157ea1714..d23f93b26fc 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift @@ -18,16 +18,8 @@ public class CameraManager { mapCameraOptions = newOptions } - /// Pointer HashTable for holding camera animators - internal var cameraAnimators = NSHashTable.weakObjects() - - /// List of animators currently alive - public var cameraAnimatorsList: [CameraAnimator] { - return cameraAnimators.allObjects - } - /// Internal camera animator used for animated transition - internal var internalCameraAnimator: CameraAnimator? + internal var internalAnimator: CameraAnimatorProtocol? /// May want to convert to an enum. fileprivate let northBearing: CGFloat = 0 @@ -55,7 +47,7 @@ public class CameraManager { let coordinateLocations = coordinates.map { CLLocation(latitude: $0.latitude, longitude: $0.longitude) } // Construct new camera options with current values - let cameraOptions = MapboxCoreMaps.CameraOptions(mapView.cameraView.camera) + let cameraOptions = MapboxCoreMaps.CameraOptions(mapView.cameraOptions) let defaultEdgeInsets = EdgeInsets(top: 0, left: 0, bottom: 0, right: 0) @@ -140,11 +132,13 @@ public class CameraManager { return } + internalAnimator?.stopAnimation() + let clampedCamera = CameraOptions(center: targetCamera.center, padding: targetCamera.padding, anchor: targetCamera.anchor, zoom: targetCamera.zoom?.clamped(to: mapCameraOptions.minimumZoomLevel...mapCameraOptions.maximumZoomLevel), - bearing: optimizeBearing(startBearing: mapView.cameraView.localBearing, endBearing: targetCamera.bearing), + bearing: targetCamera.bearing, pitch: targetCamera.pitch?.clamped(to: mapCameraOptions.minimumPitch...mapCameraOptions.maximumPitch)) // Return early if the cameraView's camera is already at `clampedCamera` @@ -152,19 +146,25 @@ public class CameraManager { return } - let transitionBlock = { - mapView.cameraOptions = clampedCamera - } - if animated && duration > 0 { - performCameraAnimation(duration: duration, animation: transitionBlock, completion: completion) + let animation = { (transition: inout CameraTransition) in + transition.center.toValue = clampedCamera.center + transition.padding.toValue = clampedCamera.padding + transition.anchor.toValue = clampedCamera.anchor + transition.zoom.toValue = clampedCamera.zoom + transition.bearing.toValue = clampedCamera.bearing + transition.pitch.toValue = clampedCamera.pitch + } + performCameraAnimation(duration: duration, animation: animation, completion: completion) } else { - transitionBlock() + let mbxCamera = MapboxCoreMaps.CameraOptions(clampedCamera) + mapView.mapboxMap.__map.setCameraFor(mbxCamera) } } public func cancelAnimations() { - for animator in cameraAnimators.allObjects where animator.state == .active { + guard let validMapView = mapView else { return } + for animator in validMapView.cameraAnimatorsHashTable.allObjects where animator.state == UIViewAnimatingState.active { animator.stopAnimation() } } @@ -174,25 +174,29 @@ public class CameraManager { /// - duration: If animated, how long the animation takes /// - animation: closure to perform /// - completion: animation block called on completion - fileprivate func performCameraAnimation(duration: TimeInterval, animation: @escaping () -> Void, completion: ((UIViewAnimatingPosition) -> Void)? = nil) { + fileprivate func performCameraAnimation(duration: TimeInterval, animation: @escaping CameraAnimation, completion: ((UIViewAnimatingPosition) -> Void)? = nil) { // Stop previously running animations - internalCameraAnimator?.stopAnimation() + internalAnimator?.stopAnimation() // Make a new camera animator for the new properties - internalCameraAnimator = makeCameraAnimator(duration: duration, - curve: .easeOut, - animationOwner: .custom(id: "com.mapbox.maps.cameraManager"), - animations: animation) + + let cameraAnimator = makeCameraAnimator(duration: duration, + curve: .easeOut, + animationOwner: .custom(id: "com.mapbox.maps.cameraManager"), + animations: animation) // Add completion - internalCameraAnimator?.addCompletion({ [weak self] (position) in + cameraAnimator.addCompletion({ [weak self] (position) in completion?(position) - self?.internalCameraAnimator = nil + self?.internalAnimator = nil }) // Start animation - internalCameraAnimator?.startAnimation() + cameraAnimator.startAnimation() + + // Store the animator in order to keep it alive + internalAnimator = cameraAnimator } /// Moves the viewpoint to a different location using a transition animation that @@ -200,96 +204,67 @@ public class CameraManager { /// It seamlessly incorporates zooming and panning to help /// the user find his or her bearings even after traversing a great distance. /// - /// NOTE: Keep in mind the lifecycle of the `CameraAnimator` returned by this method. - /// If a `CameraAnimator` is destroyed, before the animation is finished, - /// the animation will be interrupted and completion handlers will be called. - /// /// - Parameters: /// - camera: The camera options at the end of the animation. Any camera parameters that are nil will not be animated. /// - duration: Duration of the animation, measured in seconds. If nil, a suitable calculated duration is used. /// - completion: Completion handler called when the animation stops - /// - Returns: The optional `CameraAnimator` that will execute the FlyTo animation + /// - Returns: An instance of `CameraAnimatorProtocol` which can be interrupted if necessary + @discardableResult public func fly(to camera: CameraOptions, duration: TimeInterval? = nil, - completion: AnimationCompletion? = nil) -> CameraAnimator? { + completion: AnimationCompletion? = nil) -> CameraAnimatorProtocol? { guard let mapView = mapView else { return nil } - // Stop the `internalCameraAnimator` before beginning a `flyTo` - internalCameraAnimator?.stopAnimation() - - guard let interpolator = FlyToInterpolator(from: mapView.cameraOptions, - to: camera, - size: mapView.bounds.size) else { - return nil - } - - // If there was no duration specified, use a default - let time: TimeInterval = duration ?? interpolator.duration() + // Stop the `internalAnimator` before beginning a `flyTo` + internalAnimator?.stopAnimation() - // TODO: Consider timesteps based on the flyTo curve, for example, it would be beneficial to have a higher - // density of time steps at towards the start and end of the animation to avoid jiggling. - let timeSteps = stride(from: 0.0, through: 1.0, by: 0.025) - let keyTimes: [Double] = Array(timeSteps) + let flyToAnimator = FlyToAnimator(delegate: self) + mapView.cameraAnimatorsHashTable.add(flyToAnimator) - let animator = makeCameraAnimator(duration: time, curve: .linear) { + flyToAnimator.makeFlyToInterpolator(from: mapView.cameraOptions, + to: camera, + duration: duration, + screenFullSize: mapView.bounds.size) - UIView.animateKeyframes(withDuration: 0, delay: 0, options: []) { + flyToAnimator.addCompletion(completion) + flyToAnimator.startAnimation() + internalAnimator = flyToAnimator - for keyTime in keyTimes { - let interpolatedCoordinate = interpolator.coordinate(at: keyTime) - let interpolatedZoom = interpolator.zoom(at: keyTime) - let interpolatedBearing = interpolator.bearing(at: keyTime) - let interpolatedPitch = interpolator.pitch(at: keyTime) + return internalAnimator + } - UIView.addKeyframe(withRelativeStartTime: keyTime, relativeDuration: 0.025) { - self.mapView?.cameraView.centerCoordinate = interpolatedCoordinate - self.mapView?.cameraView.zoom = CGFloat(interpolatedZoom) - self.mapView?.cameraView.bearing = CGFloat(interpolatedBearing) - self.mapView?.cameraView.pitch = CGFloat(interpolatedPitch) - } - } - } + /// Ease the camera to a destination + /// - Parameters: + /// - camera: the target camera after animation + /// - duration: duration of the animation + /// - completion: completion to be called after animation + /// - Returns: An instance of `CameraAnimatorProtocol` which can be interrupted if necessary + @discardableResult + public func ease(to camera: CameraOptions, duration: TimeInterval, completion: AnimationCompletion? = nil) -> CameraAnimatorProtocol? { + + internalAnimator?.stopAnimation() + + let animator = makeCameraAnimator(duration: duration, curve: .easeInOut) { (transition) in + transition.center.toValue = camera.center + transition.padding.toValue = camera.padding + transition.anchor.toValue = camera.anchor + transition.zoom.toValue = camera.zoom + transition.bearing.toValue = camera.bearing + transition.pitch.toValue = camera.pitch } if let completion = completion { animator.addCompletion(completion) } - animator.startAnimation() + internalAnimator = animator - return animator + return internalAnimator } - /// This function optimizes the bearing for set camera so that it is taking the shortest path. - /// - Parameters: - /// - startBearing: The current or start bearing of the map viewport. - /// - endBearing: The bearing of where the map viewport should end at. - /// - Returns: A `CLLocationDirection` that represents the correct final bearing accounting for positive and negatives. - internal func optimizeBearing(startBearing: CLLocationDirection?, endBearing: CLLocationDirection?) -> CLLocationDirection? { - // This modulus is required to account for larger values - guard - let startBearing = startBearing?.truncatingRemainder(dividingBy: 360.0), - let endBearing = endBearing?.truncatingRemainder(dividingBy: 360.0) - else { - return nil - } - - // 180 degrees is the max the map should rotate, therefore if the difference between the end and start point is - // more than 180 we need to go the opposite direction - if endBearing - startBearing >= 180 { - return endBearing - 360 - } - - // This is the inverse of the above, accounting for negative bearings - if endBearing - startBearing <= -180 { - return endBearing + 360 - } - - return endBearing - } } fileprivate extension CoordinateBounds { diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraTransition.swift b/Sources/MapboxMaps/Foundation/Camera/CameraTransition.swift new file mode 100644 index 00000000000..97498774c0f --- /dev/null +++ b/Sources/MapboxMaps/Foundation/Camera/CameraTransition.swift @@ -0,0 +1,108 @@ +import UIKit +import CoreLocation + +/// Structure used to represent a desired change to the map's camera +public struct CameraTransition { + + /// Represents a change to the center coordinate of the map. + public var center: Change + + /// Represents a change to the zoom of the map. + public var zoom: Change + + /// Represetns a change to the padding of the map. + public var padding: Change + + /// Represents a change to the anchor of the map + public var anchor: Change + + /// Represents a change to the bearing of the map. + public var bearing: Change + + /// Ensures that bearing transitions are optimized to take the shortest path. + public var shouldOptimizeBearingPath: Bool = true + + /// Represents a change to the pitch of the map. + public var pitch: Change + + /// Generic struct used to represent a change in a value from a starting point (i.e. `fromValue`) to an end point (i.e. `toValue`). + public struct Change { + public var fromValue: T + public var toValue: T? + + init(fromValue: T, toValue: T? = nil) { + self.fromValue = fromValue + self.toValue = toValue + } + } + + internal init(with renderedCameraOptions: CameraOptions, initialAnchor: CGPoint) { + + guard let renderedCenter = renderedCameraOptions.center, + let renderedZoom = renderedCameraOptions.zoom, + let renderedPadding = renderedCameraOptions.padding, + let renderedPitch = renderedCameraOptions.pitch, + let renderedBearing = renderedCameraOptions.bearing else { + fatalError("Values in rendered CameraOptions cannot be nil") + } + + center = .init(fromValue: renderedCenter) + zoom = .init(fromValue: renderedZoom) + padding = .init(fromValue: renderedPadding) + pitch = .init(fromValue: renderedPitch) + bearing = .init(fromValue: renderedBearing) + anchor = .init(fromValue: initialAnchor) + } + + internal var toCameraOptions: CameraOptions { + + return CameraOptions(center: center.toValue, + padding: padding.toValue, + anchor: anchor.toValue, + zoom: zoom.toValue, + bearing: shouldOptimizeBearingPath ? Self.optimizeBearing(startBearing: bearing.fromValue, + endBearing: bearing.toValue) + : bearing.toValue, + pitch: pitch.toValue) + } + + internal var fromCameraOptions: CameraOptions { + + return CameraOptions(center: center.fromValue, + padding: padding.fromValue, + anchor: anchor.fromValue, + zoom: zoom.fromValue, + bearing: bearing.fromValue, + pitch: pitch.fromValue) + + } + + /// This function optimizes the bearing for set camera so that it is taking the shortest path. + /// - Parameters: + /// - startBearing: The current or start bearing of the map viewport. + /// - endBearing: The bearing of where the map viewport should end at. + /// - Returns: A `CLLocationDirection` that represents the correct final bearing accounting for positive and negatives. + internal static func optimizeBearing(startBearing: CLLocationDirection?, endBearing: CLLocationDirection?) -> CLLocationDirection? { + // This modulus is required to account for larger values + guard + let startBearing = startBearing?.truncatingRemainder(dividingBy: 360.0), + let endBearing = endBearing?.truncatingRemainder(dividingBy: 360.0) + else { + return nil + } + + // 180 degrees is the max the map should rotate, therefore if the difference between the end and start point is + // more than 180 we need to go the opposite direction + if endBearing - startBearing >= 180 { + return endBearing - 360 + } + + // This is the inverse of the above, accounting for negative bearings + if endBearing - startBearing <= -180 { + return endBearing + 360 + } + + return endBearing + } + +} diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraView.swift b/Sources/MapboxMaps/Foundation/Camera/CameraView.swift index b685bc5119b..82b3d7e9094 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraView.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraView.swift @@ -1,132 +1,8 @@ import UIKit -/// Internal protocol that provides needed information / methods for the `CameraView` -internal protocol CameraViewDelegate: class { - /// The map's current camera - var cameraOptions: CameraOptions { get } - - /// The map's current center coordinate. - var centerCoordinate: CLLocationCoordinate2D { get } - - /// The map's zoom level. - var zoom: CGFloat { get } - - /// The map's bearing, measured clockwise from 0° north. - var bearing: CLLocationDirection { get } - - /// The map's pitch, falling within a range of 0 to 60. - var pitch: CGFloat { get } - - /// The map's camera padding - var padding: UIEdgeInsets { get } - - /// The map's camera anchor - var anchor: CGPoint { get } - - /// The map should jumpt to some camera - func jumpTo(camera: CameraOptions) -} - /// A view that represents a camera view port. internal class CameraView: UIView { - internal var camera: CameraOptions { - get { - return delegate.cameraOptions - } - set { - if let newZoom = newValue.zoom { - zoom = newZoom - } - - if let newBearing = newValue.bearing { - bearing = CGFloat(newBearing) - } - - if let newPitch = newValue.pitch { - pitch = newPitch - } - - if let newPadding = newValue.padding { - padding = newPadding - } - - if let newAnchor = newValue.anchor { - anchor = newAnchor - } - - if let newCenterCoordinate = newValue.center { - centerCoordinate = newCenterCoordinate - } - } - } - - /// The camera's zoom. Animatable. - @objc dynamic internal var zoom: CGFloat { - get { - return delegate.zoom - } - set { - layer.opacity = Float(newValue) - } - } - - /// The camera's bearing. Animatable. - @objc dynamic internal var bearing: CGFloat { - get { - return CGFloat(delegate.bearing) - } - - set { - layer.cornerRadius = newValue - } - } - - /// Coordinate at the center of the camera. Animatable. - @objc dynamic internal var centerCoordinate: CLLocationCoordinate2D { - get { - return delegate.centerCoordinate - } - - set { - layer.position = CGPoint(x: newValue.longitude, y: newValue.latitude) - } - } - - /// The camera's padding. Animatable. - @objc dynamic internal var padding: UIEdgeInsets { - get { - return delegate.padding - } - set { - layer.bounds = CGRect(x: newValue.left, - y: newValue.right, - width: newValue.bottom, - height: newValue.top) - } - } - - /// The camera's pitch. Animatable. - @objc dynamic internal var pitch: CGFloat { - get { - return delegate.pitch - } - set { - layer.transform.m11 = newValue - } - } - - /// The screen coordinate that the map rotates, pitches and zooms around. Setting this also affects the horizontal vanishing point when pitched. Animatable. - @objc dynamic internal var anchor: CGPoint { - get { - return layer.presentation()?.anchorPoint ?? layer.anchorPoint - } - - set { - layer.anchorPoint = newValue - } - } - internal var localCenterCoordinate: CLLocationCoordinate2D { let proxyCoord = layer.presentation()?.position ?? layer.position return CLLocationCoordinate2D(latitude: CLLocationDegrees(proxyCoord.y), @@ -166,83 +42,42 @@ internal class CameraView: UIView { pitch: localPitch) } - private unowned var delegate: CameraViewDelegate! - - init(delegate: CameraViewDelegate, edgeInsets: UIEdgeInsets = .zero) { - self.delegate = delegate + init() { super.init(frame: .zero) - self.isHidden = true self.isUserInteractionEnabled = false - - // Sync default values from MBXMap - setFromValuesWithMapView() } internal required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - private func setFromValuesWithMapView() { - zoom = delegate.zoom - bearing = CGFloat(delegate.bearing) - pitch = delegate.pitch - padding = delegate.padding - centerCoordinate = delegate.centerCoordinate - } - - internal func update() { - - // Retrieve currently rendered camera - let currentCamera = delegate.cameraOptions - - // Get the latest interpolated values of the camera properties (if they exist) - let targetCamera = localCamera.wrap() - - // Apply targetCamera options only if they are different from currentCamera options - if currentCamera != targetCamera { - - // Diff the targetCamera with the currentCamera and apply diffed camera properties to map - var diffedCamera = CameraOptions() - - if targetCamera.zoom != currentCamera.zoom, let targetZoom = targetCamera.zoom, !targetZoom.isNaN { - diffedCamera.zoom = targetCamera.zoom - } - - if targetCamera.bearing != currentCamera.bearing, let targetBearing = targetCamera.bearing, !targetBearing.isNaN { - diffedCamera.bearing = targetCamera.bearing - } - - if targetCamera.pitch != currentCamera.pitch, let targetPitch = targetCamera.pitch, !targetPitch.isNaN { - diffedCamera.pitch = targetCamera.pitch - } - - if targetCamera.center != currentCamera.center, let targetCenter = targetCamera.center, !targetCenter.latitude.isNaN, !targetCenter.longitude.isNaN { - diffedCamera.center = targetCamera.center - } - - if targetCamera.anchor != currentCamera.anchor { - diffedCamera.anchor = targetCamera.anchor - } + internal func syncLayer(to cameraOptions: CameraOptions) { + if let zoom = cameraOptions.zoom { + layer.opacity = Float(zoom) + } - if targetCamera.padding != currentCamera.padding { - diffedCamera.padding = targetCamera.padding - } + if let bearing = cameraOptions.bearing { + layer.cornerRadius = CGFloat(bearing) + } - delegate.jumpTo(camera: diffedCamera) + if let centerCoordinate = cameraOptions.center { + layer.position = CGPoint(x: centerCoordinate.longitude, y: centerCoordinate.latitude) } - } -} -fileprivate extension CameraOptions { + if let padding = cameraOptions.padding { + layer.bounds = CGRect(x: padding.left, + y: padding.right, + width: padding.bottom, + height: padding.top) + } - func wrap() -> CameraOptions { - return CameraOptions(center: self.center?.wrap(), - padding: self.padding, - anchor: self.anchor, - zoom: self.zoom, - bearing: self.bearing, - pitch: self.pitch) + if let pitch = cameraOptions.pitch { + layer.transform.m11 = pitch + } + if let anchor = cameraOptions.anchor { + layer.anchorPoint = anchor + } } } diff --git a/Sources/MapboxMaps/Foundation/Camera/FlyToAnimator.swift b/Sources/MapboxMaps/Foundation/Camera/FlyToAnimator.swift new file mode 100644 index 00000000000..baf6ccc3bbc --- /dev/null +++ b/Sources/MapboxMaps/Foundation/Camera/FlyToAnimator.swift @@ -0,0 +1,114 @@ +import UIKit + +internal class FlyToAnimator: NSObject, CameraAnimatorProtocol { + + internal weak var delegate: CameraAnimatorDelegate? + + internal var owner: AnimationOwner + + internal var flyToInterpolator: FlyToInterpolator? + + internal var animationDuration: TimeInterval? + + internal private(set) var state: UIViewAnimatingState = .inactive + + internal var startTime: Date? + + internal var endTime: Date? + + internal var finalCameraOptions: CameraOptions? + + internal var animationCompletion: AnimationCompletion? + + internal init(delegate: CameraAnimatorDelegate, + owner: AnimationOwner = .custom(id: "fly-to")) { + + self.delegate = delegate + self.owner = owner + } + + deinit { + flyToInterpolator = nil + stopAnimation() + } + + internal func makeFlyToInterpolator(from initalCamera: CameraOptions, to finalCamera: CameraOptions, duration: TimeInterval? = nil, screenFullSize: CGSize) { + + guard let flyTo = FlyToInterpolator(from: initalCamera, + to: finalCamera, + size: screenFullSize) else { + assertionFailure("FlyToInterpolator could not be created.") + return + } + + var time = duration ?? -1.0 + + // If there was no duration specified, or a negative argument, use a default + if time < 0.0 { + time = flyTo.duration() + } + + animationDuration = time + flyToInterpolator = flyTo + finalCameraOptions = finalCamera + } + + func stopAnimation() { + state = .stopped + flyToInterpolator = nil + scheduleCompletionIfNecessary(position: .current) // `current` represents an interrupted animation. + } + + func startAnimation() { + + guard flyToInterpolator != nil, let animationDuration = animationDuration else { + fatalError("FlyToInterpolator not created") + } + + state = .active + startTime = Date() + endTime = startTime?.addingTimeInterval(animationDuration) + } + + func addCompletion(_ completion: AnimationCompletion?) { + animationCompletion = completion + } + + func scheduleCompletionIfNecessary(position: UIViewAnimatingPosition) { + if let delegate = delegate, let animationCompletion = animationCompletion { + delegate.schedulePendingCompletion(forAnimator: self, + completion: animationCompletion, + animatingPosition: position) + } + } + + func update() { + + guard state == .active, + let startTime = startTime, + let endTime = endTime, + let animationDuration = animationDuration, + let flyTo = flyToInterpolator else { + return + } + + let currentTime = Date() + + guard currentTime <= endTime else { + flyToInterpolator = nil + state = .stopped + self.scheduleCompletionIfNecessary(position: .end) + return + } + + let fractionComplete = currentTime.timeIntervalSince(startTime) / animationDuration + + let cameraOptions = CameraOptions(center: flyTo.coordinate(at: fractionComplete), + zoom: CGFloat(flyTo.zoom(at: fractionComplete)), + bearing: flyTo.bearing(at: fractionComplete), + pitch: CGFloat(flyTo.pitch(at: fractionComplete))) + + delegate?.jumpTo(camera: cameraOptions) + + } +} diff --git a/Sources/MapboxMaps/Foundation/Extensions/Core/MBXEdgeInsets.swift b/Sources/MapboxMaps/Foundation/Extensions/Core/MBXEdgeInsets.swift index b0208864f6d..8481bd047ad 100644 --- a/Sources/MapboxMaps/Foundation/Extensions/Core/MBXEdgeInsets.swift +++ b/Sources/MapboxMaps/Foundation/Extensions/Core/MBXEdgeInsets.swift @@ -9,11 +9,18 @@ internal extension EdgeInsets { } } -internal extension UIEdgeInsets { +extension UIEdgeInsets: Hashable { func toMBXEdgeInsetsValue() -> EdgeInsets { return EdgeInsets(top: Double(self.top), left: Double(self.left), bottom: Double(self.bottom), right: Double(self.right)) } + + public func hash(into hasher: inout Hasher) { + hasher.combine(top) + hasher.combine(bottom) + hasher.combine(left) + hasher.combine(right) + } } diff --git a/Sources/MapboxMaps/Foundation/Extensions/CoreGraphics.swift b/Sources/MapboxMaps/Foundation/Extensions/CoreGraphics.swift index 0ba9d7b3669..5c8a748ca7d 100644 --- a/Sources/MapboxMaps/Foundation/Extensions/CoreGraphics.swift +++ b/Sources/MapboxMaps/Foundation/Extensions/CoreGraphics.swift @@ -4,7 +4,7 @@ import CoreGraphics import MapboxCoreMaps // MARK: - CGPoint -extension CGPoint { +extension CGPoint: Hashable { /// Converts a `CGPoint` to an internal `ScreenCoordinate` type. internal var screenCoordinate: ScreenCoordinate { @@ -24,6 +24,12 @@ extension CGPoint { return CGPoint(x: origin.x + fraction * (destination.x - origin.x), y: origin.y + fraction * (destination.y - origin.y)) } + + /// Hashable conformance + public func hash(into hasher: inout Hasher) { + hasher.combine(x) + hasher.combine(y) + } } // MARK: - CGFloat diff --git a/Sources/MapboxMaps/Foundation/Extensions/CoreLocation.swift b/Sources/MapboxMaps/Foundation/Extensions/CoreLocation.swift index 836aa9f970e..1979207399e 100644 --- a/Sources/MapboxMaps/Foundation/Extensions/CoreLocation.swift +++ b/Sources/MapboxMaps/Foundation/Extensions/CoreLocation.swift @@ -4,7 +4,13 @@ import CoreGraphics import MapboxCoreMaps // MARK: - CLLocationCoordinate2D -extension CLLocationCoordinate2D { +extension CLLocationCoordinate2D: Hashable { + + /// Hashable conformance + public func hash(into hasher: inout Hasher) { + hasher.combine(latitude) + hasher.combine(longitude) + } /// Converts a `CLLocationCoordinate` to a `CLLocation`. internal var location: CLLocation { diff --git a/Sources/MapboxMaps/Gestures/GestureManager+GestureHandlerDelegate.swift b/Sources/MapboxMaps/Gestures/GestureManager+GestureHandlerDelegate.swift index 9dcad056940..a4ade3938a7 100644 --- a/Sources/MapboxMaps/Gestures/GestureManager+GestureHandlerDelegate.swift +++ b/Sources/MapboxMaps/Gestures/GestureManager+GestureHandlerDelegate.swift @@ -102,14 +102,14 @@ extension GestureManager: GestureHandlerDelegate { return false } - return mapView.cameraView.zoom >= cameraManager.mapCameraOptions.minimumZoomLevel + return mapView.zoom >= cameraManager.mapCameraOptions.minimumZoomLevel } internal func rotationStartAngle() -> CGFloat { guard let mapView = cameraManager.mapView else { return 0 } - return (mapView.cameraView.bearing * .pi) / 180.0 * -1 + return CGFloat((mapView.bearing * .pi) / 180.0 * -1) } internal func rotationChanged(with changedAngle: CGFloat, and anchor: CGPoint, and pinchScale: CGFloat) { @@ -145,10 +145,10 @@ extension GestureManager: GestureHandlerDelegate { // Avoid contention with in-progress gestures // let toleranceForSnappingToNorth: CGFloat = 7.0 - if mapView.cameraView.bearing != 0.0 + if mapView.bearing != 0.0 && pinchState != .began && pinchState != .changed { - if mapView.cameraView.bearing != 0.0 && isRotationAllowed() == false { + if mapView.bearing != 0.0 && isRotationAllowed() == false { cameraManager.setCamera(to: CameraOptions(bearing: 0), animated: false, duration: 0, @@ -167,7 +167,7 @@ extension GestureManager: GestureHandlerDelegate { guard let mapView = cameraManager.mapView else { return 0 } - return mapView.cameraView.pitch + return mapView.pitch } internal func horizontalPitchTiltTolerance() -> Double { diff --git a/Sources/MapboxMaps/MapView/MapView+Supportable.swift b/Sources/MapboxMaps/MapView/MapView+Supportable.swift index bf9ed1e3a02..3a8087aa0df 100644 --- a/Sources/MapboxMaps/MapView/MapView+Supportable.swift +++ b/Sources/MapboxMaps/MapView/MapView+Supportable.swift @@ -8,13 +8,16 @@ extension MapView: OrnamentSupportableView { } internal func compassTapped() { - // Don't have access to CameraManager, so calling UIView.animate directly. - UIView.animate(withDuration: 0.3, - delay: 0.0, - options: [.curveEaseOut, .allowUserInteraction], - animations: { [weak self] in - self?.cameraView.bearing = 0.0 - }, completion: nil) + var animator: CameraAnimator? + animator = camera.makeCameraAnimator(duration: 0.3, curve: .easeOut, animations: { (transition) in + transition.bearing.toValue = 0 + }) + + animator?.addCompletion({ (_) in + animator = nil + }) + + animator?.startAnimation() } internal func subscribeCameraChangeHandler(_ handler: @escaping (CameraOptions) -> Void) { @@ -32,7 +35,7 @@ extension MapView: LocationSupportableMapView { } public func metersPerPointAtLatitude(latitude: CLLocationDegrees) -> CLLocationDistance { - return Projection.getMetersPerPixelAtLatitude(forLatitude: latitude, zoom: Double(cameraView.zoom)) + return Projection.getMetersPerPixelAtLatitude(forLatitude: latitude, zoom: Double(zoom)) } public func subscribeRenderFrameHandler(_ handler: @escaping (MapboxCoreMaps.Event) -> Void) { diff --git a/Tests/MapboxMapsTests/Foundation/Camera/CameraAnimatorDelegateMock.swift b/Tests/MapboxMapsTests/Foundation/Camera/CameraAnimatorDelegateMock.swift index 7a92f45bdb9..7e2f580cc55 100644 --- a/Tests/MapboxMapsTests/Foundation/Camera/CameraAnimatorDelegateMock.swift +++ b/Tests/MapboxMapsTests/Foundation/Camera/CameraAnimatorDelegateMock.swift @@ -1,24 +1,49 @@ import XCTest - -#if canImport(MapboxMaps) @testable import MapboxMaps -#else -@testable import MapboxMapsFoundation -#endif final class CameraAnimatorDelegateMock: CameraAnimatorDelegate { - struct CameraAnimatorDelegateParameters {} + struct SchedulePendingCompletionParameters { + var animator: CameraAnimatorProtocol + var completion: AnimationCompletion + var animatingPosition: UIViewAnimatingPosition + } - let cameraAnimatorStub = Stub() + let schedulePendingCompletionStub = Stub() - public func schedulePendingCompletion(forAnimator animator: CameraAnimator, + public func schedulePendingCompletion(forAnimator animator: CameraAnimatorProtocol, completion: @escaping AnimationCompletion, animatingPosition: UIViewAnimatingPosition) { - cameraAnimatorStub.call(with: CameraAnimatorDelegateParameters()) + schedulePendingCompletionStub.call(with: SchedulePendingCompletionParameters(animator: animator, + completion: completion, + animatingPosition: animatingPosition)) } + let animatorFinishedStub = Stub() public func animatorIsFinished(forAnimator animator: CameraAnimator) { - cameraAnimatorStub.call(with: CameraAnimatorDelegateParameters()) + animatorFinishedStub.call(with: animator) + } + + var camera: CameraOptions { + return CameraOptions(center: .init(latitude: 10, longitude: 10), + padding: .init(top: 10, left: 10, bottom: 10, right: 10), + zoom: 10, + bearing: 10, + pitch: 20) + } + + let jumpToStub = Stub() + func jumpTo(camera: CameraOptions) { + jumpToStub.call(with: camera) + } + + let addViewToViewHeirarchyStub = Stub() + func addViewToViewHeirarchy(_ view: CameraView) { + addViewToViewHeirarchyStub.call(with: view) + } + + let anchorAfterPaddingStub = Stub(defaultReturnValue: .zero) + func anchorAfterPadding() -> CGPoint { + return anchorAfterPaddingStub.call() } } diff --git a/Tests/MapboxMapsTests/Foundation/Camera/CameraAnimatorTests.swift b/Tests/MapboxMapsTests/Foundation/Camera/CameraAnimatorTests.swift index cec47f5a81b..6cecd9d94a2 100644 --- a/Tests/MapboxMapsTests/Foundation/Camera/CameraAnimatorTests.swift +++ b/Tests/MapboxMapsTests/Foundation/Camera/CameraAnimatorTests.swift @@ -1,28 +1,77 @@ import XCTest - -#if canImport(MapboxMaps) @testable import MapboxMaps -#else -@testable import MapboxMapsFoundation -#endif + +internal let cameraOptionsTestValue = CameraOptions(center: CLLocationCoordinate2D(latitude: 10, longitude: 10), + padding: .init(top: 10, left: 10, bottom: 10, right: 10), + anchor: .init(x: 10.0, y: 10.0), + zoom: 10, + bearing: 10, + pitch: 10) internal class CameraAnimatorTests: XCTestCase { // swiftlint:disable weak_delegate var delegate: CameraAnimatorDelegateMock! - var cameraAnimator: CameraAnimator! + var propertyAnimator: UIViewPropertyAnimatorMock! + var cameraView: CameraViewMock! + var animator: CameraAnimator? override func setUp() { delegate = CameraAnimatorDelegateMock() - cameraAnimator = CameraAnimator(delegate: delegate, - propertyAnimator: UIViewPropertyAnimator(), - owner: .unspecified) + propertyAnimator = UIViewPropertyAnimatorMock() + cameraView = CameraViewMock() + animator = CameraAnimator(delegate: delegate, + propertyAnimator: propertyAnimator , + owner: .unspecified, + cameraView: cameraView) } - func testAddCompletionSchedulesACompletion() { - cameraAnimator.addCompletion({ _ in - XCTAssertEqual(self.delegate.cameraAnimatorStub.invocations.count, 1) - }) + func testInitializationAndDeinit() { + XCTAssertEqual(delegate.addViewToViewHeirarchyStub.invocations.count, 1) + + animator = nil + XCTAssertEqual(propertyAnimator.stopAnimationStub.invocations.count, 1) + XCTAssertEqual(propertyAnimator.finishAnimationStub.invocations.count, 1) + XCTAssertEqual(cameraView.removeFromSuperviewStub.invocations.count, 1) + } + + func testStartAndStopAnimation() { + animator?.addAnimations { (transition) in + transition.zoom.toValue = cameraOptionsTestValue.zoom! + } + + animator?.startAnimation() + + XCTAssertEqual(propertyAnimator.startAnimationStub.invocations.count, 1) + XCTAssertEqual(propertyAnimator.addAnimationsStub.invocations.count, 1) + XCTAssertNotNil(animator?.transition) + XCTAssertEqual(animator?.transition?.toCameraOptions.zoom, 10) + + animator?.stopAnimation() + XCTAssertEqual(propertyAnimator.stopAnimationStub.invocations.count, 1) + XCTAssertEqual(propertyAnimator.finishAnimationStub.invocations.count, 1) + XCTAssertEqual(propertyAnimator.finishAnimationStub.invocations.first?.parameters.finalPosition, .current) + } + func testUpdate() { + animator?.addAnimations { (transition) in + transition.zoom.toValue = cameraOptionsTestValue.zoom! + transition.center.toValue = cameraOptionsTestValue.center! + transition.bearing.toValue = cameraOptionsTestValue.bearing! + transition.anchor.toValue = cameraOptionsTestValue.anchor! + transition.pitch.toValue = cameraOptionsTestValue.pitch! + transition.padding.toValue = cameraOptionsTestValue.padding! + } + + animator?.startAnimation() + + propertyAnimator.shouldReturnState = .active + animator?.update() + + XCTAssertEqual(delegate.jumpToStub.invocations.count, 1) + XCTAssertEqual(delegate.jumpToStub.invocations.first?.parameters, + cameraView.localCamera) + + } } diff --git a/Tests/MapboxMapsTests/Foundation/Camera/CameraTransitionTests.swift b/Tests/MapboxMapsTests/Foundation/Camera/CameraTransitionTests.swift new file mode 100644 index 00000000000..bbe801995bb --- /dev/null +++ b/Tests/MapboxMapsTests/Foundation/Camera/CameraTransitionTests.swift @@ -0,0 +1,75 @@ +import XCTest +@testable import MapboxMaps + +class CameraTransitionTests: XCTestCase { + + func testOptimizeBearingClockwise() { + let startBearing = 0.0 + let endBearing = 90.0 + let optimizedBearing = CameraTransition.optimizeBearing(startBearing: startBearing, endBearing: endBearing) + + XCTAssertEqual(optimizedBearing, 90.0) + } + + func testOptimizeBearingCounterClockwise() { + let startBearing = 0.0 + let endBearing = 270.0 + let optimizedBearing = CameraTransition.optimizeBearing(startBearing: startBearing, endBearing: endBearing) + + // We should rotate counter clockwise which is shown by a negative angle + XCTAssertEqual(optimizedBearing, -90.0) + } + + func testOptimizeBearingWhenBearingsAreTheSame() { + let startBearing = -90.0 + let endBearing = 270.0 + let optimizedBearing = CameraTransition.optimizeBearing(startBearing: startBearing, endBearing: endBearing) + + // -90 and 270 degrees is the same bearing so should just return original + XCTAssertEqual(optimizedBearing, -90) + } + + func testOptimizeBearingWhenStartBearingIsNegative() { + var optimizedBearing: CLLocationDirection? + + // Starting at -90 aka 270 should rotate clockwise to 20 + optimizedBearing = CameraTransition.optimizeBearing(startBearing: -90.0, endBearing: 20.0) + XCTAssertEqual(optimizedBearing, 20) + + // Starting at -90 aka 270 should rotate clockwise to -270 aka 90 + optimizedBearing = CameraTransition.optimizeBearing(startBearing: -90.0, endBearing: -270) + XCTAssertEqual(optimizedBearing, 90) + } + + func testOptimizeBearingHandlesNil() { + var optimizedBearing: CLLocationDirection? + + // Test when no end bearing is provided + optimizedBearing = CameraTransition.optimizeBearing(startBearing: 0.0, endBearing: nil) + XCTAssertNil(optimizedBearing) + + // Test when no start bearing is provided + optimizedBearing = CameraTransition.optimizeBearing(startBearing: nil, endBearing: 90) + XCTAssertNil(optimizedBearing) + + // Test when no bearings are provided + optimizedBearing = CameraTransition.optimizeBearing(startBearing: nil, endBearing: nil) + XCTAssertNil(optimizedBearing) + } + + func testOptimizeBearingLargerThan360() { + var optimizedBearing: CLLocationDirection? + + // 719 degrees is the same as 359 degrees. -1 should be returned because it is the shortest path from starting at 90 + optimizedBearing = CameraTransition.optimizeBearing(startBearing: 90.0, endBearing: 719) + XCTAssertEqual(optimizedBearing, -1.0) + + // -195 should be returned because it is the shortest path from starting at 180 + optimizedBearing = CameraTransition.optimizeBearing(startBearing: 180, endBearing: -555) + XCTAssertEqual(optimizedBearing, 165) + + // -160 should be returned because it is the shortest path from starting at 180 + optimizedBearing = CameraTransition.optimizeBearing(startBearing: 180, endBearing: -520) + XCTAssertEqual(optimizedBearing, 200) + } +} diff --git a/Tests/MapboxMapsTests/Foundation/Camera/CameraViewMock.swift b/Tests/MapboxMapsTests/Foundation/Camera/CameraViewMock.swift new file mode 100644 index 00000000000..516c5f04c47 --- /dev/null +++ b/Tests/MapboxMapsTests/Foundation/Camera/CameraViewMock.swift @@ -0,0 +1,24 @@ +import UIKit +@testable import MapboxMaps + +class CameraViewMock: CameraView { + + let localCameraStub = Stub(defaultReturnValue: cameraOptionsTestValue) + override var localCamera: CameraOptions { + return localCameraStub.call() + } + + struct SyncLayerParameters { + var cameraOptions: CameraOptions + } + let syncLayerStub = Stub() + override func syncLayer(to cameraOptions: CameraOptions) { + syncLayerStub.call(with: .init(cameraOptions: cameraOptions)) + } + + let removeFromSuperviewStub = Stub() + override func removeFromSuperview() { + removeFromSuperviewStub.call() + } + +} diff --git a/Tests/MapboxMapsTests/Foundation/Camera/MapboxMapsCameraTests.swift b/Tests/MapboxMapsTests/Foundation/Camera/MapboxMapsCameraTests.swift index e28c9f82d40..af270d73d7b 100644 --- a/Tests/MapboxMapsTests/Foundation/Camera/MapboxMapsCameraTests.swift +++ b/Tests/MapboxMapsTests/Foundation/Camera/MapboxMapsCameraTests.swift @@ -1,36 +1,79 @@ import XCTest -import MetalKit - -#if canImport(MapboxMaps) @testable import MapboxMaps -#else -@testable import MapboxMapsFoundation -#endif -class CameraManagerTests: XCTestCase { +internal class CameraManagerIntegrationTests: MapViewIntegrationTestCase { + + var cameraManager: CameraManager { + guard let mapView = mapView else { + fatalError("MapView must not be nil") + } + return mapView.camera + } - var mapView: BaseMapView! - var cameraManager: CameraManager! - var mapInitOptions: MapInitOptions! + func testSetCameraEnforcesMinZoom() { - override func setUp() { - mapInitOptions = MapInitOptions(resourceOptions: ResourceOptions(accessToken: "pk.feedcafedeadbeefbadebede")) + guard let mapView = mapView else { + XCTFail("MapView must not be nil") + return + } - mapView = BaseMapView(frame: CGRect(x: 0, y: 0, width: 100, height: 100), - mapInitOptions: mapInitOptions, - styleURI: nil) - cameraManager = CameraManager(for: mapView, with: MapCameraOptions()) + mapView.update(with: { (config) in + config.camera.minimumZoomLevel = CGFloat.random(in: 0..() + override func startAnimation() { + startAnimationStub.call() + } + + struct StopAnimationParameters { + var withoutFinishing: Bool + } + let stopAnimationStub = Stub() + override func stopAnimation(_ withoutFinishing: Bool) { + stopAnimationStub.call(with: StopAnimationParameters(withoutFinishing: withoutFinishing)) + } + + let pauseAnimationsStub = Stub() + override func pauseAnimation() { + pauseAnimationsStub.call() + } + + struct AddAnimationParameters { + var animation: () -> Void + } + let addAnimationsStub = Stub() + override func addAnimations(_ animation: @escaping () -> Void) { + addAnimationsStub.call(with: .init(animation: animation)) + } + + let addCompletionStub = Stub() + override func addCompletion(_ completion: @escaping (UIViewAnimatingPosition) -> Void) { + addCompletionStub.call() + } + + struct ContinueAnimationParameters { + var parameters: UITimingCurveProvider? + var durationFactor: CGFloat + } + let continueAnimationStub = Stub() + override func continueAnimation(withTimingParameters parameters: UITimingCurveProvider?, durationFactor: CGFloat) { + continueAnimationStub.call(with: .init(parameters: parameters, durationFactor: durationFactor)) + } + + struct FinishAnimationParameters { + var finalPosition: UIViewAnimatingPosition + } + + let finishAnimationStub = Stub() + override func finishAnimation(at finalPosition: UIViewAnimatingPosition) { + finishAnimationStub.call(with: .init(finalPosition: finalPosition)) + } +} diff --git a/Tests/MapboxMapsTests/Gestures/GestureManagerTests.swift b/Tests/MapboxMapsTests/Gestures/GestureManagerTests.swift index 29679768291..942dc4835f7 100644 --- a/Tests/MapboxMapsTests/Gestures/GestureManagerTests.swift +++ b/Tests/MapboxMapsTests/Gestures/GestureManagerTests.swift @@ -90,14 +90,6 @@ final class GestureManagerTests: XCTestCase { shouldRecognizeSimultaneouslyWith: tapGestureRecognizer)) } - func testScaleForZoom() { - mapView.cameraView.zoom = CGFloat.random(in: 0...22) - - let scale = gestureManager.scaleForZoom() - - XCTAssertEqual(scale, mapView.cameraView.zoom) - } - func testPinchScaleChanged_SetsCamera() { let zoom = CGFloat.random(in: 0...22) diff --git a/Tests/MapboxMapsTests/MapView/Integration Tests/ExampleIntegrationTest.swift b/Tests/MapboxMapsTests/MapView/Integration Tests/ExampleIntegrationTest.swift index 718c5717051..b9ef1645279 100644 --- a/Tests/MapboxMapsTests/MapView/Integration Tests/ExampleIntegrationTest.swift +++ b/Tests/MapboxMapsTests/MapView/Integration Tests/ExampleIntegrationTest.swift @@ -21,9 +21,6 @@ internal class ExampleIntegrationTest: MapViewIntegrationTestCase { style.uri = .streets - mapView.centerCoordinate = CLLocationCoordinate2D(latitude: 42.0, longitude: -71.0) - mapView.zoom = 8.0 - didFinishLoadingStyle = { _ in expectation.fulfill() } diff --git a/Tests/MapboxMapsTests/MapView/Integration Tests/Map/DidIdleFailureIntegrationTest.swift b/Tests/MapboxMapsTests/MapView/Integration Tests/Map/DidIdleFailureIntegrationTest.swift index ff58f427baf..9b0c219dfed 100644 --- a/Tests/MapboxMapsTests/MapView/Integration Tests/Map/DidIdleFailureIntegrationTest.swift +++ b/Tests/MapboxMapsTests/MapView/Integration Tests/Map/DidIdleFailureIntegrationTest.swift @@ -176,9 +176,6 @@ internal class DidIdleFailureIntegrationTest: IntegrationTestCase { style.uri = .streets - mapView.centerCoordinate = CLLocationCoordinate2D(latitude: 42.0, longitude: -71.0) - mapView.zoom = 8.0 - mapView.on(.mapLoadingError) { event in let userInfo: [String: Any] = (event.data as? [String: Any]) ?? [:] Log.error(forMessage: "Map failed to load with error: \(userInfo)", category: "Map") From 372559d68b192720a1b416de60d6806b58586c17 Mon Sep 17 00:00:00 2001 From: Nishant Karajgikar Date: Wed, 21 Apr 2021 10:19:46 -0400 Subject: [PATCH 02/40] Honor constant anchors in animated transitions --- .../MapboxMaps/Foundation/Camera/CameraAnimator.swift | 7 +++++-- .../MapboxMaps/Foundation/Camera/CameraTransition.swift | 9 +++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift b/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift index 6de32fb6778..4467dc87380 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift @@ -166,8 +166,11 @@ public class CameraAnimator: NSObject, CameraAnimatorProtocol { if transition.bearing.toValue != nil { cameraOptions.bearing = interpolatedCamera.bearing } - - if transition.anchor.toValue != nil { + + // Honor the constant anchor if provided as part of the transition + if transition.constantAnchor != nil { + cameraOptions.anchor = transition.constantAnchor + } else if transition.anchor.toValue != nil { cameraOptions.anchor = interpolatedCamera.anchor } diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraTransition.swift b/Sources/MapboxMaps/Foundation/Camera/CameraTransition.swift index 97498774c0f..632b729e417 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraTransition.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraTransition.swift @@ -5,6 +5,7 @@ import CoreLocation public struct CameraTransition { /// Represents a change to the center coordinate of the map. + /// NOTE: Setting the `toValue` of `center` overrides any `anchor` animations public var center: Change /// Represents a change to the zoom of the map. @@ -14,7 +15,13 @@ public struct CameraTransition { public var padding: Change /// Represents a change to the anchor of the map + /// NOTE: Incompatible with concurrent center animations public var anchor: Change + + /// Set this value in order to make bearing/zoom animations + /// honor a constant anchor throughout the transition + /// NOTE: Incompatible with concurrent center animations + public var constantAnchor: CGPoint? /// Represents a change to the bearing of the map. public var bearing: Change @@ -55,7 +62,6 @@ public struct CameraTransition { } internal var toCameraOptions: CameraOptions { - return CameraOptions(center: center.toValue, padding: padding.toValue, anchor: anchor.toValue, @@ -67,7 +73,6 @@ public struct CameraTransition { } internal var fromCameraOptions: CameraOptions { - return CameraOptions(center: center.fromValue, padding: padding.fromValue, anchor: anchor.fromValue, From 506187ec979c9d36aa660f50166808893f448744 Mon Sep 17 00:00:00 2001 From: Nishant Karajgikar Date: Wed, 21 Apr 2021 11:57:04 -0400 Subject: [PATCH 03/40] Minor refactoring --- Sources/MapboxMaps/Foundation/BaseMapView.swift | 17 ++++++++++++++--- .../CameraManager+CameraAnimatorDelegate.swift | 8 ++------ 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/Sources/MapboxMaps/Foundation/BaseMapView.swift b/Sources/MapboxMaps/Foundation/BaseMapView.swift index 0630612ea66..2b1c826242a 100644 --- a/Sources/MapboxMaps/Foundation/BaseMapView.swift +++ b/Sources/MapboxMaps/Foundation/BaseMapView.swift @@ -79,7 +79,7 @@ open class BaseMapView: UIView { return center } - /// The map's zoom level. + /// The map's current zoom level. public var zoom: CGFloat { guard let zoom = cameraOptions.zoom else { fatalError("Zoom is nil in camera options") @@ -87,7 +87,7 @@ open class BaseMapView: UIView { return CGFloat(zoom) } - /// The map's bearing, measured clockwise from 0° north. + /// The map's current bearing, measured clockwise from 0° north. public var bearing: CLLocationDirection { guard let bearing = cameraOptions.bearing else { fatalError("Bearing is nil in camera options") @@ -95,13 +95,24 @@ open class BaseMapView: UIView { return CLLocationDirection(bearing) } - /// The map's pitch, falling within a range of 0 to 60. + /// The map's current pitch, falling within a range of 0 to 60. public var pitch: CGFloat { guard let pitch = cameraOptions.pitch else { fatalError("Pitch is nil in camera options") } return pitch } + + /// The map's current anchor, calculated after applying padding (if it exists) + public var anchor: CGPoint { + + let paddding = padding + let xAfterPadding = center.x + paddding.left - paddding.right + let yAfterPadding = center.y + paddding.top - paddding.bottom + let anchor = CGPoint(x: xAfterPadding, y: yAfterPadding) + + return anchor + } /// The map's camera padding public var padding: UIEdgeInsets { diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraManager+CameraAnimatorDelegate.swift b/Sources/MapboxMaps/Foundation/Camera/CameraManager+CameraAnimatorDelegate.swift index 7f2b53b6ba8..3736e50ff0f 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraManager+CameraAnimatorDelegate.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraManager+CameraAnimatorDelegate.swift @@ -139,11 +139,7 @@ extension CameraManager: CameraAnimatorDelegate { guard let validMapView = mapView else { fatalError("MapView cannot be nil.") } - let paddding = validMapView.padding - let xAfterPadding = validMapView.center.x + paddding.left - paddding.right - let yAfterPadding = validMapView.center.y + paddding.top - paddding.bottom - let anchor = CGPoint(x: xAfterPadding, y: yAfterPadding) - - return anchor + + return validMapView.anchor } } From 1e4caafdb3938e454b3494e0333c0d8039752176 Mon Sep 17 00:00:00 2001 From: Nishant Karajgikar Date: Wed, 21 Apr 2021 15:25:31 -0400 Subject: [PATCH 04/40] fix lint --- .../Examples/All Examples/CameraAnimatorsExample.swift | 1 - Sources/MapboxMaps/Foundation/BaseMapView.swift | 4 ++-- Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift | 2 +- .../Camera/CameraManager+CameraAnimatorDelegate.swift | 4 ++-- Sources/MapboxMaps/Foundation/Camera/CameraTransition.swift | 2 +- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Apps/Examples/Examples/All Examples/CameraAnimatorsExample.swift b/Apps/Examples/Examples/All Examples/CameraAnimatorsExample.swift index 92bd9968177..edb8739ed0e 100644 --- a/Apps/Examples/Examples/All Examples/CameraAnimatorsExample.swift +++ b/Apps/Examples/Examples/All Examples/CameraAnimatorsExample.swift @@ -56,7 +56,6 @@ public class CameraAnimatorsExample: UIViewController, ExampleProtocol { mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight] view.addSubview(mapView) - mapView.on(.styleLoaded) { [weak self] _ in guard let self = self else { return } // Center the map over New York City. diff --git a/Sources/MapboxMaps/Foundation/BaseMapView.swift b/Sources/MapboxMaps/Foundation/BaseMapView.swift index 2b1c826242a..311a0fa1321 100644 --- a/Sources/MapboxMaps/Foundation/BaseMapView.swift +++ b/Sources/MapboxMaps/Foundation/BaseMapView.swift @@ -102,10 +102,10 @@ open class BaseMapView: UIView { } return pitch } - + /// The map's current anchor, calculated after applying padding (if it exists) public var anchor: CGPoint { - + let paddding = padding let xAfterPadding = center.x + paddding.left - paddding.right let yAfterPadding = center.y + paddding.top - paddding.bottom diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift b/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift index 4467dc87380..7eb5493d87b 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift @@ -166,7 +166,7 @@ public class CameraAnimator: NSObject, CameraAnimatorProtocol { if transition.bearing.toValue != nil { cameraOptions.bearing = interpolatedCamera.bearing } - + // Honor the constant anchor if provided as part of the transition if transition.constantAnchor != nil { cameraOptions.anchor = transition.constantAnchor diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraManager+CameraAnimatorDelegate.swift b/Sources/MapboxMaps/Foundation/Camera/CameraManager+CameraAnimatorDelegate.swift index 3736e50ff0f..bcc680b5420 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraManager+CameraAnimatorDelegate.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraManager+CameraAnimatorDelegate.swift @@ -119,7 +119,7 @@ extension CameraManager: CameraAnimatorDelegate { guard let validMapView = mapView else { fatalError("MapView cannot be nil.") } - + let mbxCameraOptions = MapboxCoreMaps.CameraOptions(camera) validMapView.mapboxMap.__map.setCameraFor(mbxCameraOptions) } @@ -139,7 +139,7 @@ extension CameraManager: CameraAnimatorDelegate { guard let validMapView = mapView else { fatalError("MapView cannot be nil.") } - + return validMapView.anchor } } diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraTransition.swift b/Sources/MapboxMaps/Foundation/Camera/CameraTransition.swift index 632b729e417..5ad1964cc2a 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraTransition.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraTransition.swift @@ -17,7 +17,7 @@ public struct CameraTransition { /// Represents a change to the anchor of the map /// NOTE: Incompatible with concurrent center animations public var anchor: Change - + /// Set this value in order to make bearing/zoom animations /// honor a constant anchor throughout the transition /// NOTE: Incompatible with concurrent center animations From 23be375d0f55b968ed44e3fe9375490c9c77d132 Mon Sep 17 00:00:00 2001 From: Nishant Karajgikar Date: Wed, 21 Apr 2021 15:30:42 -0400 Subject: [PATCH 05/40] Fix stresstest app --- Apps/StressTest/StressTest/ViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Apps/StressTest/StressTest/ViewController.swift b/Apps/StressTest/StressTest/ViewController.swift index f095461121d..5f1c642da23 100644 --- a/Apps/StressTest/StressTest/ViewController.swift +++ b/Apps/StressTest/StressTest/ViewController.swift @@ -203,7 +203,7 @@ class ViewController: UIViewController { let endOptions = CameraOptions(center: end, zoom: 17) - var animator: CameraAnimator? + var animator: CameraAnimatorProtocol? animator = mapView.camera.fly(to: endOptions) { _ in print("Removing line annotation for animator \(String(describing: animator))") self.mapView.annotations.removeAnnotation(lineAnnotation) From 5d05d6e8bbb85ebdd0821c8496663a730e761c32 Mon Sep 17 00:00:00 2001 From: Nishant Karajgikar Date: Wed, 21 Apr 2021 15:41:33 -0400 Subject: [PATCH 06/40] Expose animation owner on CameraAnimator --- .../Foundation/Camera/CameraAnimator.swift | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift b/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift index 7eb5493d87b..4efa426fd94 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift @@ -2,13 +2,15 @@ import UIKit import CoreLocation @objc public protocol CameraAnimatorProtocol: AnyObject { - + + /// Stops the animation in its tracks and calls any provided completion func stopAnimation() - + + /// The current state of the animation var state: UIViewAnimatingState { get } + /// TODO: Move this to a non-public interface func update() - } public typealias CameraAnimation = (inout CameraTransition) -> Void @@ -23,7 +25,7 @@ public class CameraAnimator: NSObject, CameraAnimatorProtocol { internal weak var delegate: CameraAnimatorDelegate? /// The ID of the owner of this `CameraAnimator`. - internal var owner: AnimationOwner + public internal(set) var owner: AnimationOwner /// The `CameraView` owned by this animator internal var cameraView: CameraView @@ -32,7 +34,7 @@ public class CameraAnimator: NSObject, CameraAnimatorProtocol { internal var animation: CameraAnimation? /// Defines the transition that will occur to the `CameraOptions` of the renderer due to this animator - internal var transition: CameraTransition? + public internal(set) var transition: CameraTransition? /// The state from of the animator. public var state: UIViewAnimatingState { return propertyAnimator.state } From 89a4c128e8a78e742abfb1b9c55f2d38f0d8079b Mon Sep 17 00:00:00 2001 From: Nishant Karajgikar Date: Wed, 21 Apr 2021 15:53:06 -0400 Subject: [PATCH 07/40] Update changelog --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bbee2aea293..4e9e5bf35bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,15 @@ Mapbox welcomes participation and contributions from everyone. * `Snapshotter` no longer conforms to `Observer`, and the method it required is now internal. * The `BaseMapView.__map` property has been moved to `BaseMapView.mapboxMap.__map`. ([#280](https://github.com/mapbox/mapbox-maps-ios/pull/280)) * A `CameraOptions` struct has been introduced. This shadows the class of the same name from MapboxCoreMaps and. This avoids unintended sharing and better reflects the intended value semantics of the `CameraOptions` concept. ([#284](https://github.com/mapbox/mapbox-maps-ios/pull/284)) + + #### Camera Animations + * A new `CameraTransition` struct has been introduced to allow better control on the "from" and "to" values of a camera animation ([#282](https://github.com/mapbox/mapbox-maps-ios/pull/282)) + * A mutable version of the `CameraTransition` struct is passed into every animation block. + * Animations can only be constructor injected into `CameraAnimator` as part of the `makeCameraAnimator*` methods on `mapView.camera`. + + #### Gestures + - Gestures now directly call `__map.setCamera()` instead of going via CoreAnimation + - #### Dependencies * Updated dependencies to MapboxCoreMaps 10.0.0-beta.20 and MapboxCommon 11.0.1 From dc81f3a760dd259ea033fd0735d76bd8085db378 Mon Sep 17 00:00:00 2001 From: Nishant Karajgikar Date: Wed, 21 Apr 2021 16:30:38 -0400 Subject: [PATCH 08/40] cleanup --- CHANGELOG.md | 4 ++-- .../Camera/CameraManager+CameraAnimatorDelegate.swift | 4 +--- Sources/MapboxMaps/Foundation/Camera/CameraManager.swift | 3 +-- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e9e5bf35bc..dde685bc299 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,12 +25,12 @@ Mapbox welcomes participation and contributions from everyone. * A `CameraOptions` struct has been introduced. This shadows the class of the same name from MapboxCoreMaps and. This avoids unintended sharing and better reflects the intended value semantics of the `CameraOptions` concept. ([#284](https://github.com/mapbox/mapbox-maps-ios/pull/284)) #### Camera Animations - * A new `CameraTransition` struct has been introduced to allow better control on the "from" and "to" values of a camera animation ([#282](https://github.com/mapbox/mapbox-maps-ios/pull/282)) + * A new `CameraTransition` struct has been introduced to allow better control on the "from" and "to" values of a camera animation ([#282](https://github.com/mapbox/mapbox-maps-ios/pull/282)) * A mutable version of the `CameraTransition` struct is passed into every animation block. * Animations can only be constructor injected into `CameraAnimator` as part of the `makeCameraAnimator*` methods on `mapView.camera`. #### Gestures - - Gestures now directly call `__map.setCamera()` instead of going via CoreAnimation + - Gestures now directly call `__map.setCamera()` instead of using CoreAnimation - #### Dependencies diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraManager+CameraAnimatorDelegate.swift b/Sources/MapboxMaps/Foundation/Camera/CameraManager+CameraAnimatorDelegate.swift index bcc680b5420..d770aa9b5ff 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraManager+CameraAnimatorDelegate.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraManager+CameraAnimatorDelegate.swift @@ -119,9 +119,7 @@ extension CameraManager: CameraAnimatorDelegate { guard let validMapView = mapView else { fatalError("MapView cannot be nil.") } - - let mbxCameraOptions = MapboxCoreMaps.CameraOptions(camera) - validMapView.mapboxMap.__map.setCameraFor(mbxCameraOptions) + validMapView.mapboxMap.updateCamera(with: camera) } func addViewToViewHeirarchy(_ view: CameraView) { diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift b/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift index d23f93b26fc..cb59fffa4aa 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift @@ -157,8 +157,7 @@ public class CameraManager { } performCameraAnimation(duration: duration, animation: animation, completion: completion) } else { - let mbxCamera = MapboxCoreMaps.CameraOptions(clampedCamera) - mapView.mapboxMap.__map.setCameraFor(mbxCamera) + mapView.mapboxMap.updateCamera(with: clampedCamera) } } From 329390a349518103fedda098ffffedd56148c538 Mon Sep 17 00:00:00 2001 From: Nishant Karajgikar Date: Wed, 21 Apr 2021 16:38:58 -0400 Subject: [PATCH 09/40] Rename makeCameraAnimator* methods to makeAnimator* methods --- .../All Examples/CameraAnimatorsExample.swift | 6 ++-- CHANGELOG.md | 3 +- .../Foundation/Camera/CameraAnimator.swift | 4 +-- ...CameraManager+CameraAnimatorDelegate.swift | 34 +++++++++---------- .../Foundation/Camera/CameraManager.swift | 4 +-- .../MapView/MapView+Supportable.swift | 2 +- 6 files changed, 27 insertions(+), 26 deletions(-) diff --git a/Apps/Examples/Examples/All Examples/CameraAnimatorsExample.swift b/Apps/Examples/Examples/All Examples/CameraAnimatorsExample.swift index edb8739ed0e..0652fc30314 100644 --- a/Apps/Examples/Examples/All Examples/CameraAnimatorsExample.swift +++ b/Apps/Examples/Examples/All Examples/CameraAnimatorsExample.swift @@ -12,7 +12,7 @@ public class CameraAnimatorsExample: UIViewController, ExampleProtocol { // Store the CameraAnimators so that the do not fall out of scope. lazy var zoomAnimator: CameraAnimator = { - let animator = mapView.camera.makeCameraAnimator(duration: 4, curve: .easeInOut) { (transition) in + let animator = mapView.camera.makeAnimator(duration: 4, curve: .easeInOut) { (transition) in transition.zoom.toValue = 14 } @@ -25,7 +25,7 @@ public class CameraAnimatorsExample: UIViewController, ExampleProtocol { }() lazy var pitchAnimator: CameraAnimator = { - let animator = mapView.camera.makeCameraAnimator(duration: 2, curve: .easeInOut) { (transition) in + let animator = mapView.camera.makeAnimator(duration: 2, curve: .easeInOut) { (transition) in transition.pitch.toValue = 55 } @@ -38,7 +38,7 @@ public class CameraAnimatorsExample: UIViewController, ExampleProtocol { }() lazy var bearingAnimator: CameraAnimator = { - let animator = mapView.camera.makeCameraAnimator(duration: 4, curve: .easeInOut) { (transition) in + let animator = mapView.camera.makeAnimator(duration: 4, curve: .easeInOut) { (transition) in transition.bearing.toValue = -45 } diff --git a/CHANGELOG.md b/CHANGELOG.md index dde685bc299..5e3271c9de0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,8 @@ Mapbox welcomes participation and contributions from everyone. #### Camera Animations * A new `CameraTransition` struct has been introduced to allow better control on the "from" and "to" values of a camera animation ([#282](https://github.com/mapbox/mapbox-maps-ios/pull/282)) * A mutable version of the `CameraTransition` struct is passed into every animation block. - * Animations can only be constructor injected into `CameraAnimator` as part of the `makeCameraAnimator*` methods on `mapView.camera`. + * Animations can only be constructor injected into `CameraAnimator` as part of the `makeAnimator*` methods on `mapView.camera`. + * The `makeCameraAnimator*` methods have been renamed to `makeAnimator*` methods #### Gestures - Gestures now directly call `__map.setCamera()` instead of using CoreAnimation diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift b/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift index 4efa426fd94..381d511419b 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift @@ -2,10 +2,10 @@ import UIKit import CoreLocation @objc public protocol CameraAnimatorProtocol: AnyObject { - + /// Stops the animation in its tracks and calls any provided completion func stopAnimation() - + /// The current state of the animation var state: UIViewAnimatingState { get } diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraManager+CameraAnimatorDelegate.swift b/Sources/MapboxMaps/Foundation/Camera/CameraManager+CameraAnimatorDelegate.swift index d770aa9b5ff..a3a39b6971d 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraManager+CameraAnimatorDelegate.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraManager+CameraAnimatorDelegate.swift @@ -15,10 +15,10 @@ extension CameraManager: CameraAnimatorDelegate { /// - timingParameters: The object providing the timing information. This object must adopt the `UITimingCurveProvider` protocol. /// - animationOwner: Property that conforms to `AnimationOwnerProtocol` to represent who owns that animation. /// - Returns: A class that represents an animator with the provided configuration. - public func makeCameraAnimator(duration: TimeInterval, - timingParameters parameters: UITimingCurveProvider, - animationOwner: AnimationOwner = .unspecified, - animations: @escaping CameraAnimation) -> CameraAnimator { + public func makeAnimator(duration: TimeInterval, + timingParameters parameters: UITimingCurveProvider, + animationOwner: AnimationOwner = .unspecified, + animations: @escaping CameraAnimation) -> CameraAnimator { let propertyAnimator = UIViewPropertyAnimator(duration: duration, timingParameters: parameters) let cameraAnimator = CameraAnimator(delegate: self, propertyAnimator: propertyAnimator, owner: animationOwner) cameraAnimator.addAnimations(animations) @@ -39,10 +39,10 @@ extension CameraManager: CameraAnimatorDelegate { /// Use this block to modify any animatable view properties. When you start the animations, /// those properties are animated from their current values to the new values using the specified animation parameters. /// - Returns: A class that represents an animator with the provided configuration. - public func makeCameraAnimator(duration: TimeInterval, - curve: UIView.AnimationCurve, - animationOwner: AnimationOwner = .unspecified, - animations: @escaping CameraAnimation) -> CameraAnimator { + public func makeAnimator(duration: TimeInterval, + curve: UIView.AnimationCurve, + animationOwner: AnimationOwner = .unspecified, + animations: @escaping CameraAnimation) -> CameraAnimator { let propertyAnimator = UIViewPropertyAnimator(duration: duration, curve: curve) let cameraAnimator = CameraAnimator(delegate: self, propertyAnimator: propertyAnimator, owner: animationOwner) cameraAnimator.addAnimations(animations) @@ -64,11 +64,11 @@ extension CameraManager: CameraAnimatorDelegate { /// Use this block to modify any animatable view properties. When you start the animations, /// those properties are animated from their current values to the new values using the specified animation parameters. /// - Returns: A class that represents an animator with the provided configuration. - public func makeCameraAnimator(duration: TimeInterval, - controlPoint1 point1: CGPoint, - controlPoint2 point2: CGPoint, - animationOwner: AnimationOwner = .unspecified, - animations: @escaping CameraAnimation) -> CameraAnimator { + public func makeAnimator(duration: TimeInterval, + controlPoint1 point1: CGPoint, + controlPoint2 point2: CGPoint, + animationOwner: AnimationOwner = .unspecified, + animations: @escaping CameraAnimation) -> CameraAnimator { let propertyAnimator = UIViewPropertyAnimator(duration: duration, controlPoint1: point1, controlPoint2: point2) let cameraAnimator = CameraAnimator(delegate: self, propertyAnimator: propertyAnimator, owner: animationOwner) cameraAnimator.addAnimations(animations) @@ -90,10 +90,10 @@ extension CameraManager: CameraAnimatorDelegate { /// Use this block to modify any animatable view properties. When you start the animations, /// those properties are animated from their current values to the new values using the specified animation parameters. /// - Returns: A class that represents an animator with the provided configuration. - public func makeCameraAnimator(duration: TimeInterval, - dampingRatio ratio: CGFloat, - animationOwner: AnimationOwner = .unspecified, - animations: @escaping CameraAnimation) -> CameraAnimator { + public func makeAnimator(duration: TimeInterval, + dampingRatio ratio: CGFloat, + animationOwner: AnimationOwner = .unspecified, + animations: @escaping CameraAnimation) -> CameraAnimator { let propertyAnimator = UIViewPropertyAnimator(duration: duration, dampingRatio: ratio) let cameraAnimator = CameraAnimator(delegate: self, propertyAnimator: propertyAnimator, owner: animationOwner) cameraAnimator.addAnimations(animations) diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift b/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift index cb59fffa4aa..0f6fac6b9e1 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift @@ -180,7 +180,7 @@ public class CameraManager { // Make a new camera animator for the new properties - let cameraAnimator = makeCameraAnimator(duration: duration, + let cameraAnimator = makeAnimator(duration: duration, curve: .easeOut, animationOwner: .custom(id: "com.mapbox.maps.cameraManager"), animations: animation) @@ -246,7 +246,7 @@ public class CameraManager { internalAnimator?.stopAnimation() - let animator = makeCameraAnimator(duration: duration, curve: .easeInOut) { (transition) in + let animator = makeAnimator(duration: duration, curve: .easeInOut) { (transition) in transition.center.toValue = camera.center transition.padding.toValue = camera.padding transition.anchor.toValue = camera.anchor diff --git a/Sources/MapboxMaps/MapView/MapView+Supportable.swift b/Sources/MapboxMaps/MapView/MapView+Supportable.swift index 3a8087aa0df..afd3ef7d0c3 100644 --- a/Sources/MapboxMaps/MapView/MapView+Supportable.swift +++ b/Sources/MapboxMaps/MapView/MapView+Supportable.swift @@ -9,7 +9,7 @@ extension MapView: OrnamentSupportableView { internal func compassTapped() { var animator: CameraAnimator? - animator = camera.makeCameraAnimator(duration: 0.3, curve: .easeOut, animations: { (transition) in + animator = camera.makeAnimator(duration: 0.3, curve: .easeOut, animations: { (transition) in transition.bearing.toValue = 0 }) From dffe75a4a6326fc417fb7c42842cd6eadecf515d Mon Sep 17 00:00:00 2001 From: Nishant Karajgikar Date: Wed, 21 Apr 2021 18:06:27 -0400 Subject: [PATCH 10/40] Nil out the internal animator when an animation completes --- .../MapboxMaps/Foundation/Camera/CameraManager.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift b/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift index 0f6fac6b9e1..8fef7afc70b 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift @@ -228,6 +228,12 @@ public class CameraManager { duration: duration, screenFullSize: mapView.bounds.size) + // Nil out the internalAnimator after `flyTo` finishes + flyToAnimator.addCompletion { [weak self](_) in + self?.internalAnimator = nil + } + + // Add the developer-provided completion (if present) flyToAnimator.addCompletion(completion) flyToAnimator.startAnimation() internalAnimator = flyToAnimator @@ -254,7 +260,13 @@ public class CameraManager { transition.bearing.toValue = camera.bearing transition.pitch.toValue = camera.pitch } + + // Nil out the `internalAnimator` once the "ease to" finishes + animator.addCompletion { [weak self] (_) in + self?.internalAnimator = nil + } + // Add the developer-provided completion (if present) if let completion = completion { animator.addCompletion(completion) } From 5f600e55ee659b4dc6b151806e51544a5e2f8adc Mon Sep 17 00:00:00 2001 From: Nishant Karajgikar Date: Thu, 22 Apr 2021 09:57:08 -0400 Subject: [PATCH 11/40] fix lint --- Sources/MapboxMaps/Foundation/Camera/CameraManager.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift b/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift index 8fef7afc70b..d86d8416a7e 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift @@ -232,7 +232,7 @@ public class CameraManager { flyToAnimator.addCompletion { [weak self](_) in self?.internalAnimator = nil } - + // Add the developer-provided completion (if present) flyToAnimator.addCompletion(completion) flyToAnimator.startAnimation() @@ -260,7 +260,7 @@ public class CameraManager { transition.bearing.toValue = camera.bearing transition.pitch.toValue = camera.pitch } - + // Nil out the `internalAnimator` once the "ease to" finishes animator.addCompletion { [weak self] (_) in self?.internalAnimator = nil From be3b10f7e5645acf11a91850f426d181f331c214 Mon Sep 17 00:00:00 2001 From: Nishant Karajgikar Date: Thu, 22 Apr 2021 10:48:39 -0400 Subject: [PATCH 12/40] Add CameraViewTests --- .../Foundation/Camera/CameraViewTests.swift | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 Tests/MapboxMapsTests/Foundation/Camera/CameraViewTests.swift diff --git a/Tests/MapboxMapsTests/Foundation/Camera/CameraViewTests.swift b/Tests/MapboxMapsTests/Foundation/Camera/CameraViewTests.swift new file mode 100644 index 00000000000..ab0690ff441 --- /dev/null +++ b/Tests/MapboxMapsTests/Foundation/Camera/CameraViewTests.swift @@ -0,0 +1,39 @@ +import XCTest +@testable import MapboxMaps + +final class CameraViewTests: XCTestCase { + + let cameraOptions = CameraOptions(center: .init(latitude: 10, longitude: 10), + padding: .init(top: 10, left: 10, bottom: 10, right: 10), + anchor: .init(x: 10, y: 10), + zoom: 10, + bearing: 10, + pitch: 10) + + var cameraView: CameraView! + + override func setUp() { + cameraView = CameraView() + cameraView.syncLayer(to: cameraOptions) + } + + func testSyncLayer() { + XCTAssertEqual(cameraView.layer.opacity, Float(cameraOptions.zoom!)) + XCTAssertEqual(cameraView.layer.cornerRadius, CGFloat(cameraOptions.bearing!)) + let padding = cameraOptions.padding! + XCTAssertEqual(cameraView.layer.bounds, CGRect(x: padding.left, + y: padding.right, + width: padding.bottom, + height: padding.top)) + let center = cameraOptions.center! + XCTAssertEqual(cameraView.layer.position, CGPoint(x: center.longitude, + y: center.latitude)) + XCTAssertEqual(cameraView.layer.transform.m11, cameraOptions.pitch!) + XCTAssertEqual(cameraView.layer.anchorPoint, cameraOptions.anchor!) + } + + func testLocalCamera() { + XCTAssertEqual(cameraView.localCamera, cameraOptions) + } + +} From 85c15e3af8b6e1bf663f961348c13ed1b412efc2 Mon Sep 17 00:00:00 2001 From: Nishant Karajgikar Date: Thu, 22 Apr 2021 11:16:05 -0400 Subject: [PATCH 13/40] Add FlyToAnimatorTests [run device tests] --- .../Camera/FlyToAnimatorTests.swift | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 Tests/MapboxMapsTests/Foundation/Camera/FlyToAnimatorTests.swift diff --git a/Tests/MapboxMapsTests/Foundation/Camera/FlyToAnimatorTests.swift b/Tests/MapboxMapsTests/Foundation/Camera/FlyToAnimatorTests.swift new file mode 100644 index 00000000000..19eb21ee3b7 --- /dev/null +++ b/Tests/MapboxMapsTests/Foundation/Camera/FlyToAnimatorTests.swift @@ -0,0 +1,77 @@ +import XCTest +@testable import MapboxMaps + +final class FlyToAnimatorTests: XCTestCase { + + let initalCameraOptions = CameraOptions(center: CLLocationCoordinate2D(latitude: 42.3601, + longitude: -71.0589), + padding: .zero, + zoom: 10, + bearing: 10, + pitch: 10) + + let finalCameraOptions = CameraOptions(center: CLLocationCoordinate2D(latitude: 37.7749, + longitude: -122.4194), + padding: .zero, + zoom: 10, + bearing: 10, + pitch: 10) + + var flyToAnimator: FlyToAnimator! + var cameraAnimatorDelegateMock: CameraAnimatorDelegateMock! + + override func setUp() { + cameraAnimatorDelegateMock = CameraAnimatorDelegateMock() + flyToAnimator = FlyToAnimator(delegate: cameraAnimatorDelegateMock) + flyToAnimator.makeFlyToInterpolator(from: initalCameraOptions, + to: finalCameraOptions, + duration: 10, + screenFullSize: .init(width: 500, height: 500)) + } + + func testMakeFlyToInterpolator() { + XCTAssertNotNil(flyToAnimator.flyToInterpolator) + XCTAssertNotNil(flyToAnimator.delegate) + XCTAssertEqual(flyToAnimator.finalCameraOptions, finalCameraOptions) + XCTAssertEqual(flyToAnimator.animationDuration, 10) + + } + + func testStartAnimation() { + flyToAnimator.startAnimation() + XCTAssertEqual(flyToAnimator.state, .active) + XCTAssertNotNil(flyToAnimator.startTime) + XCTAssertNotNil(flyToAnimator.endTime) + } + + func testAddCompletion() { + flyToAnimator.addCompletion { (position) in + print(position) + } + + XCTAssertNotNil(flyToAnimator.animationCompletion) + } + + func testStopAnimation() { + + flyToAnimator.addCompletion { (position) in + print(position) + } + + flyToAnimator.startAnimation() + + flyToAnimator.stopAnimation() + + XCTAssertEqual(flyToAnimator.state, .stopped) + XCTAssertNil(flyToAnimator.flyToInterpolator) + XCTAssertEqual(cameraAnimatorDelegateMock.schedulePendingCompletionStub.invocations.count, 1) + XCTAssertEqual(cameraAnimatorDelegateMock.schedulePendingCompletionStub.invocations.first!.parameters.animatingPosition, .current) + } + + func testUpdate() { + flyToAnimator.startAnimation() + flyToAnimator.update() + XCTAssertEqual(cameraAnimatorDelegateMock.jumpToStub.invocations.count, 1) + } + +} From b818b133dcc6c8b96a7e57a98a140c59c6c9b0dd Mon Sep 17 00:00:00 2001 From: Nishant Karajgikar Date: Thu, 22 Apr 2021 12:18:35 -0400 Subject: [PATCH 14/40] Remove unnecessary deinit --- Sources/MapboxMaps/Foundation/Camera/FlyToAnimator.swift | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Sources/MapboxMaps/Foundation/Camera/FlyToAnimator.swift b/Sources/MapboxMaps/Foundation/Camera/FlyToAnimator.swift index baf6ccc3bbc..a0a7e6cc3e2 100644 --- a/Sources/MapboxMaps/Foundation/Camera/FlyToAnimator.swift +++ b/Sources/MapboxMaps/Foundation/Camera/FlyToAnimator.swift @@ -27,11 +27,6 @@ internal class FlyToAnimator: NSObject, CameraAnimatorProtocol { self.owner = owner } - deinit { - flyToInterpolator = nil - stopAnimation() - } - internal func makeFlyToInterpolator(from initalCamera: CameraOptions, to finalCamera: CameraOptions, duration: TimeInterval? = nil, screenFullSize: CGSize) { guard let flyTo = FlyToInterpolator(from: initalCamera, From 2b06df59f18566f3aee96a113534564ab7623528 Mon Sep 17 00:00:00 2001 From: Nishant Karajgikar Date: Thu, 22 Apr 2021 13:45:38 -0400 Subject: [PATCH 15/40] Add files to MapboxMaps.xcworkspace [run device tests] --- Mapbox/MapboxMaps.xcodeproj/project.pbxproj | 28 +++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/Mapbox/MapboxMaps.xcodeproj/project.pbxproj b/Mapbox/MapboxMaps.xcodeproj/project.pbxproj index 2d33aad4334..5154a802882 100644 --- a/Mapbox/MapboxMaps.xcodeproj/project.pbxproj +++ b/Mapbox/MapboxMaps.xcodeproj/project.pbxproj @@ -149,6 +149,13 @@ 0CDFEB0D25A7745A008BC505 /* UtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CDFEAD725A77430008BC505 /* UtilsTests.swift */; }; 0CE3D1B425816BD6000585A2 /* MapView+Supportable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE3D1B325816BD6000585A2 /* MapView+Supportable.swift */; }; 0CE8ED6125890F870066E56C /* ModelSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE8ED6025890F870066E56C /* ModelSource.swift */; }; + 0CE8F1222631ECD0004F94E0 /* CameraTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE8F1202631ECD0004F94E0 /* CameraTransition.swift */; }; + 0CE8F1232631ECD0004F94E0 /* FlyToAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE8F1212631ECD0004F94E0 /* FlyToAnimator.swift */; }; + 0CE8F12C2631ECFB004F94E0 /* CameraViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE8F1272631ECFB004F94E0 /* CameraViewTests.swift */; }; + 0CE8F12D2631ECFB004F94E0 /* UIViewPropertyAnimatorMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE8F1282631ECFB004F94E0 /* UIViewPropertyAnimatorMock.swift */; }; + 0CE8F12E2631ECFB004F94E0 /* FlyToAnimatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE8F1292631ECFB004F94E0 /* FlyToAnimatorTests.swift */; }; + 0CE8F12F2631ECFB004F94E0 /* CameraViewMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE8F12A2631ECFB004F94E0 /* CameraViewMock.swift */; }; + 0CE8F1302631ECFB004F94E0 /* CameraTransitionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE8F12B2631ECFB004F94E0 /* CameraTransitionTests.swift */; }; 0CECCCCD253491A80000FC64 /* LocationSupportableMapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CECCCCC253491A80000FC64 /* LocationSupportableMapView.swift */; }; 0CF0C42924EB1779000DC118 /* Enums.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CF0C42824EB1779000DC118 /* Enums.swift */; }; 1FECC9E02474519D00B63910 /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FECC9DF2474519D00B63910 /* Expression.swift */; }; @@ -529,6 +536,13 @@ 0CDFEAD725A77430008BC505 /* UtilsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UtilsTests.swift; sourceTree = ""; }; 0CE3D1B325816BD6000585A2 /* MapView+Supportable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MapView+Supportable.swift"; sourceTree = ""; }; 0CE8ED6025890F870066E56C /* ModelSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelSource.swift; sourceTree = ""; }; + 0CE8F1202631ECD0004F94E0 /* CameraTransition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraTransition.swift; sourceTree = ""; }; + 0CE8F1212631ECD0004F94E0 /* FlyToAnimator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlyToAnimator.swift; sourceTree = ""; }; + 0CE8F1272631ECFB004F94E0 /* CameraViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraViewTests.swift; sourceTree = ""; }; + 0CE8F1282631ECFB004F94E0 /* UIViewPropertyAnimatorMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewPropertyAnimatorMock.swift; sourceTree = ""; }; + 0CE8F1292631ECFB004F94E0 /* FlyToAnimatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlyToAnimatorTests.swift; sourceTree = ""; }; + 0CE8F12A2631ECFB004F94E0 /* CameraViewMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraViewMock.swift; sourceTree = ""; }; + 0CE8F12B2631ECFB004F94E0 /* CameraTransitionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraTransitionTests.swift; sourceTree = ""; }; 0CECCCCC253491A80000FC64 /* LocationSupportableMapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationSupportableMapView.swift; sourceTree = ""; }; 0CF0C42824EB1779000DC118 /* Enums.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Enums.swift; sourceTree = ""; }; 0CF118A2243D1C630097779A /* PinchGestureHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinchGestureHandler.swift; sourceTree = ""; }; @@ -1222,6 +1236,8 @@ 1F4B7D7A2464C36E00745F05 /* Camera */ = { isa = PBXGroup; children = ( + 0CE8F1202631ECD0004F94E0 /* CameraTransition.swift */, + 0CE8F1212631ECD0004F94E0 /* FlyToAnimator.swift */, B5B55D41260E4D1500EBB589 /* AnimationOwner.swift */, B5B55D42260E4D1500EBB589 /* CameraAnimationDelegate.swift */, B5B55D40260E4D1500EBB589 /* CameraAnimator.swift */, @@ -1393,6 +1409,11 @@ A4FE887B255F364600FBF117 /* Camera */ = { isa = PBXGroup; children = ( + 0CE8F12B2631ECFB004F94E0 /* CameraTransitionTests.swift */, + 0CE8F12A2631ECFB004F94E0 /* CameraViewMock.swift */, + 0CE8F1272631ECFB004F94E0 /* CameraViewTests.swift */, + 0CE8F1292631ECFB004F94E0 /* FlyToAnimatorTests.swift */, + 0CE8F1282631ECFB004F94E0 /* UIViewPropertyAnimatorMock.swift */, B5B8B74E26308AC300D936E5 /* CameraOptionsTests.swift */, B5B55D56260E4D7500EBB589 /* CameraAnimatorDelegateMock.swift */, B5B55D55260E4D7500EBB589 /* CameraAnimatorTests.swift */, @@ -1894,6 +1915,7 @@ 07A8A3D1251C4CA6004281CF /* Annotation.swift in Sources */, 0782B04C258C14FC00D5FCE5 /* Int.swift in Sources */, CA0492D6261382E900CF458A /* StyleURI.swift in Sources */, + 0CE8F1232631ECD0004F94E0 /* FlyToAnimator.swift in Sources */, 0C708F5924EB2927003CE791 /* Sources.swift in Sources */, 0C708F2524EB1EE2003CE791 /* HillshadeLayer.swift in Sources */, CABCDF512620E0FF00D61635 /* MapOptions.swift in Sources */, @@ -1975,6 +1997,7 @@ CABCDF372620E03A00D61635 /* CredentialsManager.swift in Sources */, 0782B11B258C1B9C00D5FCE5 /* Feature.swift in Sources */, C64B6FEA25E0479000C8E07E /* Bundle+MapboxMaps.swift in Sources */, + 0CE8F1222631ECD0004F94E0 /* CameraTransition.swift in Sources */, 0782AFE6258BFD2300D5FCE5 /* CoreLocation.swift in Sources */, 2B8637D52461601100698135 /* MapboxScaleBarOrnamentView.swift in Sources */, 0CD62F0824588505006421D1 /* QuickZoomGestureHandler.swift in Sources */, @@ -2032,8 +2055,10 @@ files = ( CA03F08F2624FDCB00673961 /* MapInitOptionsIntegrationTests.swift in Sources */, 0C5CFCDC25BB951B0001E753 /* ModelLayerTests.swift in Sources */, + 0CE8F12F2631ECFB004F94E0 /* CameraViewMock.swift in Sources */, B5B55D5F260E4D7B00EBB589 /* CameraAnimatorDelegateMock.swift in Sources */, CA548FD3251C404B00F829A3 /* PolygonTests.swift in Sources */, + 0CE8F1302631ECFB004F94E0 /* CameraTransitionTests.swift in Sources */, CAC1965525AEAFD900F69FEA /* GlyphsRasterizationOptionsTests.swift in Sources */, 0C32C9B625F979680057ED31 /* VectorSourceIntegrationTests.swift in Sources */, CA548FD4251C404B00F829A3 /* MapboxMapsCameraTests.swift in Sources */, @@ -2071,6 +2096,7 @@ 0CC6EF0B25C3263400BFB153 /* ModelLayerIntegrationTests.swift in Sources */, 0CC6EF1A25C3263400BFB153 /* FillExtrusionLayerIntegrationTests.swift in Sources */, 0CC6EF1D25C3263400BFB153 /* HeatmapLayerIntegrationTests.swift in Sources */, + 0CE8F12D2631ECFB004F94E0 /* UIViewPropertyAnimatorMock.swift in Sources */, CA548FE0251C404B00F829A3 /* GeoJSONManagerTests.swift in Sources */, CA548FE1251C404B00F829A3 /* PinchGestureHandlerTests.swift in Sources */, CA050571259104E200FF7D02 /* FeatureQueryingTest.swift in Sources */, @@ -2133,6 +2159,7 @@ B5B8B74F26308AC300D936E5 /* CameraOptionsTests.swift in Sources */, CAD7BA0625A368DE00E64C78 /* ExampleIntegrationTest.swift in Sources */, C6334944262E28C300D17701 /* StyleTransitionTests.swift in Sources */, + 0CE8F12C2631ECFB004F94E0 /* CameraViewTests.swift in Sources */, CA99A8702540CD1900D16C78 /* StyleLoadIntegrationTests.swift in Sources */, CA548FF5251C404B00F829A3 /* Fixture.swift in Sources */, 0C5CFCD325BB951B0001E753 /* RasterLayerTests.swift in Sources */, @@ -2149,6 +2176,7 @@ 0CDFEB0D25A7745A008BC505 /* UtilsTests.swift in Sources */, CA548FFB251C404B00F829A3 /* StyleURITests.swift in Sources */, CA2E4A1B2538D3530096DEDE /* MapViewIntegrationTestCase.swift in Sources */, + 0CE8F12E2631ECFB004F94E0 /* FlyToAnimatorTests.swift in Sources */, 0C32CA2A25F982300057ED31 /* ImageSourceTests.swift in Sources */, B521FC9D262F665C00B9A446 /* SizeTests.swift in Sources */, 0C5CFCD625BB951B0001E753 /* FillExtrusionLayerTests.swift in Sources */, From c72c8eaf015782f257d4ed5d6df31fe5cc633fa0 Mon Sep 17 00:00:00 2001 From: Nishant Karajgikar Date: Thu, 22 Apr 2021 14:02:26 -0400 Subject: [PATCH 16/40] review comments --- Sources/MapboxMaps/Foundation/BaseMapView.swift | 10 +--------- .../MapboxMaps/Foundation/Camera/CameraAnimator.swift | 6 ++---- .../Camera/CameraManager+CameraAnimatorDelegate.swift | 8 ++++---- .../MapboxMaps/Foundation/Camera/CameraManager.swift | 2 +- 4 files changed, 8 insertions(+), 18 deletions(-) diff --git a/Sources/MapboxMaps/Foundation/BaseMapView.swift b/Sources/MapboxMaps/Foundation/BaseMapView.swift index 311a0fa1321..bb7cbd2c2d3 100644 --- a/Sources/MapboxMaps/Foundation/BaseMapView.swift +++ b/Sources/MapboxMaps/Foundation/BaseMapView.swift @@ -35,15 +35,7 @@ open class BaseMapView: UIView { /// List of animators currently alive public var cameraAnimators: [CameraAnimator] { - - var animators: [CameraAnimator] = [] - cameraAnimatorsHashTable.allObjects.forEach { (animator) in - if let animator = animator as? CameraAnimator { - animators.append(animator) - } - } - - return animators + return cameraAnimatorsHashTable.allObjects.compactMap { $0 as? CameraAnimator } } /// Map of event types to subscribed event handlers diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift b/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift index 381d511419b..500e64e5c80 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift @@ -13,8 +13,6 @@ import CoreLocation func update() } -public typealias CameraAnimation = (inout CameraTransition) -> Void - // MARK: CameraAnimator Class public class CameraAnimator: NSObject, CameraAnimatorProtocol { @@ -31,7 +29,7 @@ public class CameraAnimator: NSObject, CameraAnimatorProtocol { internal var cameraView: CameraView /// Represents the animation that this animator is attempting to execute - internal var animation: CameraAnimation? + internal var animation: ((inout CameraTransition) -> Void)? /// Defines the transition that will occur to the `CameraOptions` of the renderer due to this animator public internal(set) var transition: CameraTransition? @@ -126,7 +124,7 @@ public class CameraAnimator: NSObject, CameraAnimatorProtocol { } /// Add animations block to the animator. - internal func addAnimations(_ animations: @escaping CameraAnimation) { + internal func addAnimations(_ animations: @escaping (inout CameraTransition) -> Void) { animation = animations } diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraManager+CameraAnimatorDelegate.swift b/Sources/MapboxMaps/Foundation/Camera/CameraManager+CameraAnimatorDelegate.swift index a3a39b6971d..fb6e964cbe9 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraManager+CameraAnimatorDelegate.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraManager+CameraAnimatorDelegate.swift @@ -18,7 +18,7 @@ extension CameraManager: CameraAnimatorDelegate { public func makeAnimator(duration: TimeInterval, timingParameters parameters: UITimingCurveProvider, animationOwner: AnimationOwner = .unspecified, - animations: @escaping CameraAnimation) -> CameraAnimator { + animations: @escaping (inout CameraTransition) -> Void) -> CameraAnimator { let propertyAnimator = UIViewPropertyAnimator(duration: duration, timingParameters: parameters) let cameraAnimator = CameraAnimator(delegate: self, propertyAnimator: propertyAnimator, owner: animationOwner) cameraAnimator.addAnimations(animations) @@ -42,7 +42,7 @@ extension CameraManager: CameraAnimatorDelegate { public func makeAnimator(duration: TimeInterval, curve: UIView.AnimationCurve, animationOwner: AnimationOwner = .unspecified, - animations: @escaping CameraAnimation) -> CameraAnimator { + animations: @escaping (inout CameraTransition) -> Void) -> CameraAnimator { let propertyAnimator = UIViewPropertyAnimator(duration: duration, curve: curve) let cameraAnimator = CameraAnimator(delegate: self, propertyAnimator: propertyAnimator, owner: animationOwner) cameraAnimator.addAnimations(animations) @@ -68,7 +68,7 @@ extension CameraManager: CameraAnimatorDelegate { controlPoint1 point1: CGPoint, controlPoint2 point2: CGPoint, animationOwner: AnimationOwner = .unspecified, - animations: @escaping CameraAnimation) -> CameraAnimator { + animations: @escaping (inout CameraTransition) -> Void) -> CameraAnimator { let propertyAnimator = UIViewPropertyAnimator(duration: duration, controlPoint1: point1, controlPoint2: point2) let cameraAnimator = CameraAnimator(delegate: self, propertyAnimator: propertyAnimator, owner: animationOwner) cameraAnimator.addAnimations(animations) @@ -93,7 +93,7 @@ extension CameraManager: CameraAnimatorDelegate { public func makeAnimator(duration: TimeInterval, dampingRatio ratio: CGFloat, animationOwner: AnimationOwner = .unspecified, - animations: @escaping CameraAnimation) -> CameraAnimator { + animations: @escaping (inout CameraTransition) -> Void) -> CameraAnimator { let propertyAnimator = UIViewPropertyAnimator(duration: duration, dampingRatio: ratio) let cameraAnimator = CameraAnimator(delegate: self, propertyAnimator: propertyAnimator, owner: animationOwner) cameraAnimator.addAnimations(animations) diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift b/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift index d86d8416a7e..1b10df6d28d 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift @@ -173,7 +173,7 @@ public class CameraManager { /// - duration: If animated, how long the animation takes /// - animation: closure to perform /// - completion: animation block called on completion - fileprivate func performCameraAnimation(duration: TimeInterval, animation: @escaping CameraAnimation, completion: ((UIViewAnimatingPosition) -> Void)? = nil) { + fileprivate func performCameraAnimation(duration: TimeInterval, animation: @escaping (inout CameraTransition) -> Void, completion: ((UIViewAnimatingPosition) -> Void)? = nil) { // Stop previously running animations internalAnimator?.stopAnimation() From 490e25b600fca89ee0912e66965c450543bd69c2 Mon Sep 17 00:00:00 2001 From: Nishant Karajgikar Date: Thu, 22 Apr 2021 14:17:14 -0400 Subject: [PATCH 17/40] Fix issue where flyTo completion would not get called if flyToAnimator was deinited --- .../MapboxMaps/Foundation/Camera/CameraManager.swift | 8 ++++---- .../MapboxMaps/Foundation/Camera/FlyToAnimator.swift | 12 ++++++++++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift b/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift index 1b10df6d28d..f81d89b7f42 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift @@ -229,12 +229,12 @@ public class CameraManager { screenFullSize: mapView.bounds.size) // Nil out the internalAnimator after `flyTo` finishes - flyToAnimator.addCompletion { [weak self](_) in + flyToAnimator.addCompletion { [weak self](position) in + // Call the developer-provided completion (if present) + completion?(position) self?.internalAnimator = nil } - - // Add the developer-provided completion (if present) - flyToAnimator.addCompletion(completion) + flyToAnimator.startAnimation() internalAnimator = flyToAnimator diff --git a/Sources/MapboxMaps/Foundation/Camera/FlyToAnimator.swift b/Sources/MapboxMaps/Foundation/Camera/FlyToAnimator.swift index a0a7e6cc3e2..41df8c76a7e 100644 --- a/Sources/MapboxMaps/Foundation/Camera/FlyToAnimator.swift +++ b/Sources/MapboxMaps/Foundation/Camera/FlyToAnimator.swift @@ -26,6 +26,10 @@ internal class FlyToAnimator: NSObject, CameraAnimatorProtocol { self.delegate = delegate self.owner = owner } + + deinit { + scheduleCompletionIfNecessary(position: .current) + } internal func makeFlyToInterpolator(from initalCamera: CameraOptions, to finalCamera: CameraOptions, duration: TimeInterval? = nil, screenFullSize: CGSize) { @@ -70,11 +74,15 @@ internal class FlyToAnimator: NSObject, CameraAnimatorProtocol { } func scheduleCompletionIfNecessary(position: UIViewAnimatingPosition) { - if let delegate = delegate, let animationCompletion = animationCompletion { + if let delegate = delegate, let validAnimationCompletion = animationCompletion { delegate.schedulePendingCompletion(forAnimator: self, - completion: animationCompletion, + completion: validAnimationCompletion, animatingPosition: position) + + // Once a completion has been scheduled, `nil` it out so it can't be executed again. + animationCompletion = nil } + } func update() { From ed68b5ac969c3f7cb881bb61790eef7ec11a0267 Mon Sep 17 00:00:00 2001 From: Nishant Karajgikar Date: Thu, 22 Apr 2021 14:54:34 -0400 Subject: [PATCH 18/40] Clean up access specifiers --- .../Foundation/Camera/CameraAnimator.swift | 24 +++++++++---------- .../Camera/CameraAnimatorTests.swift | 5 ++-- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift b/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift index 500e64e5c80..616c3250bb8 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift @@ -17,41 +17,41 @@ import CoreLocation public class CameraAnimator: NSObject, CameraAnimatorProtocol { /// Instance of the property animator that will run animations. - internal var propertyAnimator: UIViewPropertyAnimator + internal private(set) var propertyAnimator: UIViewPropertyAnimator /// Delegate that conforms to `CameraAnimatorDelegate`. - internal weak var delegate: CameraAnimatorDelegate? + internal private(set) weak var delegate: CameraAnimatorDelegate? /// The ID of the owner of this `CameraAnimator`. public internal(set) var owner: AnimationOwner /// The `CameraView` owned by this animator - internal var cameraView: CameraView + internal private(set) var cameraView: CameraView /// Represents the animation that this animator is attempting to execute - internal var animation: ((inout CameraTransition) -> Void)? + internal private(set) var animation: ((inout CameraTransition) -> Void)? /// Defines the transition that will occur to the `CameraOptions` of the renderer due to this animator public internal(set) var transition: CameraTransition? /// The state from of the animator. - public var state: UIViewAnimatingState { return propertyAnimator.state } + public var state: UIViewAnimatingState { propertyAnimator.state } /// Boolean that represents if the animation is running or not. - public var isRunning: Bool { return propertyAnimator.isRunning } + public var isRunning: Bool { propertyAnimator.isRunning } /// Boolean that represents if the animation is running normally or in reverse. - public var isReversed: Bool { return propertyAnimator.isReversed } + public var isReversed: Bool { propertyAnimator.isReversed } /// A Boolean value that indicates whether a completed animation remains in the active state. public var pausesOnCompletion: Bool { - get { return propertyAnimator.pausesOnCompletion} + get { propertyAnimator.pausesOnCompletion} set { propertyAnimator.pausesOnCompletion = newValue } } /// Value that represents what percentage of the animation has been completed. public var fractionComplete: Double { - get { return Double(propertyAnimator.fractionComplete) } + get { Double(propertyAnimator.fractionComplete) } set { propertyAnimator.fractionComplete = CGFloat(newValue) } } @@ -63,10 +63,7 @@ public class CameraAnimator: NSObject, CameraAnimatorProtocol { self.delegate = delegate self.propertyAnimator = propertyAnimator self.owner = owner - - // Set up the short lived camera view self.cameraView = cameraView - delegate.addViewToViewHeirarchy(cameraView) } deinit { @@ -87,6 +84,9 @@ public class CameraAnimator: NSObject, CameraAnimatorProtocol { guard let animation = animation else { fatalError("Animation cannot be nil when starting an animation") } + + // Set up the short lived camera view + delegate.addViewToViewHeirarchy(cameraView) var cameraTransition = CameraTransition(with: delegate.camera, initialAnchor: delegate.anchorAfterPadding()) animation(&cameraTransition) diff --git a/Tests/MapboxMapsTests/Foundation/Camera/CameraAnimatorTests.swift b/Tests/MapboxMapsTests/Foundation/Camera/CameraAnimatorTests.swift index 6cecd9d94a2..e3fa8a46df4 100644 --- a/Tests/MapboxMapsTests/Foundation/Camera/CameraAnimatorTests.swift +++ b/Tests/MapboxMapsTests/Foundation/Camera/CameraAnimatorTests.swift @@ -26,9 +26,7 @@ internal class CameraAnimatorTests: XCTestCase { cameraView: cameraView) } - func testInitializationAndDeinit() { - XCTAssertEqual(delegate.addViewToViewHeirarchyStub.invocations.count, 1) - + func testDeinit() { animator = nil XCTAssertEqual(propertyAnimator.stopAnimationStub.invocations.count, 1) XCTAssertEqual(propertyAnimator.finishAnimationStub.invocations.count, 1) @@ -42,6 +40,7 @@ internal class CameraAnimatorTests: XCTestCase { animator?.startAnimation() + XCTAssertEqual(delegate.addViewToViewHeirarchyStub.invocations.count, 1) XCTAssertEqual(propertyAnimator.startAnimationStub.invocations.count, 1) XCTAssertEqual(propertyAnimator.addAnimationsStub.invocations.count, 1) XCTAssertNotNil(animator?.transition) From 60e2e7a574c644ce814f9f20ed6cfd5a0a1a6e8f Mon Sep 17 00:00:00 2001 From: Nishant Karajgikar Date: Thu, 22 Apr 2021 15:03:41 -0400 Subject: [PATCH 19/40] more review comments --- .../Foundation/Camera/CameraAnimator.swift | 15 ++++++++------- .../Foundation/Camera/CameraManager.swift | 2 +- .../Foundation/Camera/FlyToAnimator.swift | 6 +++--- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift b/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift index 616c3250bb8..94bc9f70cc7 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift @@ -23,7 +23,7 @@ public class CameraAnimator: NSObject, CameraAnimatorProtocol { internal private(set) weak var delegate: CameraAnimatorDelegate? /// The ID of the owner of this `CameraAnimator`. - public internal(set) var owner: AnimationOwner + public private(set) var owner: AnimationOwner /// The `CameraView` owned by this animator internal private(set) var cameraView: CameraView @@ -32,7 +32,7 @@ public class CameraAnimator: NSObject, CameraAnimatorProtocol { internal private(set) var animation: ((inout CameraTransition) -> Void)? /// Defines the transition that will occur to the `CameraOptions` of the renderer due to this animator - public internal(set) var transition: CameraTransition? + public private(set) var transition: CameraTransition? /// The state from of the animator. public var state: UIViewAnimatingState { propertyAnimator.state } @@ -84,16 +84,16 @@ public class CameraAnimator: NSObject, CameraAnimatorProtocol { guard let animation = animation else { fatalError("Animation cannot be nil when starting an animation") } - + // Set up the short lived camera view delegate.addViewToViewHeirarchy(cameraView) var cameraTransition = CameraTransition(with: delegate.camera, initialAnchor: delegate.anchorAfterPadding()) animation(&cameraTransition) - propertyAnimator.addAnimations { [weak self] in - guard let self = self else { return } - self.cameraView.syncLayer(to: cameraTransition.toCameraOptions) // Set up the "to" values for the interpolation + propertyAnimator.addAnimations { [weak cameraView] in + guard let cameraView = cameraView else { return } + cameraView.syncLayer(to: cameraTransition.toCameraOptions) // Set up the "to" values for the interpolation } cameraView.syncLayer(to: cameraTransition.fromCameraOptions) // Set up the "from" values for the interpoloation @@ -137,7 +137,8 @@ public class CameraAnimator: NSObject, CameraAnimatorProtocol { internal func wrapCompletion(_ completion: @escaping AnimationCompletion) -> (UIViewAnimatingPosition) -> Void { return { [weak self] animationPosition in guard let self = self, let delegate = self.delegate else { return } - self.transition = nil // Clear out the set maintaining the properties being animated by this animator -- since the animation is complete if we are here. + self.transition = nil // Clear out the transition being animated by this animator, + // since the animation is complete if we are here. delegate.schedulePendingCompletion(forAnimator: self, completion: completion, animatingPosition: animationPosition) } } diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift b/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift index f81d89b7f42..f37b99ce5c9 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift @@ -234,7 +234,7 @@ public class CameraManager { completion?(position) self?.internalAnimator = nil } - + flyToAnimator.startAnimation() internalAnimator = flyToAnimator diff --git a/Sources/MapboxMaps/Foundation/Camera/FlyToAnimator.swift b/Sources/MapboxMaps/Foundation/Camera/FlyToAnimator.swift index 41df8c76a7e..327062aaa3e 100644 --- a/Sources/MapboxMaps/Foundation/Camera/FlyToAnimator.swift +++ b/Sources/MapboxMaps/Foundation/Camera/FlyToAnimator.swift @@ -26,7 +26,7 @@ internal class FlyToAnimator: NSObject, CameraAnimatorProtocol { self.delegate = delegate self.owner = owner } - + deinit { scheduleCompletionIfNecessary(position: .current) } @@ -78,11 +78,11 @@ internal class FlyToAnimator: NSObject, CameraAnimatorProtocol { delegate.schedulePendingCompletion(forAnimator: self, completion: validAnimationCompletion, animatingPosition: position) - + // Once a completion has been scheduled, `nil` it out so it can't be executed again. animationCompletion = nil } - + } func update() { From 6bdc1363612e32e103897f1fc1a30e0fd554db15 Mon Sep 17 00:00:00 2001 From: Nishant Karajgikar Date: Thu, 22 Apr 2021 18:04:01 -0400 Subject: [PATCH 20/40] Make properties in FlyToAnimator internal private(set) --- .../Foundation/Camera/FlyToAnimator.swift | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Sources/MapboxMaps/Foundation/Camera/FlyToAnimator.swift b/Sources/MapboxMaps/Foundation/Camera/FlyToAnimator.swift index 327062aaa3e..424482106ce 100644 --- a/Sources/MapboxMaps/Foundation/Camera/FlyToAnimator.swift +++ b/Sources/MapboxMaps/Foundation/Camera/FlyToAnimator.swift @@ -2,23 +2,23 @@ import UIKit internal class FlyToAnimator: NSObject, CameraAnimatorProtocol { - internal weak var delegate: CameraAnimatorDelegate? + internal private(set) weak var delegate: CameraAnimatorDelegate? - internal var owner: AnimationOwner + internal private(set) var owner: AnimationOwner - internal var flyToInterpolator: FlyToInterpolator? + internal private(set) var flyToInterpolator: FlyToInterpolator? - internal var animationDuration: TimeInterval? + internal private(set) var animationDuration: TimeInterval? internal private(set) var state: UIViewAnimatingState = .inactive - internal var startTime: Date? + internal private(set) var startTime: Date? - internal var endTime: Date? + internal private(set) var endTime: Date? - internal var finalCameraOptions: CameraOptions? + internal private(set) var finalCameraOptions: CameraOptions? - internal var animationCompletion: AnimationCompletion? + internal private(set) var animationCompletion: AnimationCompletion? internal init(delegate: CameraAnimatorDelegate, owner: AnimationOwner = .custom(id: "fly-to")) { From 875a53677d7dea288624b1b492508f10979c0c35 Mon Sep 17 00:00:00 2001 From: Nishant Karajgikar Date: Thu, 22 Apr 2021 20:57:57 -0400 Subject: [PATCH 21/40] Make optimizeBearing static function a computed var --- .../Foundation/Camera/CameraTransition.swift | 53 +++++++++---------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraTransition.swift b/Sources/MapboxMaps/Foundation/Camera/CameraTransition.swift index 5ad1964cc2a..bd6af6a6e84 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraTransition.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraTransition.swift @@ -24,7 +24,7 @@ public struct CameraTransition { public var constantAnchor: CGPoint? /// Represents a change to the bearing of the map. - public var bearing: Change + public var bearing: Change /// Ensures that bearing transitions are optimized to take the shortest path. public var shouldOptimizeBearingPath: Bool = true @@ -53,12 +53,12 @@ public struct CameraTransition { fatalError("Values in rendered CameraOptions cannot be nil") } - center = .init(fromValue: renderedCenter) - zoom = .init(fromValue: renderedZoom) - padding = .init(fromValue: renderedPadding) - pitch = .init(fromValue: renderedPitch) - bearing = .init(fromValue: renderedBearing) - anchor = .init(fromValue: initialAnchor) + center = Change(fromValue: renderedCenter) + zoom = Change(fromValue: renderedZoom) + padding = Change(fromValue: renderedPadding) + pitch = Change(fromValue: renderedPitch) + bearing = Change(fromValue: renderedBearing) + anchor = Change(fromValue: initialAnchor) } internal var toCameraOptions: CameraOptions { @@ -66,9 +66,7 @@ public struct CameraTransition { padding: padding.toValue, anchor: anchor.toValue, zoom: zoom.toValue, - bearing: shouldOptimizeBearingPath ? Self.optimizeBearing(startBearing: bearing.fromValue, - endBearing: bearing.toValue) - : bearing.toValue, + bearing: optimizedBearingToValue, pitch: pitch.toValue) } @@ -81,33 +79,34 @@ public struct CameraTransition { pitch: pitch.fromValue) } - - /// This function optimizes the bearing for set camera so that it is taking the shortest path. - /// - Parameters: - /// - startBearing: The current or start bearing of the map viewport. - /// - endBearing: The bearing of where the map viewport should end at. - /// - Returns: A `CLLocationDirection` that represents the correct final bearing accounting for positive and negatives. - internal static func optimizeBearing(startBearing: CLLocationDirection?, endBearing: CLLocationDirection?) -> CLLocationDirection? { - // This modulus is required to account for larger values - guard - let startBearing = startBearing?.truncatingRemainder(dividingBy: 360.0), - let endBearing = endBearing?.truncatingRemainder(dividingBy: 360.0) - else { + + internal var optimizedBearingToValue: CLLocationDirection? { + + // If we should not optimize bearing transition, then return the original `toValue` + guard shouldOptimizeBearingPath else { + return bearing.toValue + } + + // If `bearing.toValue` is nil, then return nil. + guard let toBearing = bearing.toValue?.truncatingRemainder(dividingBy: 360.0) else { return nil } + + let fromBearing = bearing.fromValue.truncatingRemainder(dividingBy: 360.0) // 180 degrees is the max the map should rotate, therefore if the difference between the end and start point is // more than 180 we need to go the opposite direction - if endBearing - startBearing >= 180 { - return endBearing - 360 + if toBearing - fromBearing >= 180 { + return toBearing - 360 } // This is the inverse of the above, accounting for negative bearings - if endBearing - startBearing <= -180 { - return endBearing + 360 + if toBearing - fromBearing <= -180 { + return toBearing + 360 } - return endBearing + return toBearing + } } From e740816b5792d5f188bb07fd3571c28a8d8145a9 Mon Sep 17 00:00:00 2001 From: Nishant Karajgikar Date: Thu, 22 Apr 2021 21:00:24 -0400 Subject: [PATCH 22/40] Convert constantAnchor to computed var --- .../MapboxMaps/Foundation/Camera/CameraAnimator.swift | 5 +---- .../Foundation/Camera/CameraTransition.swift | 10 +++++++++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift b/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift index 94bc9f70cc7..b580e48b5f5 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift @@ -168,10 +168,7 @@ public class CameraAnimator: NSObject, CameraAnimatorProtocol { cameraOptions.bearing = interpolatedCamera.bearing } - // Honor the constant anchor if provided as part of the transition - if transition.constantAnchor != nil { - cameraOptions.anchor = transition.constantAnchor - } else if transition.anchor.toValue != nil { + if transition.anchor.toValue != nil { cameraOptions.anchor = interpolatedCamera.anchor } diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraTransition.swift b/Sources/MapboxMaps/Foundation/Camera/CameraTransition.swift index bd6af6a6e84..28d6607a169 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraTransition.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraTransition.swift @@ -21,7 +21,15 @@ public struct CameraTransition { /// Set this value in order to make bearing/zoom animations /// honor a constant anchor throughout the transition /// NOTE: Incompatible with concurrent center animations - public var constantAnchor: CGPoint? + public var constantAnchor: CGPoint? { + set { + anchor.fromValue = newValue ?? anchor.fromValue + anchor.toValue = newValue + } + get { + return anchor.fromValue == anchor.toValue ? anchor.fromValue : nil + } + } /// Represents a change to the bearing of the map. public var bearing: Change From 38750e76130c927ce72677e4548659e77cdce2cf Mon Sep 17 00:00:00 2001 From: Nishant Karajgikar Date: Thu, 22 Apr 2021 21:37:59 -0400 Subject: [PATCH 23/40] Fix cameraTransitionTests --- .../Foundation/Camera/CameraTransition.swift | 10 ++-- .../Camera/CameraTransitionTests.swift | 48 ++++++++++++------- 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraTransition.swift b/Sources/MapboxMaps/Foundation/Camera/CameraTransition.swift index 28d6607a169..b7bd49157d5 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraTransition.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraTransition.swift @@ -87,19 +87,19 @@ public struct CameraTransition { pitch: pitch.fromValue) } - + internal var optimizedBearingToValue: CLLocationDirection? { - + // If we should not optimize bearing transition, then return the original `toValue` guard shouldOptimizeBearingPath else { return bearing.toValue } - + // If `bearing.toValue` is nil, then return nil. guard let toBearing = bearing.toValue?.truncatingRemainder(dividingBy: 360.0) else { return nil } - + let fromBearing = bearing.fromValue.truncatingRemainder(dividingBy: 360.0) // 180 degrees is the max the map should rotate, therefore if the difference between the end and start point is @@ -114,7 +114,7 @@ public struct CameraTransition { } return toBearing - + } } diff --git a/Tests/MapboxMapsTests/Foundation/Camera/CameraTransitionTests.swift b/Tests/MapboxMapsTests/Foundation/Camera/CameraTransitionTests.swift index bbe801995bb..1adcbb77009 100644 --- a/Tests/MapboxMapsTests/Foundation/Camera/CameraTransitionTests.swift +++ b/Tests/MapboxMapsTests/Foundation/Camera/CameraTransitionTests.swift @@ -3,10 +3,15 @@ import XCTest class CameraTransitionTests: XCTestCase { + var cameraTransition = CameraTransition(with: cameraOptionsTestValue, + initialAnchor: .zero) + func testOptimizeBearingClockwise() { let startBearing = 0.0 let endBearing = 90.0 - let optimizedBearing = CameraTransition.optimizeBearing(startBearing: startBearing, endBearing: endBearing) + cameraTransition.bearing.fromValue = startBearing + cameraTransition.bearing.toValue = endBearing + let optimizedBearing = cameraTransition.optimizedBearingToValue XCTAssertEqual(optimizedBearing, 90.0) } @@ -14,7 +19,9 @@ class CameraTransitionTests: XCTestCase { func testOptimizeBearingCounterClockwise() { let startBearing = 0.0 let endBearing = 270.0 - let optimizedBearing = CameraTransition.optimizeBearing(startBearing: startBearing, endBearing: endBearing) + cameraTransition.bearing.fromValue = startBearing + cameraTransition.bearing.toValue = endBearing + let optimizedBearing = cameraTransition.optimizedBearingToValue // We should rotate counter clockwise which is shown by a negative angle XCTAssertEqual(optimizedBearing, -90.0) @@ -23,7 +30,9 @@ class CameraTransitionTests: XCTestCase { func testOptimizeBearingWhenBearingsAreTheSame() { let startBearing = -90.0 let endBearing = 270.0 - let optimizedBearing = CameraTransition.optimizeBearing(startBearing: startBearing, endBearing: endBearing) + cameraTransition.bearing.fromValue = startBearing + cameraTransition.bearing.toValue = endBearing + let optimizedBearing = cameraTransition.optimizedBearingToValue // -90 and 270 degrees is the same bearing so should just return original XCTAssertEqual(optimizedBearing, -90) @@ -33,11 +42,17 @@ class CameraTransitionTests: XCTestCase { var optimizedBearing: CLLocationDirection? // Starting at -90 aka 270 should rotate clockwise to 20 - optimizedBearing = CameraTransition.optimizeBearing(startBearing: -90.0, endBearing: 20.0) + cameraTransition.bearing.fromValue = -90 + cameraTransition.bearing.toValue = 20 + + optimizedBearing = cameraTransition.optimizedBearingToValue XCTAssertEqual(optimizedBearing, 20) // Starting at -90 aka 270 should rotate clockwise to -270 aka 90 - optimizedBearing = CameraTransition.optimizeBearing(startBearing: -90.0, endBearing: -270) + cameraTransition.bearing.fromValue = -90 + cameraTransition.bearing.toValue = -270 + + optimizedBearing = cameraTransition.optimizedBearingToValue XCTAssertEqual(optimizedBearing, 90) } @@ -45,15 +60,10 @@ class CameraTransitionTests: XCTestCase { var optimizedBearing: CLLocationDirection? // Test when no end bearing is provided - optimizedBearing = CameraTransition.optimizeBearing(startBearing: 0.0, endBearing: nil) - XCTAssertNil(optimizedBearing) - - // Test when no start bearing is provided - optimizedBearing = CameraTransition.optimizeBearing(startBearing: nil, endBearing: 90) - XCTAssertNil(optimizedBearing) + cameraTransition.bearing.fromValue = 0.0 + cameraTransition.bearing.toValue = nil - // Test when no bearings are provided - optimizedBearing = CameraTransition.optimizeBearing(startBearing: nil, endBearing: nil) + optimizedBearing = cameraTransition.optimizedBearingToValue XCTAssertNil(optimizedBearing) } @@ -61,15 +71,21 @@ class CameraTransitionTests: XCTestCase { var optimizedBearing: CLLocationDirection? // 719 degrees is the same as 359 degrees. -1 should be returned because it is the shortest path from starting at 90 - optimizedBearing = CameraTransition.optimizeBearing(startBearing: 90.0, endBearing: 719) + cameraTransition.bearing.fromValue = 90 + cameraTransition.bearing.toValue = 719 + optimizedBearing = cameraTransition.optimizedBearingToValue XCTAssertEqual(optimizedBearing, -1.0) // -195 should be returned because it is the shortest path from starting at 180 - optimizedBearing = CameraTransition.optimizeBearing(startBearing: 180, endBearing: -555) + cameraTransition.bearing.fromValue = 180 + cameraTransition.bearing.toValue = -555 + optimizedBearing = cameraTransition.optimizedBearingToValue XCTAssertEqual(optimizedBearing, 165) // -160 should be returned because it is the shortest path from starting at 180 - optimizedBearing = CameraTransition.optimizeBearing(startBearing: 180, endBearing: -520) + cameraTransition.bearing.fromValue = 180 + cameraTransition.bearing.toValue = -520 + optimizedBearing = cameraTransition.optimizedBearingToValue XCTAssertEqual(optimizedBearing, 200) } } From 8a46d80f9b126205414f3596b60a2b20fbfc65cb Mon Sep 17 00:00:00 2001 From: Nishant Karajgikar Date: Thu, 22 Apr 2021 21:46:37 -0400 Subject: [PATCH 24/40] Use Timer to schedule a delayed start to an animation --- .../All Examples/CameraAnimatorsExample.swift | 2 +- .../Foundation/Camera/CameraAnimator.swift | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Apps/Examples/Examples/All Examples/CameraAnimatorsExample.swift b/Apps/Examples/Examples/All Examples/CameraAnimatorsExample.swift index 0652fc30314..dfd391bbb0b 100644 --- a/Apps/Examples/Examples/All Examples/CameraAnimatorsExample.swift +++ b/Apps/Examples/Examples/All Examples/CameraAnimatorsExample.swift @@ -67,7 +67,7 @@ public class CameraAnimatorsExample: UIViewController, ExampleProtocol { guard let self = self else { return } print("Animating zoom from zoom lvl 3 -> zoom lvl 14") - self.zoomAnimator.startAnimation(afterDelay: 1) + self.zoomAnimator.startAnimation(afterDelay: 10) self.finish() } } diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift b/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift index b580e48b5f5..873d44890cb 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift @@ -33,6 +33,9 @@ public class CameraAnimator: NSObject, CameraAnimatorProtocol { /// Defines the transition that will occur to the `CameraOptions` of the renderer due to this animator public private(set) var transition: CameraTransition? + + /// A timer used to delay the start of an animation + private var delayedAnimationTimer: Timer? /// The state from of the animator. public var state: UIViewAnimatingState { propertyAnimator.state } @@ -102,14 +105,15 @@ public class CameraAnimator: NSObject, CameraAnimatorProtocol { propertyAnimator.startAnimation() } - + /// Starts the animation after a delay /// - Parameter delay: Delay (in seconds) after which the animation should start public func startAnimation(afterDelay delay: TimeInterval) { - DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in + delayedAnimationTimer = Timer.scheduledTimer(withTimeInterval: delay, repeats: false, block: { [weak self] (timer) in guard let self = self else { return } self.startAnimation() - } + timer.invalidate() + }) } /// Pauses the animation. @@ -140,6 +144,10 @@ public class CameraAnimator: NSObject, CameraAnimatorProtocol { self.transition = nil // Clear out the transition being animated by this animator, // since the animation is complete if we are here. delegate.schedulePendingCompletion(forAnimator: self, completion: completion, animatingPosition: animationPosition) + + // Invalidate the delayed animation timer if it exists + self.delayedAnimationTimer?.invalidate() + self.delayedAnimationTimer = nil } } From 68356b872d4038e47a4576efd1862ba0300559d7 Mon Sep 17 00:00:00 2001 From: Nishant Karajgikar Date: Thu, 22 Apr 2021 22:50:19 -0400 Subject: [PATCH 25/40] Split CameraAnimatorProtocol into public facing and private facing protocols --- .../DebugApp/DebugViewController.swift | 2 +- .../All Examples/CameraAnimatorsExample.swift | 8 ++++---- .../StressTest/StressTest/ViewController.swift | 2 +- Mapbox/MapboxMaps.xcodeproj/project.pbxproj | 15 ++++++++------- .../MapboxMaps/Foundation/BaseMapView.swift | 2 +- ...nimator.swift => BasicCameraAnimator.swift} | 18 ++++++++++++------ .../Camera/CameraAnimationDelegate.swift | 2 +- .../CameraManager+CameraAnimatorDelegate.swift | 18 +++++++++--------- .../Foundation/Camera/CameraManager.swift | 8 ++++---- ...nimator.swift => FlyToCameraAnimator.swift} | 16 ++++++++-------- .../MapView/MapView+Supportable.swift | 2 +- .../Camera/CameraAnimatorDelegateMock.swift | 8 ++++---- .../Camera/CameraAnimatorTests.swift | 4 ++-- .../Foundation/Camera/FlyToAnimatorTests.swift | 4 ++-- 14 files changed, 58 insertions(+), 51 deletions(-) rename Sources/MapboxMaps/Foundation/Camera/{CameraAnimator.swift => BasicCameraAnimator.swift} (95%) rename Sources/MapboxMaps/Foundation/Camera/{FlyToAnimator.swift => FlyToCameraAnimator.swift} (88%) diff --git a/Apps/DebugApp/DebugApp/DebugViewController.swift b/Apps/DebugApp/DebugApp/DebugViewController.swift index 711cec5ea8c..8651986cb0e 100644 --- a/Apps/DebugApp/DebugApp/DebugViewController.swift +++ b/Apps/DebugApp/DebugApp/DebugViewController.swift @@ -11,7 +11,7 @@ import Turf public class DebugViewController: UIViewController { internal var mapView: MapView! - internal var runningAnimator: CameraAnimator? + internal var runningAnimator: BasicCameraAnimator? override public func viewDidLoad() { super.viewDidLoad() diff --git a/Apps/Examples/Examples/All Examples/CameraAnimatorsExample.swift b/Apps/Examples/Examples/All Examples/CameraAnimatorsExample.swift index dfd391bbb0b..2df3a67f4c3 100644 --- a/Apps/Examples/Examples/All Examples/CameraAnimatorsExample.swift +++ b/Apps/Examples/Examples/All Examples/CameraAnimatorsExample.swift @@ -11,7 +11,7 @@ public class CameraAnimatorsExample: UIViewController, ExampleProtocol { let newYork = CLLocationCoordinate2D(latitude: 40.7128, longitude: -74.0060) // Store the CameraAnimators so that the do not fall out of scope. - lazy var zoomAnimator: CameraAnimator = { + lazy var zoomAnimator: BasicCameraAnimator = { let animator = mapView.camera.makeAnimator(duration: 4, curve: .easeInOut) { (transition) in transition.zoom.toValue = 14 } @@ -24,7 +24,7 @@ public class CameraAnimatorsExample: UIViewController, ExampleProtocol { return animator }() - lazy var pitchAnimator: CameraAnimator = { + lazy var pitchAnimator: BasicCameraAnimator = { let animator = mapView.camera.makeAnimator(duration: 2, curve: .easeInOut) { (transition) in transition.pitch.toValue = 55 } @@ -37,7 +37,7 @@ public class CameraAnimatorsExample: UIViewController, ExampleProtocol { return animator }() - lazy var bearingAnimator: CameraAnimator = { + lazy var bearingAnimator: BasicCameraAnimator = { let animator = mapView.camera.makeAnimator(duration: 4, curve: .easeInOut) { (transition) in transition.bearing.toValue = -45 } @@ -67,7 +67,7 @@ public class CameraAnimatorsExample: UIViewController, ExampleProtocol { guard let self = self else { return } print("Animating zoom from zoom lvl 3 -> zoom lvl 14") - self.zoomAnimator.startAnimation(afterDelay: 10) + self.zoomAnimator.startAnimation(afterDelay: 1) self.finish() } } diff --git a/Apps/StressTest/StressTest/ViewController.swift b/Apps/StressTest/StressTest/ViewController.swift index 5f1c642da23..f095461121d 100644 --- a/Apps/StressTest/StressTest/ViewController.swift +++ b/Apps/StressTest/StressTest/ViewController.swift @@ -203,7 +203,7 @@ class ViewController: UIViewController { let endOptions = CameraOptions(center: end, zoom: 17) - var animator: CameraAnimatorProtocol? + var animator: CameraAnimator? animator = mapView.camera.fly(to: endOptions) { _ in print("Removing line annotation for animator \(String(describing: animator))") self.mapView.annotations.removeAnnotation(lineAnnotation) diff --git a/Mapbox/MapboxMaps.xcodeproj/project.pbxproj b/Mapbox/MapboxMaps.xcodeproj/project.pbxproj index 5154a802882..8b0ec927507 100644 --- a/Mapbox/MapboxMaps.xcodeproj/project.pbxproj +++ b/Mapbox/MapboxMaps.xcodeproj/project.pbxproj @@ -148,9 +148,10 @@ 0CD62F392458CDD6006421D1 /* GestureUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0765213A245740BA00FDD209 /* GestureUtilities.swift */; }; 0CDFEB0D25A7745A008BC505 /* UtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CDFEAD725A77430008BC505 /* UtilsTests.swift */; }; 0CE3D1B425816BD6000585A2 /* MapView+Supportable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE3D1B325816BD6000585A2 /* MapView+Supportable.swift */; }; + 0CE40D9826326C9A0067FCE5 /* BasicCameraAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE40D9626326C9A0067FCE5 /* BasicCameraAnimator.swift */; }; + 0CE40D9926326C9A0067FCE5 /* FlyToCameraAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE40D9726326C9A0067FCE5 /* FlyToCameraAnimator.swift */; }; 0CE8ED6125890F870066E56C /* ModelSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE8ED6025890F870066E56C /* ModelSource.swift */; }; 0CE8F1222631ECD0004F94E0 /* CameraTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE8F1202631ECD0004F94E0 /* CameraTransition.swift */; }; - 0CE8F1232631ECD0004F94E0 /* FlyToAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE8F1212631ECD0004F94E0 /* FlyToAnimator.swift */; }; 0CE8F12C2631ECFB004F94E0 /* CameraViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE8F1272631ECFB004F94E0 /* CameraViewTests.swift */; }; 0CE8F12D2631ECFB004F94E0 /* UIViewPropertyAnimatorMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE8F1282631ECFB004F94E0 /* UIViewPropertyAnimatorMock.swift */; }; 0CE8F12E2631ECFB004F94E0 /* FlyToAnimatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE8F1292631ECFB004F94E0 /* FlyToAnimatorTests.swift */; }; @@ -195,7 +196,6 @@ B5A6921E262754DF00A03412 /* DelegatingMapClientTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A69219262754DF00A03412 /* DelegatingMapClientTests.swift */; }; B5A6921F262754DF00A03412 /* CredentialsManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A6921A262754DF00A03412 /* CredentialsManagerTests.swift */; }; B5A6922B2627566A00A03412 /* MapInitOptionsTests.xib in Resources */ = {isa = PBXBuildFile; fileRef = B5A6922A2627566A00A03412 /* MapInitOptionsTests.xib */; }; - B5B55D44260E4D1500EBB589 /* CameraAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B55D40260E4D1500EBB589 /* CameraAnimator.swift */; }; B5B55D45260E4D1500EBB589 /* AnimationOwner.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B55D41260E4D1500EBB589 /* AnimationOwner.swift */; }; B5B55D46260E4D1500EBB589 /* CameraAnimationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B55D42260E4D1500EBB589 /* CameraAnimationDelegate.swift */; }; B5B55D47260E4D1500EBB589 /* CameraManager+CameraAnimatorDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B55D43260E4D1500EBB589 /* CameraManager+CameraAnimatorDelegate.swift */; }; @@ -535,9 +535,10 @@ 0CD34FE6242AAE0900943687 /* MapView+Managers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MapView+Managers.swift"; sourceTree = ""; }; 0CDFEAD725A77430008BC505 /* UtilsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UtilsTests.swift; sourceTree = ""; }; 0CE3D1B325816BD6000585A2 /* MapView+Supportable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MapView+Supportable.swift"; sourceTree = ""; }; + 0CE40D9626326C9A0067FCE5 /* BasicCameraAnimator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasicCameraAnimator.swift; sourceTree = ""; }; + 0CE40D9726326C9A0067FCE5 /* FlyToCameraAnimator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlyToCameraAnimator.swift; sourceTree = ""; }; 0CE8ED6025890F870066E56C /* ModelSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelSource.swift; sourceTree = ""; }; 0CE8F1202631ECD0004F94E0 /* CameraTransition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraTransition.swift; sourceTree = ""; }; - 0CE8F1212631ECD0004F94E0 /* FlyToAnimator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlyToAnimator.swift; sourceTree = ""; }; 0CE8F1272631ECFB004F94E0 /* CameraViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraViewTests.swift; sourceTree = ""; }; 0CE8F1282631ECFB004F94E0 /* UIViewPropertyAnimatorMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewPropertyAnimatorMock.swift; sourceTree = ""; }; 0CE8F1292631ECFB004F94E0 /* FlyToAnimatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlyToAnimatorTests.swift; sourceTree = ""; }; @@ -618,7 +619,6 @@ B5A69219262754DF00A03412 /* DelegatingMapClientTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DelegatingMapClientTests.swift; sourceTree = ""; }; B5A6921A262754DF00A03412 /* CredentialsManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CredentialsManagerTests.swift; sourceTree = ""; }; B5A6922A2627566A00A03412 /* MapInitOptionsTests.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MapInitOptionsTests.xib; sourceTree = ""; }; - B5B55D40260E4D1500EBB589 /* CameraAnimator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraAnimator.swift; sourceTree = ""; }; B5B55D41260E4D1500EBB589 /* AnimationOwner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimationOwner.swift; sourceTree = ""; }; B5B55D42260E4D1500EBB589 /* CameraAnimationDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraAnimationDelegate.swift; sourceTree = ""; }; B5B55D43260E4D1500EBB589 /* CameraManager+CameraAnimatorDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CameraManager+CameraAnimatorDelegate.swift"; sourceTree = ""; }; @@ -1236,11 +1236,11 @@ 1F4B7D7A2464C36E00745F05 /* Camera */ = { isa = PBXGroup; children = ( + 0CE40D9626326C9A0067FCE5 /* BasicCameraAnimator.swift */, + 0CE40D9726326C9A0067FCE5 /* FlyToCameraAnimator.swift */, 0CE8F1202631ECD0004F94E0 /* CameraTransition.swift */, - 0CE8F1212631ECD0004F94E0 /* FlyToAnimator.swift */, B5B55D41260E4D1500EBB589 /* AnimationOwner.swift */, B5B55D42260E4D1500EBB589 /* CameraAnimationDelegate.swift */, - B5B55D40260E4D1500EBB589 /* CameraAnimator.swift */, B5B55D43260E4D1500EBB589 /* CameraManager+CameraAnimatorDelegate.swift */, 1FC8DA55243BED0500A19318 /* CameraView.swift */, 07729A5724623E9B00440187 /* CameraOptions.swift */, @@ -1915,13 +1915,13 @@ 07A8A3D1251C4CA6004281CF /* Annotation.swift in Sources */, 0782B04C258C14FC00D5FCE5 /* Int.swift in Sources */, CA0492D6261382E900CF458A /* StyleURI.swift in Sources */, - 0CE8F1232631ECD0004F94E0 /* FlyToAnimator.swift in Sources */, 0C708F5924EB2927003CE791 /* Sources.swift in Sources */, 0C708F2524EB1EE2003CE791 /* HillshadeLayer.swift in Sources */, CABCDF512620E0FF00D61635 /* MapOptions.swift in Sources */, 0C708F2124EB1EE2003CE791 /* FillExtrusionLayer.swift in Sources */, B521FC98262F627300B9A446 /* Size.swift in Sources */, 0C26425524EECD14001FE2E3 /* AllExpressions.swift in Sources */, + 0CE40D9826326C9A0067FCE5 /* BasicCameraAnimator.swift in Sources */, 0782B1A3258C250400D5FCE5 /* Geometry.swift in Sources */, 0CD62F1024588530006421D1 /* GestureManager.swift in Sources */, 0742060324E4A5D0000A8932 /* PointAnnotation.swift in Sources */, @@ -1981,6 +1981,7 @@ CA0C426926028ED30054D9D0 /* AnnotationOptions.swift in Sources */, 0C708F2F24EB1EE2003CE791 /* RasterLayer.swift in Sources */, 0C35750225DD8D960085D775 /* StyleErrors.swift in Sources */, + 0CE40D9926326C9A0067FCE5 /* FlyToCameraAnimator.swift in Sources */, 0CE3D1B425816BD6000585A2 /* MapView+Supportable.swift in Sources */, 0C9DE369252C263600880CC8 /* GeoJSONSourceData.swift in Sources */, 0782B142258C1CAB00D5FCE5 /* NSValue.swift in Sources */, diff --git a/Sources/MapboxMaps/Foundation/BaseMapView.swift b/Sources/MapboxMaps/Foundation/BaseMapView.swift index bb7cbd2c2d3..d4a4418faf3 100644 --- a/Sources/MapboxMaps/Foundation/BaseMapView.swift +++ b/Sources/MapboxMaps/Foundation/BaseMapView.swift @@ -31,7 +31,7 @@ open class BaseMapView: UIView { internal var pendingAnimatorCompletionBlocks: [PendingAnimationCompletion] = [] /// Pointer HashTable for holding camera animators - internal var cameraAnimatorsHashTable = NSHashTable.weakObjects() + internal var cameraAnimatorsHashTable = NSHashTable.weakObjects() /// List of animators currently alive public var cameraAnimators: [CameraAnimator] { diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift b/Sources/MapboxMaps/Foundation/Camera/BasicCameraAnimator.swift similarity index 95% rename from Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift rename to Sources/MapboxMaps/Foundation/Camera/BasicCameraAnimator.swift index 873d44890cb..d541e756e56 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraAnimator.swift +++ b/Sources/MapboxMaps/Foundation/Camera/BasicCameraAnimator.swift @@ -1,20 +1,26 @@ import UIKit import CoreLocation -@objc public protocol CameraAnimatorProtocol: AnyObject { +@objc public protocol CameraAnimator: AnyObject { /// Stops the animation in its tracks and calls any provided completion func stopAnimation() /// The current state of the animation var state: UIViewAnimatingState { get } +} - /// TODO: Move this to a non-public interface +/// Internal-facing protocol to represent camera animators +@objc internal protocol CameraAnimatorInterface: AnyObject { func update() + + func stopAnimation() + + var state: UIViewAnimatingState { get } } // MARK: CameraAnimator Class -public class CameraAnimator: NSObject, CameraAnimatorProtocol { +public class BasicCameraAnimator: NSObject, CameraAnimator, CameraAnimatorInterface { /// Instance of the property animator that will run animations. internal private(set) var propertyAnimator: UIViewPropertyAnimator @@ -33,7 +39,7 @@ public class CameraAnimator: NSObject, CameraAnimatorProtocol { /// Defines the transition that will occur to the `CameraOptions` of the renderer due to this animator public private(set) var transition: CameraTransition? - + /// A timer used to delay the start of an animation private var delayedAnimationTimer: Timer? @@ -105,7 +111,7 @@ public class CameraAnimator: NSObject, CameraAnimatorProtocol { propertyAnimator.startAnimation() } - + /// Starts the animation after a delay /// - Parameter delay: Delay (in seconds) after which the animation should start public func startAnimation(afterDelay delay: TimeInterval) { @@ -144,7 +150,7 @@ public class CameraAnimator: NSObject, CameraAnimatorProtocol { self.transition = nil // Clear out the transition being animated by this animator, // since the animation is complete if we are here. delegate.schedulePendingCompletion(forAnimator: self, completion: completion, animatingPosition: animationPosition) - + // Invalidate the delayed animation timer if it exists self.delayedAnimationTimer?.invalidate() self.delayedAnimationTimer = nil diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraAnimationDelegate.swift b/Sources/MapboxMaps/Foundation/Camera/CameraAnimationDelegate.swift index 0140de9a32a..d6c27142e45 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraAnimationDelegate.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraAnimationDelegate.swift @@ -23,7 +23,7 @@ internal protocol CameraAnimatorDelegate: class { /// - animator: The current animator that this delegate function is being called from /// - completion: The completion block that needs to be scheduled /// - animatingPosition: The position of the animation needed for the closure - func schedulePendingCompletion(forAnimator animator: CameraAnimatorProtocol, + func schedulePendingCompletion(forAnimator animator: CameraAnimator, completion: @escaping AnimationCompletion, animatingPosition: UIViewAnimatingPosition) } diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraManager+CameraAnimatorDelegate.swift b/Sources/MapboxMaps/Foundation/Camera/CameraManager+CameraAnimatorDelegate.swift index fb6e964cbe9..d742d8349ec 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraManager+CameraAnimatorDelegate.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraManager+CameraAnimatorDelegate.swift @@ -18,9 +18,9 @@ extension CameraManager: CameraAnimatorDelegate { public func makeAnimator(duration: TimeInterval, timingParameters parameters: UITimingCurveProvider, animationOwner: AnimationOwner = .unspecified, - animations: @escaping (inout CameraTransition) -> Void) -> CameraAnimator { + animations: @escaping (inout CameraTransition) -> Void) -> BasicCameraAnimator { let propertyAnimator = UIViewPropertyAnimator(duration: duration, timingParameters: parameters) - let cameraAnimator = CameraAnimator(delegate: self, propertyAnimator: propertyAnimator, owner: animationOwner) + let cameraAnimator = BasicCameraAnimator(delegate: self, propertyAnimator: propertyAnimator, owner: animationOwner) cameraAnimator.addAnimations(animations) mapView?.cameraAnimatorsHashTable.add(cameraAnimator) return cameraAnimator @@ -42,9 +42,9 @@ extension CameraManager: CameraAnimatorDelegate { public func makeAnimator(duration: TimeInterval, curve: UIView.AnimationCurve, animationOwner: AnimationOwner = .unspecified, - animations: @escaping (inout CameraTransition) -> Void) -> CameraAnimator { + animations: @escaping (inout CameraTransition) -> Void) -> BasicCameraAnimator { let propertyAnimator = UIViewPropertyAnimator(duration: duration, curve: curve) - let cameraAnimator = CameraAnimator(delegate: self, propertyAnimator: propertyAnimator, owner: animationOwner) + let cameraAnimator = BasicCameraAnimator(delegate: self, propertyAnimator: propertyAnimator, owner: animationOwner) cameraAnimator.addAnimations(animations) mapView?.cameraAnimatorsHashTable.add(cameraAnimator) return cameraAnimator @@ -68,9 +68,9 @@ extension CameraManager: CameraAnimatorDelegate { controlPoint1 point1: CGPoint, controlPoint2 point2: CGPoint, animationOwner: AnimationOwner = .unspecified, - animations: @escaping (inout CameraTransition) -> Void) -> CameraAnimator { + animations: @escaping (inout CameraTransition) -> Void) -> BasicCameraAnimator { let propertyAnimator = UIViewPropertyAnimator(duration: duration, controlPoint1: point1, controlPoint2: point2) - let cameraAnimator = CameraAnimator(delegate: self, propertyAnimator: propertyAnimator, owner: animationOwner) + let cameraAnimator = BasicCameraAnimator(delegate: self, propertyAnimator: propertyAnimator, owner: animationOwner) cameraAnimator.addAnimations(animations) mapView?.cameraAnimatorsHashTable.add(cameraAnimator) return cameraAnimator @@ -93,16 +93,16 @@ extension CameraManager: CameraAnimatorDelegate { public func makeAnimator(duration: TimeInterval, dampingRatio ratio: CGFloat, animationOwner: AnimationOwner = .unspecified, - animations: @escaping (inout CameraTransition) -> Void) -> CameraAnimator { + animations: @escaping (inout CameraTransition) -> Void) -> BasicCameraAnimator { let propertyAnimator = UIViewPropertyAnimator(duration: duration, dampingRatio: ratio) - let cameraAnimator = CameraAnimator(delegate: self, propertyAnimator: propertyAnimator, owner: animationOwner) + let cameraAnimator = BasicCameraAnimator(delegate: self, propertyAnimator: propertyAnimator, owner: animationOwner) cameraAnimator.addAnimations(animations) mapView?.cameraAnimatorsHashTable.add(cameraAnimator) return cameraAnimator } // MARK: CameraAnimatorDelegate functions - func schedulePendingCompletion(forAnimator animator: CameraAnimatorProtocol, completion: @escaping AnimationCompletion, animatingPosition: UIViewAnimatingPosition) { + func schedulePendingCompletion(forAnimator animator: CameraAnimator, completion: @escaping AnimationCompletion, animatingPosition: UIViewAnimatingPosition) { guard let mapView = mapView else { return } mapView.pendingAnimatorCompletionBlocks.append((completion, animatingPosition)) } diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift b/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift index f37b99ce5c9..45299f30d7d 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift @@ -19,7 +19,7 @@ public class CameraManager { } /// Internal camera animator used for animated transition - internal var internalAnimator: CameraAnimatorProtocol? + internal var internalAnimator: CameraAnimator? /// May want to convert to an enum. fileprivate let northBearing: CGFloat = 0 @@ -211,7 +211,7 @@ public class CameraManager { @discardableResult public func fly(to camera: CameraOptions, duration: TimeInterval? = nil, - completion: AnimationCompletion? = nil) -> CameraAnimatorProtocol? { + completion: AnimationCompletion? = nil) -> CameraAnimator? { guard let mapView = mapView else { return nil @@ -220,7 +220,7 @@ public class CameraManager { // Stop the `internalAnimator` before beginning a `flyTo` internalAnimator?.stopAnimation() - let flyToAnimator = FlyToAnimator(delegate: self) + let flyToAnimator = FlyToCameraAnimator(delegate: self) mapView.cameraAnimatorsHashTable.add(flyToAnimator) flyToAnimator.makeFlyToInterpolator(from: mapView.cameraOptions, @@ -248,7 +248,7 @@ public class CameraManager { /// - completion: completion to be called after animation /// - Returns: An instance of `CameraAnimatorProtocol` which can be interrupted if necessary @discardableResult - public func ease(to camera: CameraOptions, duration: TimeInterval, completion: AnimationCompletion? = nil) -> CameraAnimatorProtocol? { + public func ease(to camera: CameraOptions, duration: TimeInterval, completion: AnimationCompletion? = nil) -> CameraAnimator? { internalAnimator?.stopAnimation() diff --git a/Sources/MapboxMaps/Foundation/Camera/FlyToAnimator.swift b/Sources/MapboxMaps/Foundation/Camera/FlyToCameraAnimator.swift similarity index 88% rename from Sources/MapboxMaps/Foundation/Camera/FlyToAnimator.swift rename to Sources/MapboxMaps/Foundation/Camera/FlyToCameraAnimator.swift index 424482106ce..b3585ffb6f7 100644 --- a/Sources/MapboxMaps/Foundation/Camera/FlyToAnimator.swift +++ b/Sources/MapboxMaps/Foundation/Camera/FlyToCameraAnimator.swift @@ -1,16 +1,16 @@ import UIKit -internal class FlyToAnimator: NSObject, CameraAnimatorProtocol { +public class FlyToCameraAnimator: NSObject, CameraAnimator, CameraAnimatorInterface { internal private(set) weak var delegate: CameraAnimatorDelegate? - internal private(set) var owner: AnimationOwner + public private(set) var owner: AnimationOwner internal private(set) var flyToInterpolator: FlyToInterpolator? internal private(set) var animationDuration: TimeInterval? - internal private(set) var state: UIViewAnimatingState = .inactive + public private(set) var state: UIViewAnimatingState = .inactive internal private(set) var startTime: Date? @@ -52,13 +52,13 @@ internal class FlyToAnimator: NSObject, CameraAnimatorProtocol { finalCameraOptions = finalCamera } - func stopAnimation() { + public func stopAnimation() { state = .stopped flyToInterpolator = nil scheduleCompletionIfNecessary(position: .current) // `current` represents an interrupted animation. } - func startAnimation() { + internal func startAnimation() { guard flyToInterpolator != nil, let animationDuration = animationDuration else { fatalError("FlyToInterpolator not created") @@ -69,11 +69,11 @@ internal class FlyToAnimator: NSObject, CameraAnimatorProtocol { endTime = startTime?.addingTimeInterval(animationDuration) } - func addCompletion(_ completion: AnimationCompletion?) { + internal func addCompletion(_ completion: AnimationCompletion?) { animationCompletion = completion } - func scheduleCompletionIfNecessary(position: UIViewAnimatingPosition) { + internal func scheduleCompletionIfNecessary(position: UIViewAnimatingPosition) { if let delegate = delegate, let validAnimationCompletion = animationCompletion { delegate.schedulePendingCompletion(forAnimator: self, completion: validAnimationCompletion, @@ -85,7 +85,7 @@ internal class FlyToAnimator: NSObject, CameraAnimatorProtocol { } - func update() { + internal func update() { guard state == .active, let startTime = startTime, diff --git a/Sources/MapboxMaps/MapView/MapView+Supportable.swift b/Sources/MapboxMaps/MapView/MapView+Supportable.swift index afd3ef7d0c3..bcc9b08a3a3 100644 --- a/Sources/MapboxMaps/MapView/MapView+Supportable.swift +++ b/Sources/MapboxMaps/MapView/MapView+Supportable.swift @@ -8,7 +8,7 @@ extension MapView: OrnamentSupportableView { } internal func compassTapped() { - var animator: CameraAnimator? + var animator: BasicCameraAnimator? animator = camera.makeAnimator(duration: 0.3, curve: .easeOut, animations: { (transition) in transition.bearing.toValue = 0 }) diff --git a/Tests/MapboxMapsTests/Foundation/Camera/CameraAnimatorDelegateMock.swift b/Tests/MapboxMapsTests/Foundation/Camera/CameraAnimatorDelegateMock.swift index 7e2f580cc55..5756effdf98 100644 --- a/Tests/MapboxMapsTests/Foundation/Camera/CameraAnimatorDelegateMock.swift +++ b/Tests/MapboxMapsTests/Foundation/Camera/CameraAnimatorDelegateMock.swift @@ -4,14 +4,14 @@ import XCTest final class CameraAnimatorDelegateMock: CameraAnimatorDelegate { struct SchedulePendingCompletionParameters { - var animator: CameraAnimatorProtocol + var animator: CameraAnimator var completion: AnimationCompletion var animatingPosition: UIViewAnimatingPosition } let schedulePendingCompletionStub = Stub() - public func schedulePendingCompletion(forAnimator animator: CameraAnimatorProtocol, + public func schedulePendingCompletion(forAnimator animator: CameraAnimator, completion: @escaping AnimationCompletion, animatingPosition: UIViewAnimatingPosition) { schedulePendingCompletionStub.call(with: SchedulePendingCompletionParameters(animator: animator, @@ -19,8 +19,8 @@ final class CameraAnimatorDelegateMock: CameraAnimatorDelegate { animatingPosition: animatingPosition)) } - let animatorFinishedStub = Stub() - public func animatorIsFinished(forAnimator animator: CameraAnimator) { + let animatorFinishedStub = Stub() + public func animatorIsFinished(forAnimator animator: BasicCameraAnimator) { animatorFinishedStub.call(with: animator) } diff --git a/Tests/MapboxMapsTests/Foundation/Camera/CameraAnimatorTests.swift b/Tests/MapboxMapsTests/Foundation/Camera/CameraAnimatorTests.swift index e3fa8a46df4..88a008d215a 100644 --- a/Tests/MapboxMapsTests/Foundation/Camera/CameraAnimatorTests.swift +++ b/Tests/MapboxMapsTests/Foundation/Camera/CameraAnimatorTests.swift @@ -14,13 +14,13 @@ internal class CameraAnimatorTests: XCTestCase { var delegate: CameraAnimatorDelegateMock! var propertyAnimator: UIViewPropertyAnimatorMock! var cameraView: CameraViewMock! - var animator: CameraAnimator? + var animator: BasicCameraAnimator? override func setUp() { delegate = CameraAnimatorDelegateMock() propertyAnimator = UIViewPropertyAnimatorMock() cameraView = CameraViewMock() - animator = CameraAnimator(delegate: delegate, + animator = BasicCameraAnimator(delegate: delegate, propertyAnimator: propertyAnimator , owner: .unspecified, cameraView: cameraView) diff --git a/Tests/MapboxMapsTests/Foundation/Camera/FlyToAnimatorTests.swift b/Tests/MapboxMapsTests/Foundation/Camera/FlyToAnimatorTests.swift index 19eb21ee3b7..9a4a06cc7c6 100644 --- a/Tests/MapboxMapsTests/Foundation/Camera/FlyToAnimatorTests.swift +++ b/Tests/MapboxMapsTests/Foundation/Camera/FlyToAnimatorTests.swift @@ -17,12 +17,12 @@ final class FlyToAnimatorTests: XCTestCase { bearing: 10, pitch: 10) - var flyToAnimator: FlyToAnimator! + var flyToAnimator: FlyToCameraAnimator! var cameraAnimatorDelegateMock: CameraAnimatorDelegateMock! override func setUp() { cameraAnimatorDelegateMock = CameraAnimatorDelegateMock() - flyToAnimator = FlyToAnimator(delegate: cameraAnimatorDelegateMock) + flyToAnimator = FlyToCameraAnimator(delegate: cameraAnimatorDelegateMock) flyToAnimator.makeFlyToInterpolator(from: initalCameraOptions, to: finalCameraOptions, duration: 10, From 11b7055f7974549c2105175b492b6ae8bf54818c Mon Sep 17 00:00:00 2001 From: Nishant Karajgikar Date: Thu, 22 Apr 2021 23:07:58 -0400 Subject: [PATCH 26/40] Add docs for cancelAnimations --- Sources/MapboxMaps/Foundation/Camera/CameraManager.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift b/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift index 45299f30d7d..e613384681e 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift @@ -161,6 +161,10 @@ public class CameraManager { } } + /// Interrupts all `active` animation. + /// The camera remains at the last point before the cancel request was invoked, i.e., + /// the camera is not reset or fast-forwarded to the end of the transition. + /// Canceled animations cannot be restarted / resumed. The animator must be recreated. public func cancelAnimations() { guard let validMapView = mapView else { return } for animator in validMapView.cameraAnimatorsHashTable.allObjects where animator.state == UIViewAnimatingState.active { From 2fb6a243182563ac1efb68c8641db1b6f9472f89 Mon Sep 17 00:00:00 2001 From: Nishant Karajgikar Date: Thu, 22 Apr 2021 23:24:41 -0400 Subject: [PATCH 27/40] Make cameraView private --- Sources/MapboxMaps/Foundation/Camera/BasicCameraAnimator.swift | 2 +- .../MapboxMapsTests/Foundation/Camera/CameraAnimatorTests.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/MapboxMaps/Foundation/Camera/BasicCameraAnimator.swift b/Sources/MapboxMaps/Foundation/Camera/BasicCameraAnimator.swift index d541e756e56..86d950bb678 100644 --- a/Sources/MapboxMaps/Foundation/Camera/BasicCameraAnimator.swift +++ b/Sources/MapboxMaps/Foundation/Camera/BasicCameraAnimator.swift @@ -32,7 +32,7 @@ public class BasicCameraAnimator: NSObject, CameraAnimator, CameraAnimatorInterf public private(set) var owner: AnimationOwner /// The `CameraView` owned by this animator - internal private(set) var cameraView: CameraView + private var cameraView: CameraView /// Represents the animation that this animator is attempting to execute internal private(set) var animation: ((inout CameraTransition) -> Void)? diff --git a/Tests/MapboxMapsTests/Foundation/Camera/CameraAnimatorTests.swift b/Tests/MapboxMapsTests/Foundation/Camera/CameraAnimatorTests.swift index 88a008d215a..da0721b1678 100644 --- a/Tests/MapboxMapsTests/Foundation/Camera/CameraAnimatorTests.swift +++ b/Tests/MapboxMapsTests/Foundation/Camera/CameraAnimatorTests.swift @@ -8,7 +8,7 @@ internal let cameraOptionsTestValue = CameraOptions(center: CLLocationCoordinate bearing: 10, pitch: 10) -internal class CameraAnimatorTests: XCTestCase { +internal class BasicCameraAnimatorTests: XCTestCase { // swiftlint:disable weak_delegate var delegate: CameraAnimatorDelegateMock! From a9105ad5205e31870d0ba3761c1d8cb2ca5bb413 Mon Sep 17 00:00:00 2001 From: Nishant Karajgikar Date: Thu, 22 Apr 2021 23:37:00 -0400 Subject: [PATCH 28/40] Fix xcodeproj [run device tests] --- Mapbox/MapboxMaps.xcodeproj/project.pbxproj | 65 ++++++++++----------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/Mapbox/MapboxMaps.xcodeproj/project.pbxproj b/Mapbox/MapboxMaps.xcodeproj/project.pbxproj index 8b0ec927507..3aac87ed886 100644 --- a/Mapbox/MapboxMaps.xcodeproj/project.pbxproj +++ b/Mapbox/MapboxMaps.xcodeproj/project.pbxproj @@ -48,6 +48,14 @@ 0C32CA2425F982300057ED31 /* GeoJsonSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C32CA1A25F982300057ED31 /* GeoJsonSourceTests.swift */; }; 0C32CA2725F982300057ED31 /* VectorSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C32CA1B25F982300057ED31 /* VectorSourceTests.swift */; }; 0C32CA2A25F982300057ED31 /* ImageSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C32CA1C25F982300057ED31 /* ImageSourceTests.swift */; }; + 0C350D83263278090090FA74 /* FlyToCameraAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C350D80263278090090FA74 /* FlyToCameraAnimator.swift */; }; + 0C350D84263278090090FA74 /* BasicCameraAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C350D81263278090090FA74 /* BasicCameraAnimator.swift */; }; + 0C350D85263278090090FA74 /* CameraTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C350D82263278090090FA74 /* CameraTransition.swift */; }; + 0C350D8E263278420090FA74 /* CameraTransitionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C350D89263278420090FA74 /* CameraTransitionTests.swift */; }; + 0C350D8F263278420090FA74 /* CameraViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C350D8A263278420090FA74 /* CameraViewTests.swift */; }; + 0C350D90263278420090FA74 /* FlyToAnimatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C350D8B263278420090FA74 /* FlyToAnimatorTests.swift */; }; + 0C350D91263278420090FA74 /* CameraViewMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C350D8C263278420090FA74 /* CameraViewMock.swift */; }; + 0C350D92263278420090FA74 /* UIViewPropertyAnimatorMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C350D8D263278420090FA74 /* UIViewPropertyAnimatorMock.swift */; }; 0C35750225DD8D960085D775 /* StyleErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C3574EE25DD8D5C0085D775 /* StyleErrors.swift */; }; 0C35751725DD8E010085D775 /* StyleIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C35751525DD8E010085D775 /* StyleIntegrationTests.swift */; }; 0C37B1E425CB05F000DCDD3D /* ColorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C37B1E225CB05F000DCDD3D /* ColorTests.swift */; }; @@ -148,15 +156,7 @@ 0CD62F392458CDD6006421D1 /* GestureUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0765213A245740BA00FDD209 /* GestureUtilities.swift */; }; 0CDFEB0D25A7745A008BC505 /* UtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CDFEAD725A77430008BC505 /* UtilsTests.swift */; }; 0CE3D1B425816BD6000585A2 /* MapView+Supportable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE3D1B325816BD6000585A2 /* MapView+Supportable.swift */; }; - 0CE40D9826326C9A0067FCE5 /* BasicCameraAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE40D9626326C9A0067FCE5 /* BasicCameraAnimator.swift */; }; - 0CE40D9926326C9A0067FCE5 /* FlyToCameraAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE40D9726326C9A0067FCE5 /* FlyToCameraAnimator.swift */; }; 0CE8ED6125890F870066E56C /* ModelSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE8ED6025890F870066E56C /* ModelSource.swift */; }; - 0CE8F1222631ECD0004F94E0 /* CameraTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE8F1202631ECD0004F94E0 /* CameraTransition.swift */; }; - 0CE8F12C2631ECFB004F94E0 /* CameraViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE8F1272631ECFB004F94E0 /* CameraViewTests.swift */; }; - 0CE8F12D2631ECFB004F94E0 /* UIViewPropertyAnimatorMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE8F1282631ECFB004F94E0 /* UIViewPropertyAnimatorMock.swift */; }; - 0CE8F12E2631ECFB004F94E0 /* FlyToAnimatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE8F1292631ECFB004F94E0 /* FlyToAnimatorTests.swift */; }; - 0CE8F12F2631ECFB004F94E0 /* CameraViewMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE8F12A2631ECFB004F94E0 /* CameraViewMock.swift */; }; - 0CE8F1302631ECFB004F94E0 /* CameraTransitionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CE8F12B2631ECFB004F94E0 /* CameraTransitionTests.swift */; }; 0CECCCCD253491A80000FC64 /* LocationSupportableMapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CECCCCC253491A80000FC64 /* LocationSupportableMapView.swift */; }; 0CF0C42924EB1779000DC118 /* Enums.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CF0C42824EB1779000DC118 /* Enums.swift */; }; 1FECC9E02474519D00B63910 /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FECC9DF2474519D00B63910 /* Expression.swift */; }; @@ -442,6 +442,14 @@ 0C32CA1A25F982300057ED31 /* GeoJsonSourceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeoJsonSourceTests.swift; sourceTree = ""; }; 0C32CA1B25F982300057ED31 /* VectorSourceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VectorSourceTests.swift; sourceTree = ""; }; 0C32CA1C25F982300057ED31 /* ImageSourceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageSourceTests.swift; sourceTree = ""; }; + 0C350D80263278090090FA74 /* FlyToCameraAnimator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlyToCameraAnimator.swift; sourceTree = ""; }; + 0C350D81263278090090FA74 /* BasicCameraAnimator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasicCameraAnimator.swift; sourceTree = ""; }; + 0C350D82263278090090FA74 /* CameraTransition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraTransition.swift; sourceTree = ""; }; + 0C350D89263278420090FA74 /* CameraTransitionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraTransitionTests.swift; sourceTree = ""; }; + 0C350D8A263278420090FA74 /* CameraViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraViewTests.swift; sourceTree = ""; }; + 0C350D8B263278420090FA74 /* FlyToAnimatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlyToAnimatorTests.swift; sourceTree = ""; }; + 0C350D8C263278420090FA74 /* CameraViewMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraViewMock.swift; sourceTree = ""; }; + 0C350D8D263278420090FA74 /* UIViewPropertyAnimatorMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewPropertyAnimatorMock.swift; sourceTree = ""; }; 0C3574EE25DD8D5C0085D775 /* StyleErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StyleErrors.swift; sourceTree = ""; }; 0C35751525DD8E010085D775 /* StyleIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StyleIntegrationTests.swift; sourceTree = ""; }; 0C37B1E225CB05F000DCDD3D /* ColorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorTests.swift; sourceTree = ""; }; @@ -535,15 +543,7 @@ 0CD34FE6242AAE0900943687 /* MapView+Managers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MapView+Managers.swift"; sourceTree = ""; }; 0CDFEAD725A77430008BC505 /* UtilsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UtilsTests.swift; sourceTree = ""; }; 0CE3D1B325816BD6000585A2 /* MapView+Supportable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MapView+Supportable.swift"; sourceTree = ""; }; - 0CE40D9626326C9A0067FCE5 /* BasicCameraAnimator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasicCameraAnimator.swift; sourceTree = ""; }; - 0CE40D9726326C9A0067FCE5 /* FlyToCameraAnimator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlyToCameraAnimator.swift; sourceTree = ""; }; 0CE8ED6025890F870066E56C /* ModelSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelSource.swift; sourceTree = ""; }; - 0CE8F1202631ECD0004F94E0 /* CameraTransition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraTransition.swift; sourceTree = ""; }; - 0CE8F1272631ECFB004F94E0 /* CameraViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraViewTests.swift; sourceTree = ""; }; - 0CE8F1282631ECFB004F94E0 /* UIViewPropertyAnimatorMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewPropertyAnimatorMock.swift; sourceTree = ""; }; - 0CE8F1292631ECFB004F94E0 /* FlyToAnimatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlyToAnimatorTests.swift; sourceTree = ""; }; - 0CE8F12A2631ECFB004F94E0 /* CameraViewMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraViewMock.swift; sourceTree = ""; }; - 0CE8F12B2631ECFB004F94E0 /* CameraTransitionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraTransitionTests.swift; sourceTree = ""; }; 0CECCCCC253491A80000FC64 /* LocationSupportableMapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationSupportableMapView.swift; sourceTree = ""; }; 0CF0C42824EB1779000DC118 /* Enums.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Enums.swift; sourceTree = ""; }; 0CF118A2243D1C630097779A /* PinchGestureHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinchGestureHandler.swift; sourceTree = ""; }; @@ -1236,9 +1236,9 @@ 1F4B7D7A2464C36E00745F05 /* Camera */ = { isa = PBXGroup; children = ( - 0CE40D9626326C9A0067FCE5 /* BasicCameraAnimator.swift */, - 0CE40D9726326C9A0067FCE5 /* FlyToCameraAnimator.swift */, - 0CE8F1202631ECD0004F94E0 /* CameraTransition.swift */, + 0C350D81263278090090FA74 /* BasicCameraAnimator.swift */, + 0C350D82263278090090FA74 /* CameraTransition.swift */, + 0C350D80263278090090FA74 /* FlyToCameraAnimator.swift */, B5B55D41260E4D1500EBB589 /* AnimationOwner.swift */, B5B55D42260E4D1500EBB589 /* CameraAnimationDelegate.swift */, B5B55D43260E4D1500EBB589 /* CameraManager+CameraAnimatorDelegate.swift */, @@ -1409,11 +1409,11 @@ A4FE887B255F364600FBF117 /* Camera */ = { isa = PBXGroup; children = ( - 0CE8F12B2631ECFB004F94E0 /* CameraTransitionTests.swift */, - 0CE8F12A2631ECFB004F94E0 /* CameraViewMock.swift */, - 0CE8F1272631ECFB004F94E0 /* CameraViewTests.swift */, - 0CE8F1292631ECFB004F94E0 /* FlyToAnimatorTests.swift */, - 0CE8F1282631ECFB004F94E0 /* UIViewPropertyAnimatorMock.swift */, + 0C350D89263278420090FA74 /* CameraTransitionTests.swift */, + 0C350D8C263278420090FA74 /* CameraViewMock.swift */, + 0C350D8A263278420090FA74 /* CameraViewTests.swift */, + 0C350D8B263278420090FA74 /* FlyToAnimatorTests.swift */, + 0C350D8D263278420090FA74 /* UIViewPropertyAnimatorMock.swift */, B5B8B74E26308AC300D936E5 /* CameraOptionsTests.swift */, B5B55D56260E4D7500EBB589 /* CameraAnimatorDelegateMock.swift */, B5B55D55260E4D7500EBB589 /* CameraAnimatorTests.swift */, @@ -1921,7 +1921,6 @@ 0C708F2124EB1EE2003CE791 /* FillExtrusionLayer.swift in Sources */, B521FC98262F627300B9A446 /* Size.swift in Sources */, 0C26425524EECD14001FE2E3 /* AllExpressions.swift in Sources */, - 0CE40D9826326C9A0067FCE5 /* BasicCameraAnimator.swift in Sources */, 0782B1A3258C250400D5FCE5 /* Geometry.swift in Sources */, 0CD62F1024588530006421D1 /* GestureManager.swift in Sources */, 0742060324E4A5D0000A8932 /* PointAnnotation.swift in Sources */, @@ -1929,6 +1928,7 @@ 0CD62F0724588501006421D1 /* PinchGestureHandler.swift in Sources */, 0782B17B258C236B00D5FCE5 /* MBXGeometry.swift in Sources */, B529341D2624E9D6003B181C /* DelegatingMapClient.swift in Sources */, + 0C350D85263278090090FA74 /* CameraTransition.swift in Sources */, 0C3B1E9224DDADD000CC29E8 /* MapboxMobileEvents+TelemetryProtocol.swift in Sources */, 0C9640E12531056700CABD3E /* LocationConsumer.swift in Sources */, 0C708F4D24EB23C7003CE791 /* RasterDemSource.swift in Sources */, @@ -1968,7 +1968,6 @@ 0C55010B2476D83A00AE019A /* Light.swift in Sources */, B5B55D45260E4D1500EBB589 /* AnimationOwner.swift in Sources */, 0CD34FE8242AAE0900943687 /* MapView+Managers.swift in Sources */, - B5B55D44260E4D1500EBB589 /* CameraAnimator.swift in Sources */, CA6245D52627E72A00C79547 /* TileRegionLoadOptions+MapboxMaps.swift in Sources */, 5A9648FE246429C3001FF05D /* MapboxCompassOrnamentView.swift in Sources */, 0C8AA1F3257FE1830037FD6B /* MapEvents.swift in Sources */, @@ -1981,7 +1980,6 @@ CA0C426926028ED30054D9D0 /* AnnotationOptions.swift in Sources */, 0C708F2F24EB1EE2003CE791 /* RasterLayer.swift in Sources */, 0C35750225DD8D960085D775 /* StyleErrors.swift in Sources */, - 0CE40D9926326C9A0067FCE5 /* FlyToCameraAnimator.swift in Sources */, 0CE3D1B425816BD6000585A2 /* MapView+Supportable.swift in Sources */, 0C9DE369252C263600880CC8 /* GeoJSONSourceData.swift in Sources */, 0782B142258C1CAB00D5FCE5 /* NSValue.swift in Sources */, @@ -1997,8 +1995,8 @@ 0782B010258C051900D5FCE5 /* ScreenCoordinate.swift in Sources */, CABCDF372620E03A00D61635 /* CredentialsManager.swift in Sources */, 0782B11B258C1B9C00D5FCE5 /* Feature.swift in Sources */, + 0C350D84263278090090FA74 /* BasicCameraAnimator.swift in Sources */, C64B6FEA25E0479000C8E07E /* Bundle+MapboxMaps.swift in Sources */, - 0CE8F1222631ECD0004F94E0 /* CameraTransition.swift in Sources */, 0782AFE6258BFD2300D5FCE5 /* CoreLocation.swift in Sources */, 2B8637D52461601100698135 /* MapboxScaleBarOrnamentView.swift in Sources */, 0CD62F0824588505006421D1 /* QuickZoomGestureHandler.swift in Sources */, @@ -2007,6 +2005,7 @@ 0706C49225B1128A008733C0 /* Terrain.swift in Sources */, CA03F1132626948300673961 /* StylePackLoadOptions+MapboxMaps.swift in Sources */, CABCDF4B2620E0D800D61635 /* Bool.swift in Sources */, + 0C350D83263278090090FA74 /* FlyToCameraAnimator.swift in Sources */, 0C88F22624F04D1600FC35F3 /* ExpressionBuilder.swift in Sources */, 1FECC9E02474519D00B63910 /* Expression.swift in Sources */, B52050A8260538240003E5BB /* ModelLayer.swift in Sources */, @@ -2054,12 +2053,12 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 0C350D8F263278420090FA74 /* CameraViewTests.swift in Sources */, CA03F08F2624FDCB00673961 /* MapInitOptionsIntegrationTests.swift in Sources */, + 0C350D91263278420090FA74 /* CameraViewMock.swift in Sources */, 0C5CFCDC25BB951B0001E753 /* ModelLayerTests.swift in Sources */, - 0CE8F12F2631ECFB004F94E0 /* CameraViewMock.swift in Sources */, B5B55D5F260E4D7B00EBB589 /* CameraAnimatorDelegateMock.swift in Sources */, CA548FD3251C404B00F829A3 /* PolygonTests.swift in Sources */, - 0CE8F1302631ECFB004F94E0 /* CameraTransitionTests.swift in Sources */, CAC1965525AEAFD900F69FEA /* GlyphsRasterizationOptionsTests.swift in Sources */, 0C32C9B625F979680057ED31 /* VectorSourceIntegrationTests.swift in Sources */, CA548FD4251C404B00F829A3 /* MapboxMapsCameraTests.swift in Sources */, @@ -2097,7 +2096,6 @@ 0CC6EF0B25C3263400BFB153 /* ModelLayerIntegrationTests.swift in Sources */, 0CC6EF1A25C3263400BFB153 /* FillExtrusionLayerIntegrationTests.swift in Sources */, 0CC6EF1D25C3263400BFB153 /* HeatmapLayerIntegrationTests.swift in Sources */, - 0CE8F12D2631ECFB004F94E0 /* UIViewPropertyAnimatorMock.swift in Sources */, CA548FE0251C404B00F829A3 /* GeoJSONManagerTests.swift in Sources */, CA548FE1251C404B00F829A3 /* PinchGestureHandlerTests.swift in Sources */, CA050571259104E200FF7D02 /* FeatureQueryingTest.swift in Sources */, @@ -2135,6 +2133,7 @@ 0CC6EF2625C3263400BFB153 /* HillshadeLayerIntegrationTests.swift in Sources */, CA548FEB251C404B00F829A3 /* MapboxMapsFoundationTests.swift in Sources */, CA548FEC251C404B00F829A3 /* MultiPolygonTests.swift in Sources */, + 0C350D92263278420090FA74 /* UIViewPropertyAnimatorMock.swift in Sources */, B5A6921C262754DF00A03412 /* MockDelegatingMapClientDelegate.swift in Sources */, CA548FED251C404B00F829A3 /* Geometry+MBXGeometryTests.swift in Sources */, 0C5CFDD525BE29BC0001E753 /* SouceProperties+Fixtures.swift in Sources */, @@ -2160,7 +2159,6 @@ B5B8B74F26308AC300D936E5 /* CameraOptionsTests.swift in Sources */, CAD7BA0625A368DE00E64C78 /* ExampleIntegrationTest.swift in Sources */, C6334944262E28C300D17701 /* StyleTransitionTests.swift in Sources */, - 0CE8F12C2631ECFB004F94E0 /* CameraViewTests.swift in Sources */, CA99A8702540CD1900D16C78 /* StyleLoadIntegrationTests.swift in Sources */, CA548FF5251C404B00F829A3 /* Fixture.swift in Sources */, 0C5CFCD325BB951B0001E753 /* RasterLayerTests.swift in Sources */, @@ -2177,13 +2175,14 @@ 0CDFEB0D25A7745A008BC505 /* UtilsTests.swift in Sources */, CA548FFB251C404B00F829A3 /* StyleURITests.swift in Sources */, CA2E4A1B2538D3530096DEDE /* MapViewIntegrationTestCase.swift in Sources */, - 0CE8F12E2631ECFB004F94E0 /* FlyToAnimatorTests.swift in Sources */, 0C32CA2A25F982300057ED31 /* ImageSourceTests.swift in Sources */, + 0C350D90263278420090FA74 /* FlyToAnimatorTests.swift in Sources */, B521FC9D262F665C00B9A446 /* SizeTests.swift in Sources */, 0C5CFCD625BB951B0001E753 /* FillExtrusionLayerTests.swift in Sources */, B54B7F2125DB1ABA003FD6CA /* Stub.swift in Sources */, 0C01C0B925486E7200E4AA46 /* ExpressionTests.swift in Sources */, CA548FFC251C404B00F829A3 /* PanGestureHandlerTests.swift in Sources */, + 0C350D8E263278420090FA74 /* CameraTransitionTests.swift in Sources */, CA548FFD251C404B00F829A3 /* QuickZoomGestureHandlerTests.swift in Sources */, 0C9DE3B3252C2A1F00880CC8 /* GeoJSONSourceDataTests.swift in Sources */, ); From c3936e84a43acc7c2c9df8dc567d06d68dd1154b Mon Sep 17 00:00:00 2001 From: Nishant Karajgikar Date: Fri, 23 Apr 2021 10:22:53 -0400 Subject: [PATCH 29/40] Reorder completion call --- Sources/MapboxMaps/Foundation/Camera/CameraManager.swift | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift b/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift index e613384681e..dafe6d7131e 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift @@ -191,8 +191,8 @@ public class CameraManager { // Add completion cameraAnimator.addCompletion({ [weak self] (position) in - completion?(position) self?.internalAnimator = nil + completion?(position) }) // Start animation @@ -235,8 +235,8 @@ public class CameraManager { // Nil out the internalAnimator after `flyTo` finishes flyToAnimator.addCompletion { [weak self](position) in // Call the developer-provided completion (if present) - completion?(position) self?.internalAnimator = nil + completion?(position) } flyToAnimator.startAnimation() @@ -268,12 +268,9 @@ public class CameraManager { // Nil out the `internalAnimator` once the "ease to" finishes animator.addCompletion { [weak self] (_) in self?.internalAnimator = nil - } - - // Add the developer-provided completion (if present) - if let completion = completion { animator.addCompletion(completion) } + animator.startAnimation() internalAnimator = animator From c7959d1156059e143f0a09ce1479640e2401af2c Mon Sep 17 00:00:00 2001 From: Nishant Karajgikar Date: Fri, 23 Apr 2021 11:00:38 -0400 Subject: [PATCH 30/40] Review comments --- .../MapboxMaps/Foundation/BaseMapView.swift | 2 +- .../Camera/BasicCameraAnimator.swift | 19 +++++++------------ .../Foundation/Camera/CameraManager.swift | 4 ++-- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/Sources/MapboxMaps/Foundation/BaseMapView.swift b/Sources/MapboxMaps/Foundation/BaseMapView.swift index d4a4418faf3..170227d8793 100644 --- a/Sources/MapboxMaps/Foundation/BaseMapView.swift +++ b/Sources/MapboxMaps/Foundation/BaseMapView.swift @@ -35,7 +35,7 @@ open class BaseMapView: UIView { /// List of animators currently alive public var cameraAnimators: [CameraAnimator] { - return cameraAnimatorsHashTable.allObjects.compactMap { $0 as? CameraAnimator } + return cameraAnimatorsHashTable.allObjects } /// Map of event types to subscribed event handlers diff --git a/Sources/MapboxMaps/Foundation/Camera/BasicCameraAnimator.swift b/Sources/MapboxMaps/Foundation/Camera/BasicCameraAnimator.swift index 86d950bb678..ceb315dd93f 100644 --- a/Sources/MapboxMaps/Foundation/Camera/BasicCameraAnimator.swift +++ b/Sources/MapboxMaps/Foundation/Camera/BasicCameraAnimator.swift @@ -11,31 +11,27 @@ import CoreLocation } /// Internal-facing protocol to represent camera animators -@objc internal protocol CameraAnimatorInterface: AnyObject { +@objc internal protocol CameraAnimatorInterface: CameraAnimator { func update() - - func stopAnimation() - - var state: UIViewAnimatingState { get } } // MARK: CameraAnimator Class public class BasicCameraAnimator: NSObject, CameraAnimator, CameraAnimatorInterface { /// Instance of the property animator that will run animations. - internal private(set) var propertyAnimator: UIViewPropertyAnimator + private let propertyAnimator: UIViewPropertyAnimator /// Delegate that conforms to `CameraAnimatorDelegate`. internal private(set) weak var delegate: CameraAnimatorDelegate? /// The ID of the owner of this `CameraAnimator`. - public private(set) var owner: AnimationOwner + private let owner: AnimationOwner /// The `CameraView` owned by this animator - private var cameraView: CameraView + private let cameraView: CameraView /// Represents the animation that this animator is attempting to execute - internal private(set) var animation: ((inout CameraTransition) -> Void)? + private var animation: ((inout CameraTransition) -> Void)? /// Defines the transition that will occur to the `CameraOptions` of the renderer due to this animator public private(set) var transition: CameraTransition? @@ -79,6 +75,7 @@ public class BasicCameraAnimator: NSObject, CameraAnimator, CameraAnimatorInterf propertyAnimator.stopAnimation(false) propertyAnimator.finishAnimation(at: .current) cameraView.removeFromSuperview() + delayedAnimationTimer?.invalidate() } /// Starts the animation if this animator is in `inactive` state. Also used to resume a "paused" animation. @@ -116,9 +113,7 @@ public class BasicCameraAnimator: NSObject, CameraAnimator, CameraAnimatorInterf /// - Parameter delay: Delay (in seconds) after which the animation should start public func startAnimation(afterDelay delay: TimeInterval) { delayedAnimationTimer = Timer.scheduledTimer(withTimeInterval: delay, repeats: false, block: { [weak self] (timer) in - guard let self = self else { return } - self.startAnimation() - timer.invalidate() + self?.startAnimation() }) } diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift b/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift index dafe6d7131e..9c5569793bf 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift @@ -266,9 +266,9 @@ public class CameraManager { } // Nil out the `internalAnimator` once the "ease to" finishes - animator.addCompletion { [weak self] (_) in + animator.addCompletion { [weak self] (position) in self?.internalAnimator = nil - animator.addCompletion(completion) + completion?(position) } animator.startAnimation() From a2e019b1055a785b4b9ce87098abd86984074a5b Mon Sep 17 00:00:00 2001 From: Nishant Karajgikar Date: Tue, 27 Apr 2021 11:33:02 -0400 Subject: [PATCH 31/40] Changes from refactoring flytoCameraAnimator + weakset introduction --- .../DebugApp/DebugViewController.swift | 2 +- .../MapboxMaps/Foundation/BaseMapView.swift | 34 ++++- .../Foundation/Camera/AnimationOwner.swift | 2 +- .../Camera/BasicCameraAnimator.swift | 15 +-- .../Camera/CameraAnimationDelegate.swift | 3 - ...CameraManager+CameraAnimatorDelegate.swift | 15 +-- .../Foundation/Camera/CameraManager.swift | 23 ++-- .../Camera/FlyToCameraAnimator.swift | 123 +++++++----------- .../Camera/CameraAnimatorTests.swift | 34 +++-- .../Camera/FlyToAnimatorTests.swift | 108 ++++++++------- .../ExampleIntegrationTest.swift | 4 +- 11 files changed, 175 insertions(+), 188 deletions(-) diff --git a/Apps/DebugApp/DebugApp/DebugViewController.swift b/Apps/DebugApp/DebugApp/DebugViewController.swift index 8651986cb0e..711cec5ea8c 100644 --- a/Apps/DebugApp/DebugApp/DebugViewController.swift +++ b/Apps/DebugApp/DebugApp/DebugViewController.swift @@ -11,7 +11,7 @@ import Turf public class DebugViewController: UIViewController { internal var mapView: MapView! - internal var runningAnimator: BasicCameraAnimator? + internal var runningAnimator: CameraAnimator? override public func viewDidLoad() { super.viewDidLoad() diff --git a/Sources/MapboxMaps/Foundation/BaseMapView.swift b/Sources/MapboxMaps/Foundation/BaseMapView.swift index 170227d8793..e6e95fe088b 100644 --- a/Sources/MapboxMaps/Foundation/BaseMapView.swift +++ b/Sources/MapboxMaps/Foundation/BaseMapView.swift @@ -6,6 +6,26 @@ import Turf // swiftlint:disable file_length internal typealias PendingAnimationCompletion = (completion: AnimationCompletion, animatingPosition: UIViewAnimatingPosition) +internal class WeakCameraAnimatorSet { + private let hashTable = NSHashTable.weakObjects() + + internal func add(_ object: CameraAnimatorInterface) { + hashTable.add((object as! NSObject)) + } + + internal func remove(_ object: CameraAnimatorInterface) { + hashTable.remove((object as! NSObject)) + } + + internal func removeAll() { + hashTable.removeAllObjects() + } + + internal var allObjects: [CameraAnimatorInterface] { + hashTable.allObjects.map { $0 as! CameraAnimatorInterface } + } +} + open class BaseMapView: UIView { // mapbox map depends on MapInitOptions, which is not available until @@ -31,11 +51,15 @@ open class BaseMapView: UIView { internal var pendingAnimatorCompletionBlocks: [PendingAnimationCompletion] = [] /// Pointer HashTable for holding camera animators - internal var cameraAnimatorsHashTable = NSHashTable.weakObjects() + private var cameraAnimatorsSet = WeakCameraAnimatorSet() + + internal func addCameraAnimator(_ cameraAnimator: CameraAnimatorInterface) { + cameraAnimatorsSet.add(cameraAnimator) + } /// List of animators currently alive public var cameraAnimators: [CameraAnimator] { - return cameraAnimatorsHashTable.allObjects + return cameraAnimatorsSet.allObjects } /// Map of event types to subscribed event handlers @@ -230,8 +254,10 @@ open class BaseMapView: UIView { if needsDisplayRefresh { needsDisplayRefresh = false - for animator in cameraAnimatorsHashTable.allObjects { - animator.update() + for animator in cameraAnimatorsSet.allObjects { + if let cameraOptions = animator.currentCameraOptions { + mapboxMap.updateCamera(with: cameraOptions) + } } /// This executes the series of scheduled animation completion blocks and also removes them from the list diff --git a/Sources/MapboxMaps/Foundation/Camera/AnimationOwner.swift b/Sources/MapboxMaps/Foundation/Camera/AnimationOwner.swift index d5496d9dff0..e16377a9df9 100644 --- a/Sources/MapboxMaps/Foundation/Camera/AnimationOwner.swift +++ b/Sources/MapboxMaps/Foundation/Camera/AnimationOwner.swift @@ -1,5 +1,5 @@ // MARK: AnimationOwner Enum -public enum AnimationOwner { +public enum AnimationOwner: Equatable { case gestures case unspecified case custom(id: String) diff --git a/Sources/MapboxMaps/Foundation/Camera/BasicCameraAnimator.swift b/Sources/MapboxMaps/Foundation/Camera/BasicCameraAnimator.swift index ceb315dd93f..d4f8759baf7 100644 --- a/Sources/MapboxMaps/Foundation/Camera/BasicCameraAnimator.swift +++ b/Sources/MapboxMaps/Foundation/Camera/BasicCameraAnimator.swift @@ -1,7 +1,7 @@ import UIKit import CoreLocation -@objc public protocol CameraAnimator: AnyObject { +public protocol CameraAnimator { /// Stops the animation in its tracks and calls any provided completion func stopAnimation() @@ -11,8 +11,8 @@ import CoreLocation } /// Internal-facing protocol to represent camera animators -@objc internal protocol CameraAnimatorInterface: CameraAnimator { - func update() +internal protocol CameraAnimatorInterface: CameraAnimator { + var currentCameraOptions: CameraOptions? { get } } // MARK: CameraAnimator Class @@ -157,13 +157,12 @@ public class BasicCameraAnimator: NSObject, CameraAnimator, CameraAnimatorInterf propertyAnimator.continueAnimation(withTimingParameters: parameters, durationFactor: CGFloat(durationFactor)) } - public func update() { + internal var currentCameraOptions: CameraOptions? { // Only call jumpTo if this animator is currently "active" and there are known changes to animate. guard propertyAnimator.state == .active, - let transition = transition, - let delegate = delegate else { - return + let transition = transition else { + return nil } var cameraOptions = CameraOptions() @@ -193,6 +192,6 @@ public class BasicCameraAnimator: NSObject, CameraAnimator, CameraAnimatorInterf cameraOptions.pitch = interpolatedCamera.pitch } - delegate.jumpTo(camera: cameraOptions) + return cameraOptions } } diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraAnimationDelegate.swift b/Sources/MapboxMaps/Foundation/Camera/CameraAnimationDelegate.swift index d6c27142e45..e3a4cf57398 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraAnimationDelegate.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraAnimationDelegate.swift @@ -9,9 +9,6 @@ internal protocol CameraAnimatorDelegate: class { /// The current camera of the map var camera: CameraOptions { get } - /// Ask the map to set camera to new camera - func jumpTo(camera: CameraOptions) - /// Adds the view to the MapView's subviews func addViewToViewHeirarchy(_ view: CameraView) diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraManager+CameraAnimatorDelegate.swift b/Sources/MapboxMaps/Foundation/Camera/CameraManager+CameraAnimatorDelegate.swift index d742d8349ec..681e02dca85 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraManager+CameraAnimatorDelegate.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraManager+CameraAnimatorDelegate.swift @@ -22,7 +22,7 @@ extension CameraManager: CameraAnimatorDelegate { let propertyAnimator = UIViewPropertyAnimator(duration: duration, timingParameters: parameters) let cameraAnimator = BasicCameraAnimator(delegate: self, propertyAnimator: propertyAnimator, owner: animationOwner) cameraAnimator.addAnimations(animations) - mapView?.cameraAnimatorsHashTable.add(cameraAnimator) + mapView?.addCameraAnimator(cameraAnimator) return cameraAnimator } @@ -46,7 +46,7 @@ extension CameraManager: CameraAnimatorDelegate { let propertyAnimator = UIViewPropertyAnimator(duration: duration, curve: curve) let cameraAnimator = BasicCameraAnimator(delegate: self, propertyAnimator: propertyAnimator, owner: animationOwner) cameraAnimator.addAnimations(animations) - mapView?.cameraAnimatorsHashTable.add(cameraAnimator) + mapView?.addCameraAnimator(cameraAnimator) return cameraAnimator } @@ -72,7 +72,7 @@ extension CameraManager: CameraAnimatorDelegate { let propertyAnimator = UIViewPropertyAnimator(duration: duration, controlPoint1: point1, controlPoint2: point2) let cameraAnimator = BasicCameraAnimator(delegate: self, propertyAnimator: propertyAnimator, owner: animationOwner) cameraAnimator.addAnimations(animations) - mapView?.cameraAnimatorsHashTable.add(cameraAnimator) + mapView?.addCameraAnimator(cameraAnimator) return cameraAnimator } @@ -97,7 +97,7 @@ extension CameraManager: CameraAnimatorDelegate { let propertyAnimator = UIViewPropertyAnimator(duration: duration, dampingRatio: ratio) let cameraAnimator = BasicCameraAnimator(delegate: self, propertyAnimator: propertyAnimator, owner: animationOwner) cameraAnimator.addAnimations(animations) - mapView?.cameraAnimatorsHashTable.add(cameraAnimator) + mapView?.addCameraAnimator(cameraAnimator) return cameraAnimator } @@ -115,13 +115,6 @@ extension CameraManager: CameraAnimatorDelegate { return validMapView.cameraOptions } - func jumpTo(camera: CameraOptions) { - guard let validMapView = mapView else { - fatalError("MapView cannot be nil.") - } - validMapView.mapboxMap.updateCamera(with: camera) - } - func addViewToViewHeirarchy(_ view: CameraView) { guard let validMapView = mapView else { diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift b/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift index 9c5569793bf..1444cb9375e 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift @@ -167,7 +167,7 @@ public class CameraManager { /// Canceled animations cannot be restarted / resumed. The animator must be recreated. public func cancelAnimations() { guard let validMapView = mapView else { return } - for animator in validMapView.cameraAnimatorsHashTable.allObjects where animator.state == UIViewAnimatingState.active { + for animator in validMapView.cameraAnimators { animator.stopAnimation() } } @@ -217,23 +217,25 @@ public class CameraManager { duration: TimeInterval? = nil, completion: AnimationCompletion? = nil) -> CameraAnimator? { - guard let mapView = mapView else { + guard let mapView = mapView, + let flyToAnimator = FlyToCameraAnimator( + inital: mapView.cameraOptions, + final: camera, + owner: .custom(id: "fly-to"), + duration: duration, + mapSize: mapView.mapboxMap.size, + delegate: self) else { + Log.warning(forMessage: "Unable to start fly-to animation", category: "CameraManager") return nil } // Stop the `internalAnimator` before beginning a `flyTo` internalAnimator?.stopAnimation() - let flyToAnimator = FlyToCameraAnimator(delegate: self) - mapView.cameraAnimatorsHashTable.add(flyToAnimator) - - flyToAnimator.makeFlyToInterpolator(from: mapView.cameraOptions, - to: camera, - duration: duration, - screenFullSize: mapView.bounds.size) + mapView.addCameraAnimator(flyToAnimator) // Nil out the internalAnimator after `flyTo` finishes - flyToAnimator.addCompletion { [weak self](position) in + flyToAnimator.addCompletion { [weak self] (position) in // Call the developer-provided completion (if present) self?.internalAnimator = nil completion?(position) @@ -241,7 +243,6 @@ public class CameraManager { flyToAnimator.startAnimation() internalAnimator = flyToAnimator - return internalAnimator } diff --git a/Sources/MapboxMaps/Foundation/Camera/FlyToCameraAnimator.swift b/Sources/MapboxMaps/Foundation/Camera/FlyToCameraAnimator.swift index b3585ffb6f7..ae7c55416c4 100644 --- a/Sources/MapboxMaps/Foundation/Camera/FlyToCameraAnimator.swift +++ b/Sources/MapboxMaps/Foundation/Camera/FlyToCameraAnimator.swift @@ -6,112 +6,77 @@ public class FlyToCameraAnimator: NSObject, CameraAnimator, CameraAnimatorInterf public private(set) var owner: AnimationOwner - internal private(set) var flyToInterpolator: FlyToInterpolator? + private let interpolator: FlyToInterpolator - internal private(set) var animationDuration: TimeInterval? + public let duration: TimeInterval public private(set) var state: UIViewAnimatingState = .inactive - internal private(set) var startTime: Date? + private var start: Date? - internal private(set) var endTime: Date? + private let finalCameraOptions: CameraOptions - internal private(set) var finalCameraOptions: CameraOptions? + private var completionBlocks = [AnimationCompletion]() - internal private(set) var animationCompletion: AnimationCompletion? - - internal init(delegate: CameraAnimatorDelegate, - owner: AnimationOwner = .custom(id: "fly-to")) { - - self.delegate = delegate - self.owner = owner - } - - deinit { - scheduleCompletionIfNecessary(position: .current) - } - - internal func makeFlyToInterpolator(from initalCamera: CameraOptions, to finalCamera: CameraOptions, duration: TimeInterval? = nil, screenFullSize: CGSize) { - - guard let flyTo = FlyToInterpolator(from: initalCamera, - to: finalCamera, - size: screenFullSize) else { - assertionFailure("FlyToInterpolator could not be created.") - return + internal init?(inital: CameraOptions, + final: CameraOptions, + owner: AnimationOwner, + duration: TimeInterval? = nil, + mapSize: CGSize, + delegate: CameraAnimatorDelegate) { + guard let flyToInterpolator = FlyToInterpolator(from: inital, to: final, size: mapSize) else { + return nil } - - var time = duration ?? -1.0 - - // If there was no duration specified, or a negative argument, use a default - if time < 0.0 { - time = flyTo.duration() + if let duration = duration { + guard duration >= 0 else { + return nil + } } - - animationDuration = time - flyToInterpolator = flyTo - finalCameraOptions = finalCamera + self.interpolator = flyToInterpolator + self.delegate = delegate + self.owner = owner + self.finalCameraOptions = final + self.duration = duration ?? flyToInterpolator.duration() } public func stopAnimation() { state = .stopped - flyToInterpolator = nil scheduleCompletionIfNecessary(position: .current) // `current` represents an interrupted animation. } internal func startAnimation() { - - guard flyToInterpolator != nil, let animationDuration = animationDuration else { - fatalError("FlyToInterpolator not created") - } - state = .active - startTime = Date() - endTime = startTime?.addingTimeInterval(animationDuration) + start = Date() } - internal func addCompletion(_ completion: AnimationCompletion?) { - animationCompletion = completion + internal func addCompletion(_ completion: @escaping AnimationCompletion) { + completionBlocks.append(completion) } - internal func scheduleCompletionIfNecessary(position: UIViewAnimatingPosition) { - if let delegate = delegate, let validAnimationCompletion = animationCompletion { - delegate.schedulePendingCompletion(forAnimator: self, - completion: validAnimationCompletion, - animatingPosition: position) - - // Once a completion has been scheduled, `nil` it out so it can't be executed again. - animationCompletion = nil + private func scheduleCompletionIfNecessary(position: UIViewAnimatingPosition) { + for completion in completionBlocks { + delegate?.schedulePendingCompletion( + forAnimator: self, + completion: completion, + animatingPosition: position) } - + completionBlocks.removeAll() } - internal func update() { - - guard state == .active, - let startTime = startTime, - let endTime = endTime, - let animationDuration = animationDuration, - let flyTo = flyToInterpolator else { - return + internal var currentCameraOptions: CameraOptions? { + guard state == .active, let start = start else { + return nil } - - let currentTime = Date() - - guard currentTime <= endTime else { - flyToInterpolator = nil + let fractionComplete = min(Date().timeIntervalSince(start) / duration, 1) + guard fractionComplete < 1 else { state = .stopped - self.scheduleCompletionIfNecessary(position: .end) - return + scheduleCompletionIfNecessary(position: .end) + return finalCameraOptions } - - let fractionComplete = currentTime.timeIntervalSince(startTime) / animationDuration - - let cameraOptions = CameraOptions(center: flyTo.coordinate(at: fractionComplete), - zoom: CGFloat(flyTo.zoom(at: fractionComplete)), - bearing: flyTo.bearing(at: fractionComplete), - pitch: CGFloat(flyTo.pitch(at: fractionComplete))) - - delegate?.jumpTo(camera: cameraOptions) - + return CameraOptions( + center: interpolator.coordinate(at: fractionComplete), + zoom: CGFloat(interpolator.zoom(at: fractionComplete)), + bearing: interpolator.bearing(at: fractionComplete), + pitch: CGFloat(interpolator.pitch(at: fractionComplete))) } } diff --git a/Tests/MapboxMapsTests/Foundation/Camera/CameraAnimatorTests.swift b/Tests/MapboxMapsTests/Foundation/Camera/CameraAnimatorTests.swift index da0721b1678..3fea49e6100 100644 --- a/Tests/MapboxMapsTests/Foundation/Camera/CameraAnimatorTests.swift +++ b/Tests/MapboxMapsTests/Foundation/Camera/CameraAnimatorTests.swift @@ -1,12 +1,13 @@ import XCTest @testable import MapboxMaps -internal let cameraOptionsTestValue = CameraOptions(center: CLLocationCoordinate2D(latitude: 10, longitude: 10), - padding: .init(top: 10, left: 10, bottom: 10, right: 10), - anchor: .init(x: 10.0, y: 10.0), - zoom: 10, - bearing: 10, - pitch: 10) +internal let cameraOptionsTestValue = CameraOptions( + center: CLLocationCoordinate2D(latitude: 10, longitude: 10), + padding: .init(top: 10, left: 10, bottom: 10, right: 10), + anchor: .init(x: 10.0, y: 10.0), + zoom: 10, + bearing: 10, + pitch: 10) internal class BasicCameraAnimatorTests: XCTestCase { @@ -14,16 +15,17 @@ internal class BasicCameraAnimatorTests: XCTestCase { var delegate: CameraAnimatorDelegateMock! var propertyAnimator: UIViewPropertyAnimatorMock! var cameraView: CameraViewMock! - var animator: BasicCameraAnimator? + var animator: BasicCameraAnimator! override func setUp() { delegate = CameraAnimatorDelegateMock() propertyAnimator = UIViewPropertyAnimatorMock() cameraView = CameraViewMock() - animator = BasicCameraAnimator(delegate: delegate, - propertyAnimator: propertyAnimator , - owner: .unspecified, - cameraView: cameraView) + animator = BasicCameraAnimator( + delegate: delegate, + propertyAnimator: propertyAnimator , + owner: .unspecified, + cameraView: cameraView) } func testDeinit() { @@ -53,7 +55,7 @@ internal class BasicCameraAnimatorTests: XCTestCase { } - func testUpdate() { + func testCurrentCameraOptions() { animator?.addAnimations { (transition) in transition.zoom.toValue = cameraOptionsTestValue.zoom! transition.center.toValue = cameraOptionsTestValue.center! @@ -62,15 +64,11 @@ internal class BasicCameraAnimatorTests: XCTestCase { transition.pitch.toValue = cameraOptionsTestValue.pitch! transition.padding.toValue = cameraOptionsTestValue.padding! } - animator?.startAnimation() - propertyAnimator.shouldReturnState = .active - animator?.update() - XCTAssertEqual(delegate.jumpToStub.invocations.count, 1) - XCTAssertEqual(delegate.jumpToStub.invocations.first?.parameters, - cameraView.localCamera) + let cameraOptions = animator.currentCameraOptions + XCTAssertEqual(cameraOptions, cameraView.localCamera) } } diff --git a/Tests/MapboxMapsTests/Foundation/Camera/FlyToAnimatorTests.swift b/Tests/MapboxMapsTests/Foundation/Camera/FlyToAnimatorTests.swift index 9a4a06cc7c6..b2c41db7492 100644 --- a/Tests/MapboxMapsTests/Foundation/Camera/FlyToAnimatorTests.swift +++ b/Tests/MapboxMapsTests/Foundation/Camera/FlyToAnimatorTests.swift @@ -3,75 +3,85 @@ import XCTest final class FlyToAnimatorTests: XCTestCase { - let initalCameraOptions = CameraOptions(center: CLLocationCoordinate2D(latitude: 42.3601, - longitude: -71.0589), - padding: .zero, - zoom: 10, - bearing: 10, - pitch: 10) - - let finalCameraOptions = CameraOptions(center: CLLocationCoordinate2D(latitude: 37.7749, - longitude: -122.4194), - padding: .zero, - zoom: 10, - bearing: 10, - pitch: 10) + let initalCameraOptions = CameraOptions( + center: CLLocationCoordinate2D( + latitude: 42.3601, + longitude: -71.0589), + padding: .zero, + zoom: 10, + bearing: 10, + pitch: 10) + + let finalCameraOptions = CameraOptions( + center: CLLocationCoordinate2D( + latitude: 37.7749, + longitude: -122.4194), + padding: .zero, + zoom: 10, + bearing: 10, + pitch: 10) + + let animationOwner = AnimationOwner.custom(id: "") + let duration: TimeInterval = 10 var flyToAnimator: FlyToCameraAnimator! - var cameraAnimatorDelegateMock: CameraAnimatorDelegateMock! + var cameraAnimatorDelegate: CameraAnimatorDelegateMock! override func setUp() { - cameraAnimatorDelegateMock = CameraAnimatorDelegateMock() - flyToAnimator = FlyToCameraAnimator(delegate: cameraAnimatorDelegateMock) - flyToAnimator.makeFlyToInterpolator(from: initalCameraOptions, - to: finalCameraOptions, - duration: 10, - screenFullSize: .init(width: 500, height: 500)) + super.setUp() + cameraAnimatorDelegate = CameraAnimatorDelegateMock() + flyToAnimator = FlyToCameraAnimator( + inital: initalCameraOptions, + final: finalCameraOptions, + owner: .custom(id: ""), + duration: duration, + mapSize: CGSize(width: 500, height: 500), + delegate: cameraAnimatorDelegate) } - func testMakeFlyToInterpolator() { - XCTAssertNotNil(flyToAnimator.flyToInterpolator) - XCTAssertNotNil(flyToAnimator.delegate) - XCTAssertEqual(flyToAnimator.finalCameraOptions, finalCameraOptions) - XCTAssertEqual(flyToAnimator.animationDuration, 10) + override func tearDown() { + cameraAnimatorDelegate = nil + flyToAnimator = nil + super.tearDown() + } + func testInitializationWithValidOptions() { + XCTAssertTrue(flyToAnimator.delegate === cameraAnimatorDelegate) + XCTAssertEqual(flyToAnimator.owner, animationOwner) + XCTAssertEqual(flyToAnimator.duration, duration) + XCTAssertEqual(flyToAnimator.state, .inactive) } - func testStartAnimation() { - flyToAnimator.startAnimation() - XCTAssertEqual(flyToAnimator.state, .active) - XCTAssertNotNil(flyToAnimator.startTime) - XCTAssertNotNil(flyToAnimator.endTime) + func testInitializationWithANegativeDurationReturnsNil() { } - func testAddCompletion() { - flyToAnimator.addCompletion { (position) in - print(position) - } + func testInitializationWithANilDurationSetsDurationToCalculatedValue() { + } - XCTAssertNotNil(flyToAnimator.animationCompletion) + func testInitializationWithInvalidCameraOptionsReturnsNil() { } - func testStopAnimation() { + func testStartAnimationChangesStateToActive() { + flyToAnimator.startAnimation() + + XCTAssertEqual(flyToAnimator.state, .active) + } - flyToAnimator.addCompletion { (position) in - print(position) - } + func testAnimationBlocksAreScheduledWhenAnimationIsComplete() { + } - flyToAnimator.startAnimation() + func testAnimationBlocksAreScheduledWhenStopAnimationIsInvoked() { + } - flyToAnimator.stopAnimation() + func testStopAnimationChangesStateToStopped() { + } - XCTAssertEqual(flyToAnimator.state, .stopped) - XCTAssertNil(flyToAnimator.flyToInterpolator) - XCTAssertEqual(cameraAnimatorDelegateMock.schedulePendingCompletionStub.invocations.count, 1) - XCTAssertEqual(cameraAnimatorDelegateMock.schedulePendingCompletionStub.invocations.first!.parameters.animatingPosition, .current) + func testCurrentCameraOptionsReturnsNilIfAnimationIsNotRunning() { } - func testUpdate() { - flyToAnimator.startAnimation() - flyToAnimator.update() - XCTAssertEqual(cameraAnimatorDelegateMock.jumpToStub.invocations.count, 1) + func testCurrentCameraOptionsReturnsInterpolatedValueIfAnimationIsRunning() { } + func testCurrentCameraOptionsReturnsFinalCameraOptionsIfAnimationIsComplete() { + } } diff --git a/Tests/MapboxMapsTests/MapView/Integration Tests/ExampleIntegrationTest.swift b/Tests/MapboxMapsTests/MapView/Integration Tests/ExampleIntegrationTest.swift index b9ef1645279..90c07d172a5 100644 --- a/Tests/MapboxMapsTests/MapView/Integration Tests/ExampleIntegrationTest.swift +++ b/Tests/MapboxMapsTests/MapView/Integration Tests/ExampleIntegrationTest.swift @@ -9,9 +9,7 @@ internal class ExampleIntegrationTest: MapViewIntegrationTestCase { } internal func testWaitForIdle() throws { - guard - let mapView = mapView, - let style = style else { + guard let style = style else { XCTFail("There should be valid MapView and Style objects created by setUp.") return } From 3befbf93ac14846b05e42e3145d4bd3aeab374f2 Mon Sep 17 00:00:00 2001 From: Nishant Karajgikar Date: Tue, 27 Apr 2021 11:41:58 -0400 Subject: [PATCH 32/40] Review comments --- .../MapboxMaps/Foundation/BaseMapView.swift | 10 +++------- .../Camera/BasicCameraAnimator.swift | 14 -------------- .../Foundation/Camera/CameraManager.swift | 18 +++++++++++++++--- 3 files changed, 18 insertions(+), 24 deletions(-) diff --git a/Sources/MapboxMaps/Foundation/BaseMapView.swift b/Sources/MapboxMaps/Foundation/BaseMapView.swift index e6e95fe088b..e13910f7941 100644 --- a/Sources/MapboxMaps/Foundation/BaseMapView.swift +++ b/Sources/MapboxMaps/Foundation/BaseMapView.swift @@ -121,13 +121,9 @@ open class BaseMapView: UIView { /// The map's current anchor, calculated after applying padding (if it exists) public var anchor: CGPoint { - - let paddding = padding - let xAfterPadding = center.x + paddding.left - paddding.right - let yAfterPadding = center.y + paddding.top - paddding.bottom - let anchor = CGPoint(x: xAfterPadding, y: yAfterPadding) - - return anchor + let xAfterPadding = center.x + padding.left - padding.right + let yAfterPadding = center.y + padding.top - padding.bottom + return CGPoint(x: xAfterPadding, y: yAfterPadding) } /// The map's camera padding diff --git a/Sources/MapboxMaps/Foundation/Camera/BasicCameraAnimator.swift b/Sources/MapboxMaps/Foundation/Camera/BasicCameraAnimator.swift index d4f8759baf7..b25208859a3 100644 --- a/Sources/MapboxMaps/Foundation/Camera/BasicCameraAnimator.swift +++ b/Sources/MapboxMaps/Foundation/Camera/BasicCameraAnimator.swift @@ -1,20 +1,6 @@ import UIKit import CoreLocation -public protocol CameraAnimator { - - /// Stops the animation in its tracks and calls any provided completion - func stopAnimation() - - /// The current state of the animation - var state: UIViewAnimatingState { get } -} - -/// Internal-facing protocol to represent camera animators -internal protocol CameraAnimatorInterface: CameraAnimator { - var currentCameraOptions: CameraOptions? { get } -} - // MARK: CameraAnimator Class public class BasicCameraAnimator: NSObject, CameraAnimator, CameraAnimatorInterface { diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift b/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift index 1444cb9375e..d8d53ee6375 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift @@ -1,6 +1,21 @@ import UIKit import Turf +public protocol CameraAnimator { + + /// Stops the animation in its tracks and calls any provided completion + func stopAnimation() + + /// The current state of the animation + var state: UIViewAnimatingState { get } +} + +/// Internal-facing protocol to represent camera animators +internal protocol CameraAnimatorInterface: CameraAnimator { + var currentCameraOptions: CameraOptions? { get } +} + + /// An object that manages a camera's view lifecycle. public class CameraManager { @@ -191,7 +206,6 @@ public class CameraManager { // Add completion cameraAnimator.addCompletion({ [weak self] (position) in - self?.internalAnimator = nil completion?(position) }) @@ -237,7 +251,6 @@ public class CameraManager { // Nil out the internalAnimator after `flyTo` finishes flyToAnimator.addCompletion { [weak self] (position) in // Call the developer-provided completion (if present) - self?.internalAnimator = nil completion?(position) } @@ -268,7 +281,6 @@ public class CameraManager { // Nil out the `internalAnimator` once the "ease to" finishes animator.addCompletion { [weak self] (position) in - self?.internalAnimator = nil completion?(position) } From 29009621399ffecc08cee909874133d14cd30236 Mon Sep 17 00:00:00 2001 From: Nishant Karajgikar Date: Tue, 27 Apr 2021 11:47:18 -0400 Subject: [PATCH 33/40] Fix optimizeBearing logic issue + remove constantAnchor for now --- .../Foundation/Camera/CameraTransition.swift | 22 ++----------------- .../Camera/CameraTransitionTests.swift | 10 +++++++++ 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraTransition.swift b/Sources/MapboxMaps/Foundation/Camera/CameraTransition.swift index b7bd49157d5..6ba156ffbb3 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraTransition.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraTransition.swift @@ -18,19 +18,6 @@ public struct CameraTransition { /// NOTE: Incompatible with concurrent center animations public var anchor: Change - /// Set this value in order to make bearing/zoom animations - /// honor a constant anchor throughout the transition - /// NOTE: Incompatible with concurrent center animations - public var constantAnchor: CGPoint? { - set { - anchor.fromValue = newValue ?? anchor.fromValue - anchor.toValue = newValue - } - get { - return anchor.fromValue == anchor.toValue ? anchor.fromValue : nil - } - } - /// Represents a change to the bearing of the map. public var bearing: Change @@ -74,7 +61,7 @@ public struct CameraTransition { padding: padding.toValue, anchor: anchor.toValue, zoom: zoom.toValue, - bearing: optimizedBearingToValue, + bearing: shouldOptimizeBearingPath ? optimizedBearingToValue : bearing.toValue, pitch: pitch.toValue) } @@ -90,17 +77,12 @@ public struct CameraTransition { internal var optimizedBearingToValue: CLLocationDirection? { - // If we should not optimize bearing transition, then return the original `toValue` - guard shouldOptimizeBearingPath else { - return bearing.toValue - } - // If `bearing.toValue` is nil, then return nil. guard let toBearing = bearing.toValue?.truncatingRemainder(dividingBy: 360.0) else { return nil } - let fromBearing = bearing.fromValue.truncatingRemainder(dividingBy: 360.0) + let fromBearing = bearing.fromValue // 180 degrees is the max the map should rotate, therefore if the difference between the end and start point is // more than 180 we need to go the opposite direction diff --git a/Tests/MapboxMapsTests/Foundation/Camera/CameraTransitionTests.swift b/Tests/MapboxMapsTests/Foundation/Camera/CameraTransitionTests.swift index 1adcbb77009..571eb592cef 100644 --- a/Tests/MapboxMapsTests/Foundation/Camera/CameraTransitionTests.swift +++ b/Tests/MapboxMapsTests/Foundation/Camera/CameraTransitionTests.swift @@ -55,6 +55,16 @@ class CameraTransitionTests: XCTestCase { optimizedBearing = cameraTransition.optimizedBearingToValue XCTAssertEqual(optimizedBearing, 90) } + + func testOptimizeBearingWhenStartBearingIsNegativeAndIsLesserThanMinus360() { + var optimizedBearing: CLLocationDirection? + + cameraTransition.bearing.fromValue = -560 + cameraTransition.bearing.toValue = 0 + + optimizedBearing = cameraTransition.optimizedBearingToValue + XCTAssertEqual(optimizedBearing, -360) + } func testOptimizeBearingHandlesNil() { var optimizedBearing: CLLocationDirection? From 62183b90eb487ecd612ff4ee1296434630a88d10 Mon Sep 17 00:00:00 2001 From: Nishant Karajgikar Date: Tue, 27 Apr 2021 11:51:18 -0400 Subject: [PATCH 34/40] Cleanup --- .../MapboxMaps/Foundation/BaseMapView.swift | 1 + .../Camera/BasicCameraAnimator.swift | 11 +- .../Foundation/Camera/CameraManager.swift | 139 +++++++++--------- .../Foundation/Camera/CameraTransition.swift | 16 +- .../Camera/CameraTransitionTests.swift | 7 +- .../Camera/FlyToAnimatorTests.swift | 2 +- 6 files changed, 90 insertions(+), 86 deletions(-) diff --git a/Sources/MapboxMaps/Foundation/BaseMapView.swift b/Sources/MapboxMaps/Foundation/BaseMapView.swift index e13910f7941..b0a70290e3d 100644 --- a/Sources/MapboxMaps/Foundation/BaseMapView.swift +++ b/Sources/MapboxMaps/Foundation/BaseMapView.swift @@ -6,6 +6,7 @@ import Turf // swiftlint:disable file_length internal typealias PendingAnimationCompletion = (completion: AnimationCompletion, animatingPosition: UIViewAnimatingPosition) +// swiftlint:disable force_cast internal class WeakCameraAnimatorSet { private let hashTable = NSHashTable.weakObjects() diff --git a/Sources/MapboxMaps/Foundation/Camera/BasicCameraAnimator.swift b/Sources/MapboxMaps/Foundation/Camera/BasicCameraAnimator.swift index b25208859a3..a404cdceebf 100644 --- a/Sources/MapboxMaps/Foundation/Camera/BasicCameraAnimator.swift +++ b/Sources/MapboxMaps/Foundation/Camera/BasicCameraAnimator.swift @@ -80,7 +80,7 @@ public class BasicCameraAnimator: NSObject, CameraAnimator, CameraAnimatorInterf // Set up the short lived camera view delegate.addViewToViewHeirarchy(cameraView) - var cameraTransition = CameraTransition(with: delegate.camera, initialAnchor: delegate.anchorAfterPadding()) + var cameraTransition = CameraTransition(cameraOptions: delegate.camera, initialAnchor: delegate.anchorAfterPadding()) animation(&cameraTransition) propertyAnimator.addAnimations { [weak cameraView] in @@ -98,7 +98,7 @@ public class BasicCameraAnimator: NSObject, CameraAnimator, CameraAnimatorInterf /// Starts the animation after a delay /// - Parameter delay: Delay (in seconds) after which the animation should start public func startAnimation(afterDelay delay: TimeInterval) { - delayedAnimationTimer = Timer.scheduledTimer(withTimeInterval: delay, repeats: false, block: { [weak self] (timer) in + delayedAnimationTimer = Timer.scheduledTimer(withTimeInterval: delay, repeats: false, block: { [weak self] (_) in self?.startAnimation() }) } @@ -127,10 +127,9 @@ public class BasicCameraAnimator: NSObject, CameraAnimator, CameraAnimatorInterf internal func wrapCompletion(_ completion: @escaping AnimationCompletion) -> (UIViewAnimatingPosition) -> Void { return { [weak self] animationPosition in - guard let self = self, let delegate = self.delegate else { return } - self.transition = nil // Clear out the transition being animated by this animator, - // since the animation is complete if we are here. - delegate.schedulePendingCompletion(forAnimator: self, completion: completion, animatingPosition: animationPosition) + guard let self = self else { return } + self.transition = nil // Clear out the transition being animated by this animator since the animation is complete if we are here. + self.delegate?.schedulePendingCompletion(forAnimator: self, completion: completion, animatingPosition: animationPosition) // Invalidate the delayed animation timer if it exists self.delayedAnimationTimer?.invalidate() diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift b/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift index d8d53ee6375..1dc15d609c5 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift @@ -2,10 +2,10 @@ import UIKit import Turf public protocol CameraAnimator { - + /// Stops the animation in its tracks and calls any provided completion func stopAnimation() - + /// The current state of the animation var state: UIViewAnimatingState { get } } @@ -15,13 +15,12 @@ internal protocol CameraAnimatorInterface: CameraAnimator { var currentCameraOptions: CameraOptions? { get } } - /// An object that manages a camera's view lifecycle. public class CameraManager { - + /// Used to set up camera specific configuration public internal(set) var mapCameraOptions: MapCameraOptions - + /// Used to update the map's camera options and pass them to the core Map. internal func updateMapCameraOptions(newOptions: MapCameraOptions) { let boundOptions = BoundOptions(__bounds: newOptions.restrictedCoordinateBounds ?? nil, @@ -32,22 +31,22 @@ public class CameraManager { mapView?.mapboxMap.__map.setBoundsFor(boundOptions) mapCameraOptions = newOptions } - + /// Internal camera animator used for animated transition internal var internalAnimator: CameraAnimator? - + /// May want to convert to an enum. fileprivate let northBearing: CGFloat = 0 - + internal weak var mapView: BaseMapView? - + public init(for mapView: BaseMapView, with mapCameraOptions: MapCameraOptions) { self.mapView = mapView self.mapCameraOptions = mapCameraOptions } - + // MARK: Camera creation - + /// Creates a new `Camera` object to fit a given array of coordinates. /// /// Note: This method does not set the map's camera to the new values. You must call @@ -58,22 +57,22 @@ public class CameraManager { assertionFailure("MapView is nil.") return CameraOptions() } - + let coordinateLocations = coordinates.map { CLLocation(latitude: $0.latitude, longitude: $0.longitude) } - + // Construct new camera options with current values let cameraOptions = MapboxCoreMaps.CameraOptions(mapView.cameraOptions) - + let defaultEdgeInsets = EdgeInsets(top: 0, left: 0, bottom: 0, right: 0) - + // Create a new camera options with adjusted values return CameraOptions(mapView.mapboxMap.__map.cameraForCoordinates( - forCoordinates: coordinateLocations, - padding: cameraOptions.__padding ?? defaultEdgeInsets, - bearing: cameraOptions.__bearing, - pitch: cameraOptions.__pitch)) + forCoordinates: coordinateLocations, + padding: cameraOptions.__padding ?? defaultEdgeInsets, + bearing: cameraOptions.__bearing, + pitch: cameraOptions.__pitch)) } - + /// Returns the camera that best fits the given coordinate bounds, with optional edge padding, bearing, and pitch values. /// - Parameters: /// - coordinateBounds: The coordinate bounds that will be displayed within the viewport. @@ -88,14 +87,14 @@ public class CameraManager { guard let mapView = mapView else { return CameraOptions() } - + return CameraOptions(mapView.mapboxMap.__map.cameraForCoordinateBounds( - for: coordinateBounds, - padding: edgePadding.toMBXEdgeInsetsValue(), - bearing: NSNumber(value: Float(bearing)), - pitch: NSNumber(value: Float(pitch)))) + for: coordinateBounds, + padding: edgePadding.toMBXEdgeInsetsValue(), + bearing: NSNumber(value: Float(bearing)), + pitch: NSNumber(value: Float(pitch)))) } - + /// Returns the camera that best fits the given geometry, with optional edge padding, bearing, and pitch values. /// - Parameters: /// - geometry: The geoemtry that will be displayed within the viewport. @@ -111,14 +110,14 @@ public class CameraManager { assertionFailure("MapView is nil.") return CameraOptions() } - + return CameraOptions(mapView.mapboxMap.__map.cameraForGeometry( - for: MBXGeometry(geometry: geometry), - padding: edgePadding.toMBXEdgeInsetsValue(), - bearing: NSNumber(value: Float(bearing)), - pitch: NSNumber(value: Float(pitch)))) + for: MBXGeometry(geometry: geometry), + padding: edgePadding.toMBXEdgeInsetsValue(), + bearing: NSNumber(value: Float(bearing)), + pitch: NSNumber(value: Float(pitch)))) } - + /// Returns the coordinate bounds for a given `Camera` object's viewport. /// - Parameter camera: The camera that the coordinate bounds will be returned for. /// - Returns: `CoordinateBounds` for the given `Camera` @@ -129,9 +128,9 @@ public class CameraManager { } return mapView.mapboxMap.__map.coordinateBoundsForCamera(forCamera: MapboxCoreMaps.CameraOptions(camera)) } - + // MARK: Setting a new camera - + /// Transition the map's viewport to a new camera. /// - Parameters: /// - targetCamera: The target camera to transition to. @@ -146,21 +145,21 @@ public class CameraManager { assertionFailure("MapView is nil.") return } - + internalAnimator?.stopAnimation() - + let clampedCamera = CameraOptions(center: targetCamera.center, padding: targetCamera.padding, anchor: targetCamera.anchor, zoom: targetCamera.zoom?.clamped(to: mapCameraOptions.minimumZoomLevel...mapCameraOptions.maximumZoomLevel), bearing: targetCamera.bearing, pitch: targetCamera.pitch?.clamped(to: mapCameraOptions.minimumPitch...mapCameraOptions.maximumPitch)) - + // Return early if the cameraView's camera is already at `clampedCamera` guard mapView.cameraOptions != clampedCamera else { return } - + if animated && duration > 0 { let animation = { (transition: inout CameraTransition) in transition.center.toValue = clampedCamera.center @@ -175,7 +174,7 @@ public class CameraManager { mapView.mapboxMap.updateCamera(with: clampedCamera) } } - + /// Interrupts all `active` animation. /// The camera remains at the last point before the cancel request was invoked, i.e., /// the camera is not reset or fast-forwarded to the end of the transition. @@ -186,36 +185,38 @@ public class CameraManager { animator.stopAnimation() } } - + /// Private function to perform camera animation /// - Parameters: /// - duration: If animated, how long the animation takes /// - animation: closure to perform /// - completion: animation block called on completion - fileprivate func performCameraAnimation(duration: TimeInterval, animation: @escaping (inout CameraTransition) -> Void, completion: ((UIViewAnimatingPosition) -> Void)? = nil) { - + fileprivate func performCameraAnimation(duration: TimeInterval, + animation: @escaping (inout CameraTransition) -> Void, + completion: ((UIViewAnimatingPosition) -> Void)? = nil) { + // Stop previously running animations internalAnimator?.stopAnimation() - + // Make a new camera animator for the new properties - + let cameraAnimator = makeAnimator(duration: duration, - curve: .easeOut, - animationOwner: .custom(id: "com.mapbox.maps.cameraManager"), - animations: animation) - + curve: .easeOut, + animationOwner: .custom(id: "com.mapbox.maps.cameraManager"), + animations: animation) + // Add completion - cameraAnimator.addCompletion({ [weak self] (position) in + cameraAnimator.addCompletion({ (position) in completion?(position) }) - + // Start animation cameraAnimator.startAnimation() - + // Store the animator in order to keep it alive internalAnimator = cameraAnimator } - + /// Moves the viewpoint to a different location using a transition animation that /// evokes powered flight and an optional transition duration and timing function /// It seamlessly incorporates zooming and panning to help @@ -230,7 +231,7 @@ public class CameraManager { public func fly(to camera: CameraOptions, duration: TimeInterval? = nil, completion: AnimationCompletion? = nil) -> CameraAnimator? { - + guard let mapView = mapView, let flyToAnimator = FlyToCameraAnimator( inital: mapView.cameraOptions, @@ -242,23 +243,23 @@ public class CameraManager { Log.warning(forMessage: "Unable to start fly-to animation", category: "CameraManager") return nil } - + // Stop the `internalAnimator` before beginning a `flyTo` internalAnimator?.stopAnimation() - + mapView.addCameraAnimator(flyToAnimator) - + // Nil out the internalAnimator after `flyTo` finishes - flyToAnimator.addCompletion { [weak self] (position) in + flyToAnimator.addCompletion { (position) in // Call the developer-provided completion (if present) completion?(position) } - + flyToAnimator.startAnimation() internalAnimator = flyToAnimator return internalAnimator } - + /// Ease the camera to a destination /// - Parameters: /// - camera: the target camera after animation @@ -266,10 +267,12 @@ public class CameraManager { /// - completion: completion to be called after animation /// - Returns: An instance of `CameraAnimatorProtocol` which can be interrupted if necessary @discardableResult - public func ease(to camera: CameraOptions, duration: TimeInterval, completion: AnimationCompletion? = nil) -> CameraAnimator? { - + public func ease(to camera: CameraOptions, + duration: TimeInterval, + completion: AnimationCompletion? = nil) -> CameraAnimator? { + internalAnimator?.stopAnimation() - + let animator = makeAnimator(duration: duration, curve: .easeInOut) { (transition) in transition.center.toValue = camera.center transition.padding.toValue = camera.padding @@ -278,25 +281,25 @@ public class CameraManager { transition.bearing.toValue = camera.bearing transition.pitch.toValue = camera.pitch } - + // Nil out the `internalAnimator` once the "ease to" finishes - animator.addCompletion { [weak self] (position) in + animator.addCompletion { (position) in completion?(position) } - + animator.startAnimation() internalAnimator = animator - + return internalAnimator } - + } fileprivate extension CoordinateBounds { func contains(_ coordinates: [CLLocationCoordinate2D]) -> Bool { let latitudeRange = southwest.latitude...northeast.latitude let longitudeRange = southwest.longitude...northeast.longitude - + for coordinate in coordinates { if latitudeRange.contains(coordinate.latitude) || longitudeRange.contains(coordinate.longitude) { return true diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraTransition.swift b/Sources/MapboxMaps/Foundation/Camera/CameraTransition.swift index 6ba156ffbb3..bbf8cb52553 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraTransition.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraTransition.swift @@ -38,14 +38,14 @@ public struct CameraTransition { } } - internal init(with renderedCameraOptions: CameraOptions, initialAnchor: CGPoint) { - - guard let renderedCenter = renderedCameraOptions.center, - let renderedZoom = renderedCameraOptions.zoom, - let renderedPadding = renderedCameraOptions.padding, - let renderedPitch = renderedCameraOptions.pitch, - let renderedBearing = renderedCameraOptions.bearing else { - fatalError("Values in rendered CameraOptions cannot be nil") + internal init(cameraOptions: CameraOptions, initialAnchor: CGPoint) { + + guard let renderedCenter = cameraOptions.center, + let renderedZoom = cameraOptions.zoom, + let renderedPadding = cameraOptions.padding, + let renderedPitch = cameraOptions.pitch, + let renderedBearing = cameraOptions.bearing else { + fatalError("Values in CameraOptions cannot be nil") } center = Change(fromValue: renderedCenter) diff --git a/Tests/MapboxMapsTests/Foundation/Camera/CameraTransitionTests.swift b/Tests/MapboxMapsTests/Foundation/Camera/CameraTransitionTests.swift index 571eb592cef..2a86a87364d 100644 --- a/Tests/MapboxMapsTests/Foundation/Camera/CameraTransitionTests.swift +++ b/Tests/MapboxMapsTests/Foundation/Camera/CameraTransitionTests.swift @@ -3,8 +3,9 @@ import XCTest class CameraTransitionTests: XCTestCase { - var cameraTransition = CameraTransition(with: cameraOptionsTestValue, - initialAnchor: .zero) + var cameraTransition = CameraTransition( + cameraOptions: cameraOptionsTestValue, + initialAnchor: .zero) func testOptimizeBearingClockwise() { let startBearing = 0.0 @@ -55,7 +56,7 @@ class CameraTransitionTests: XCTestCase { optimizedBearing = cameraTransition.optimizedBearingToValue XCTAssertEqual(optimizedBearing, 90) } - + func testOptimizeBearingWhenStartBearingIsNegativeAndIsLesserThanMinus360() { var optimizedBearing: CLLocationDirection? diff --git a/Tests/MapboxMapsTests/Foundation/Camera/FlyToAnimatorTests.swift b/Tests/MapboxMapsTests/Foundation/Camera/FlyToAnimatorTests.swift index b2c41db7492..b43fc5e393c 100644 --- a/Tests/MapboxMapsTests/Foundation/Camera/FlyToAnimatorTests.swift +++ b/Tests/MapboxMapsTests/Foundation/Camera/FlyToAnimatorTests.swift @@ -25,7 +25,7 @@ final class FlyToAnimatorTests: XCTestCase { let duration: TimeInterval = 10 var flyToAnimator: FlyToCameraAnimator! - var cameraAnimatorDelegate: CameraAnimatorDelegateMock! + weak var cameraAnimatorDelegate: CameraAnimatorDelegateMock! override func setUp() { super.setUp() From df83e057beee854d925906579433f8a654c0c3ae Mon Sep 17 00:00:00 2001 From: Nishant Karajgikar Date: Tue, 27 Apr 2021 11:58:15 -0400 Subject: [PATCH 35/40] Remove Hashable conformances to UIKit types --- .../Foundation/Extensions/Core/MBXEdgeInsets.swift | 9 +-------- .../MapboxMaps/Foundation/Extensions/CoreGraphics.swift | 8 +------- .../MapboxMaps/Foundation/Extensions/CoreLocation.swift | 8 +------- .../Foundation/Camera/FlyToAnimatorTests.swift | 2 +- 4 files changed, 4 insertions(+), 23 deletions(-) diff --git a/Sources/MapboxMaps/Foundation/Extensions/Core/MBXEdgeInsets.swift b/Sources/MapboxMaps/Foundation/Extensions/Core/MBXEdgeInsets.swift index 8481bd047ad..9947d750808 100644 --- a/Sources/MapboxMaps/Foundation/Extensions/Core/MBXEdgeInsets.swift +++ b/Sources/MapboxMaps/Foundation/Extensions/Core/MBXEdgeInsets.swift @@ -9,18 +9,11 @@ internal extension EdgeInsets { } } -extension UIEdgeInsets: Hashable { +extension UIEdgeInsets { func toMBXEdgeInsetsValue() -> EdgeInsets { return EdgeInsets(top: Double(self.top), left: Double(self.left), bottom: Double(self.bottom), right: Double(self.right)) } - - public func hash(into hasher: inout Hasher) { - hasher.combine(top) - hasher.combine(bottom) - hasher.combine(left) - hasher.combine(right) - } } diff --git a/Sources/MapboxMaps/Foundation/Extensions/CoreGraphics.swift b/Sources/MapboxMaps/Foundation/Extensions/CoreGraphics.swift index 5c8a748ca7d..0ba9d7b3669 100644 --- a/Sources/MapboxMaps/Foundation/Extensions/CoreGraphics.swift +++ b/Sources/MapboxMaps/Foundation/Extensions/CoreGraphics.swift @@ -4,7 +4,7 @@ import CoreGraphics import MapboxCoreMaps // MARK: - CGPoint -extension CGPoint: Hashable { +extension CGPoint { /// Converts a `CGPoint` to an internal `ScreenCoordinate` type. internal var screenCoordinate: ScreenCoordinate { @@ -24,12 +24,6 @@ extension CGPoint: Hashable { return CGPoint(x: origin.x + fraction * (destination.x - origin.x), y: origin.y + fraction * (destination.y - origin.y)) } - - /// Hashable conformance - public func hash(into hasher: inout Hasher) { - hasher.combine(x) - hasher.combine(y) - } } // MARK: - CGFloat diff --git a/Sources/MapboxMaps/Foundation/Extensions/CoreLocation.swift b/Sources/MapboxMaps/Foundation/Extensions/CoreLocation.swift index 1979207399e..836aa9f970e 100644 --- a/Sources/MapboxMaps/Foundation/Extensions/CoreLocation.swift +++ b/Sources/MapboxMaps/Foundation/Extensions/CoreLocation.swift @@ -4,13 +4,7 @@ import CoreGraphics import MapboxCoreMaps // MARK: - CLLocationCoordinate2D -extension CLLocationCoordinate2D: Hashable { - - /// Hashable conformance - public func hash(into hasher: inout Hasher) { - hasher.combine(latitude) - hasher.combine(longitude) - } +extension CLLocationCoordinate2D { /// Converts a `CLLocationCoordinate` to a `CLLocation`. internal var location: CLLocation { diff --git a/Tests/MapboxMapsTests/Foundation/Camera/FlyToAnimatorTests.swift b/Tests/MapboxMapsTests/Foundation/Camera/FlyToAnimatorTests.swift index b43fc5e393c..b2c41db7492 100644 --- a/Tests/MapboxMapsTests/Foundation/Camera/FlyToAnimatorTests.swift +++ b/Tests/MapboxMapsTests/Foundation/Camera/FlyToAnimatorTests.swift @@ -25,7 +25,7 @@ final class FlyToAnimatorTests: XCTestCase { let duration: TimeInterval = 10 var flyToAnimator: FlyToCameraAnimator! - weak var cameraAnimatorDelegate: CameraAnimatorDelegateMock! + var cameraAnimatorDelegate: CameraAnimatorDelegateMock! override func setUp() { super.setUp() From 85ced780e69a815d0330631a0e5594546136dffd Mon Sep 17 00:00:00 2001 From: Nishant Karajgikar Date: Tue, 27 Apr 2021 11:59:46 -0400 Subject: [PATCH 36/40] fix lint --- .../Foundation/Camera/CameraManager.swift | 94 +++++++++---------- .../Camera/FlyToAnimatorTests.swift | 2 + 2 files changed, 49 insertions(+), 47 deletions(-) diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift b/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift index 1dc15d609c5..67a6f08d6ff 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift @@ -2,10 +2,10 @@ import UIKit import Turf public protocol CameraAnimator { - + /// Stops the animation in its tracks and calls any provided completion func stopAnimation() - + /// The current state of the animation var state: UIViewAnimatingState { get } } @@ -17,10 +17,10 @@ internal protocol CameraAnimatorInterface: CameraAnimator { /// An object that manages a camera's view lifecycle. public class CameraManager { - + /// Used to set up camera specific configuration public internal(set) var mapCameraOptions: MapCameraOptions - + /// Used to update the map's camera options and pass them to the core Map. internal func updateMapCameraOptions(newOptions: MapCameraOptions) { let boundOptions = BoundOptions(__bounds: newOptions.restrictedCoordinateBounds ?? nil, @@ -31,22 +31,22 @@ public class CameraManager { mapView?.mapboxMap.__map.setBoundsFor(boundOptions) mapCameraOptions = newOptions } - + /// Internal camera animator used for animated transition internal var internalAnimator: CameraAnimator? - + /// May want to convert to an enum. fileprivate let northBearing: CGFloat = 0 - + internal weak var mapView: BaseMapView? - + public init(for mapView: BaseMapView, with mapCameraOptions: MapCameraOptions) { self.mapView = mapView self.mapCameraOptions = mapCameraOptions } - + // MARK: Camera creation - + /// Creates a new `Camera` object to fit a given array of coordinates. /// /// Note: This method does not set the map's camera to the new values. You must call @@ -57,14 +57,14 @@ public class CameraManager { assertionFailure("MapView is nil.") return CameraOptions() } - + let coordinateLocations = coordinates.map { CLLocation(latitude: $0.latitude, longitude: $0.longitude) } - + // Construct new camera options with current values let cameraOptions = MapboxCoreMaps.CameraOptions(mapView.cameraOptions) - + let defaultEdgeInsets = EdgeInsets(top: 0, left: 0, bottom: 0, right: 0) - + // Create a new camera options with adjusted values return CameraOptions(mapView.mapboxMap.__map.cameraForCoordinates( forCoordinates: coordinateLocations, @@ -72,7 +72,7 @@ public class CameraManager { bearing: cameraOptions.__bearing, pitch: cameraOptions.__pitch)) } - + /// Returns the camera that best fits the given coordinate bounds, with optional edge padding, bearing, and pitch values. /// - Parameters: /// - coordinateBounds: The coordinate bounds that will be displayed within the viewport. @@ -87,14 +87,14 @@ public class CameraManager { guard let mapView = mapView else { return CameraOptions() } - + return CameraOptions(mapView.mapboxMap.__map.cameraForCoordinateBounds( for: coordinateBounds, padding: edgePadding.toMBXEdgeInsetsValue(), bearing: NSNumber(value: Float(bearing)), pitch: NSNumber(value: Float(pitch)))) } - + /// Returns the camera that best fits the given geometry, with optional edge padding, bearing, and pitch values. /// - Parameters: /// - geometry: The geoemtry that will be displayed within the viewport. @@ -110,14 +110,14 @@ public class CameraManager { assertionFailure("MapView is nil.") return CameraOptions() } - + return CameraOptions(mapView.mapboxMap.__map.cameraForGeometry( for: MBXGeometry(geometry: geometry), padding: edgePadding.toMBXEdgeInsetsValue(), bearing: NSNumber(value: Float(bearing)), pitch: NSNumber(value: Float(pitch)))) } - + /// Returns the coordinate bounds for a given `Camera` object's viewport. /// - Parameter camera: The camera that the coordinate bounds will be returned for. /// - Returns: `CoordinateBounds` for the given `Camera` @@ -128,9 +128,9 @@ public class CameraManager { } return mapView.mapboxMap.__map.coordinateBoundsForCamera(forCamera: MapboxCoreMaps.CameraOptions(camera)) } - + // MARK: Setting a new camera - + /// Transition the map's viewport to a new camera. /// - Parameters: /// - targetCamera: The target camera to transition to. @@ -145,21 +145,21 @@ public class CameraManager { assertionFailure("MapView is nil.") return } - + internalAnimator?.stopAnimation() - + let clampedCamera = CameraOptions(center: targetCamera.center, padding: targetCamera.padding, anchor: targetCamera.anchor, zoom: targetCamera.zoom?.clamped(to: mapCameraOptions.minimumZoomLevel...mapCameraOptions.maximumZoomLevel), bearing: targetCamera.bearing, pitch: targetCamera.pitch?.clamped(to: mapCameraOptions.minimumPitch...mapCameraOptions.maximumPitch)) - + // Return early if the cameraView's camera is already at `clampedCamera` guard mapView.cameraOptions != clampedCamera else { return } - + if animated && duration > 0 { let animation = { (transition: inout CameraTransition) in transition.center.toValue = clampedCamera.center @@ -174,7 +174,7 @@ public class CameraManager { mapView.mapboxMap.updateCamera(with: clampedCamera) } } - + /// Interrupts all `active` animation. /// The camera remains at the last point before the cancel request was invoked, i.e., /// the camera is not reset or fast-forwarded to the end of the transition. @@ -185,7 +185,7 @@ public class CameraManager { animator.stopAnimation() } } - + /// Private function to perform camera animation /// - Parameters: /// - duration: If animated, how long the animation takes @@ -194,29 +194,29 @@ public class CameraManager { fileprivate func performCameraAnimation(duration: TimeInterval, animation: @escaping (inout CameraTransition) -> Void, completion: ((UIViewAnimatingPosition) -> Void)? = nil) { - + // Stop previously running animations internalAnimator?.stopAnimation() - + // Make a new camera animator for the new properties - + let cameraAnimator = makeAnimator(duration: duration, curve: .easeOut, animationOwner: .custom(id: "com.mapbox.maps.cameraManager"), animations: animation) - + // Add completion cameraAnimator.addCompletion({ (position) in completion?(position) }) - + // Start animation cameraAnimator.startAnimation() - + // Store the animator in order to keep it alive internalAnimator = cameraAnimator } - + /// Moves the viewpoint to a different location using a transition animation that /// evokes powered flight and an optional transition duration and timing function /// It seamlessly incorporates zooming and panning to help @@ -231,7 +231,7 @@ public class CameraManager { public func fly(to camera: CameraOptions, duration: TimeInterval? = nil, completion: AnimationCompletion? = nil) -> CameraAnimator? { - + guard let mapView = mapView, let flyToAnimator = FlyToCameraAnimator( inital: mapView.cameraOptions, @@ -243,23 +243,23 @@ public class CameraManager { Log.warning(forMessage: "Unable to start fly-to animation", category: "CameraManager") return nil } - + // Stop the `internalAnimator` before beginning a `flyTo` internalAnimator?.stopAnimation() - + mapView.addCameraAnimator(flyToAnimator) - + // Nil out the internalAnimator after `flyTo` finishes flyToAnimator.addCompletion { (position) in // Call the developer-provided completion (if present) completion?(position) } - + flyToAnimator.startAnimation() internalAnimator = flyToAnimator return internalAnimator } - + /// Ease the camera to a destination /// - Parameters: /// - camera: the target camera after animation @@ -270,9 +270,9 @@ public class CameraManager { public func ease(to camera: CameraOptions, duration: TimeInterval, completion: AnimationCompletion? = nil) -> CameraAnimator? { - + internalAnimator?.stopAnimation() - + let animator = makeAnimator(duration: duration, curve: .easeInOut) { (transition) in transition.center.toValue = camera.center transition.padding.toValue = camera.padding @@ -281,25 +281,25 @@ public class CameraManager { transition.bearing.toValue = camera.bearing transition.pitch.toValue = camera.pitch } - + // Nil out the `internalAnimator` once the "ease to" finishes animator.addCompletion { (position) in completion?(position) } - + animator.startAnimation() internalAnimator = animator - + return internalAnimator } - + } fileprivate extension CoordinateBounds { func contains(_ coordinates: [CLLocationCoordinate2D]) -> Bool { let latitudeRange = southwest.latitude...northeast.latitude let longitudeRange = southwest.longitude...northeast.longitude - + for coordinate in coordinates { if latitudeRange.contains(coordinate.latitude) || longitudeRange.contains(coordinate.longitude) { return true diff --git a/Tests/MapboxMapsTests/Foundation/Camera/FlyToAnimatorTests.swift b/Tests/MapboxMapsTests/Foundation/Camera/FlyToAnimatorTests.swift index b2c41db7492..b207b06acbd 100644 --- a/Tests/MapboxMapsTests/Foundation/Camera/FlyToAnimatorTests.swift +++ b/Tests/MapboxMapsTests/Foundation/Camera/FlyToAnimatorTests.swift @@ -25,6 +25,8 @@ final class FlyToAnimatorTests: XCTestCase { let duration: TimeInterval = 10 var flyToAnimator: FlyToCameraAnimator! + + // swiftlint:disable weak_delegate var cameraAnimatorDelegate: CameraAnimatorDelegateMock! override func setUp() { From 15e900be9ad431161b87a93f8693ffdff43c9cae Mon Sep 17 00:00:00 2001 From: Nishant Karajgikar Date: Tue, 27 Apr 2021 12:03:17 -0400 Subject: [PATCH 37/40] Put CameraAnimatorsWeakSet in separate file --- Mapbox/MapboxMaps.xcodeproj/project.pbxproj | 4 ++++ .../MapboxMaps/Foundation/BaseMapView.swift | 21 ------------------ .../Camera/WeakCameraAnimatorSet.swift | 22 +++++++++++++++++++ 3 files changed, 26 insertions(+), 21 deletions(-) create mode 100644 Sources/MapboxMaps/Foundation/Camera/WeakCameraAnimatorSet.swift diff --git a/Mapbox/MapboxMaps.xcodeproj/project.pbxproj b/Mapbox/MapboxMaps.xcodeproj/project.pbxproj index 3aac87ed886..fb910a17aef 100644 --- a/Mapbox/MapboxMaps.xcodeproj/project.pbxproj +++ b/Mapbox/MapboxMaps.xcodeproj/project.pbxproj @@ -33,6 +33,7 @@ 07D7150425798CD70025BF61 /* UIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07D7150325798CD70025BF61 /* UIImage.swift */; }; 07E816DA256D72E500ACFA73 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 07182014256C1F6100F22489 /* Assets.xcassets */; }; 0C01C0B925486E7200E4AA46 /* ExpressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C01C08825486E6100E4AA46 /* ExpressionTests.swift */; }; + 0C088AC026386D2700107B5E /* WeakCameraAnimatorSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C088ABF26386D2700107B5E /* WeakCameraAnimatorSet.swift */; }; 0C1AF560244E47C1008D2A10 /* GestureOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C1AF559244DF4DF008D2A10 /* GestureOptions.swift */; }; 0C26425524EECD14001FE2E3 /* AllExpressions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C26425424EECD14001FE2E3 /* AllExpressions.swift */; }; 0C2CD9F625D19D75006D068F /* OptionsIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C2CD9E225D19D30006D068F /* OptionsIntegrationTests.swift */; }; @@ -427,6 +428,7 @@ 07EEFA8C25018B8800352933 /* AnnotationManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnnotationManagerTests.swift; sourceTree = ""; }; 0C01C08825486E6100E4AA46 /* ExpressionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpressionTests.swift; sourceTree = ""; }; 0C07948D2452331A0006A9C4 /* MapCameraOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapCameraOptions.swift; sourceTree = ""; }; + 0C088ABF26386D2700107B5E /* WeakCameraAnimatorSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeakCameraAnimatorSet.swift; sourceTree = ""; }; 0C1AF559244DF4DF008D2A10 /* GestureOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GestureOptions.swift; sourceTree = ""; }; 0C26425424EECD14001FE2E3 /* AllExpressions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AllExpressions.swift; sourceTree = ""; }; 0C2CD9E225D19D30006D068F /* OptionsIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionsIntegrationTests.swift; sourceTree = ""; }; @@ -1236,6 +1238,7 @@ 1F4B7D7A2464C36E00745F05 /* Camera */ = { isa = PBXGroup; children = ( + 0C088ABF26386D2700107B5E /* WeakCameraAnimatorSet.swift */, 0C350D81263278090090FA74 /* BasicCameraAnimator.swift */, 0C350D82263278090090FA74 /* CameraTransition.swift */, 0C350D80263278090090FA74 /* FlyToCameraAnimator.swift */, @@ -1979,6 +1982,7 @@ 2B8637E62463F36400698135 /* DistanceFormatter.swift in Sources */, CA0C426926028ED30054D9D0 /* AnnotationOptions.swift in Sources */, 0C708F2F24EB1EE2003CE791 /* RasterLayer.swift in Sources */, + 0C088AC026386D2700107B5E /* WeakCameraAnimatorSet.swift in Sources */, 0C35750225DD8D960085D775 /* StyleErrors.swift in Sources */, 0CE3D1B425816BD6000585A2 /* MapView+Supportable.swift in Sources */, 0C9DE369252C263600880CC8 /* GeoJSONSourceData.swift in Sources */, diff --git a/Sources/MapboxMaps/Foundation/BaseMapView.swift b/Sources/MapboxMaps/Foundation/BaseMapView.swift index b0a70290e3d..017e5feb24f 100644 --- a/Sources/MapboxMaps/Foundation/BaseMapView.swift +++ b/Sources/MapboxMaps/Foundation/BaseMapView.swift @@ -6,27 +6,6 @@ import Turf // swiftlint:disable file_length internal typealias PendingAnimationCompletion = (completion: AnimationCompletion, animatingPosition: UIViewAnimatingPosition) -// swiftlint:disable force_cast -internal class WeakCameraAnimatorSet { - private let hashTable = NSHashTable.weakObjects() - - internal func add(_ object: CameraAnimatorInterface) { - hashTable.add((object as! NSObject)) - } - - internal func remove(_ object: CameraAnimatorInterface) { - hashTable.remove((object as! NSObject)) - } - - internal func removeAll() { - hashTable.removeAllObjects() - } - - internal var allObjects: [CameraAnimatorInterface] { - hashTable.allObjects.map { $0 as! CameraAnimatorInterface } - } -} - open class BaseMapView: UIView { // mapbox map depends on MapInitOptions, which is not available until diff --git a/Sources/MapboxMaps/Foundation/Camera/WeakCameraAnimatorSet.swift b/Sources/MapboxMaps/Foundation/Camera/WeakCameraAnimatorSet.swift new file mode 100644 index 00000000000..2f12152608b --- /dev/null +++ b/Sources/MapboxMaps/Foundation/Camera/WeakCameraAnimatorSet.swift @@ -0,0 +1,22 @@ +import Foundation + +// swiftlint:disable force_cast +internal class WeakCameraAnimatorSet { + private let hashTable = NSHashTable.weakObjects() + + internal func add(_ object: CameraAnimatorInterface) { + hashTable.add((object as! NSObject)) + } + + internal func remove(_ object: CameraAnimatorInterface) { + hashTable.remove((object as! NSObject)) + } + + internal func removeAll() { + hashTable.removeAllObjects() + } + + internal var allObjects: [CameraAnimatorInterface] { + hashTable.allObjects.map { $0 as! CameraAnimatorInterface } + } +} From 627d13719d82c46198bfe6284e3cce5daea3d707 Mon Sep 17 00:00:00 2001 From: Nishant Karajgikar Date: Tue, 27 Apr 2021 12:08:48 -0400 Subject: [PATCH 38/40] Move CameraAnimators computed var to CameraManager --- Sources/MapboxMaps/Foundation/BaseMapView.swift | 11 ++++++----- .../MapboxMaps/Foundation/Camera/CameraManager.swift | 9 +++++++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/Sources/MapboxMaps/Foundation/BaseMapView.swift b/Sources/MapboxMaps/Foundation/BaseMapView.swift index 017e5feb24f..44ea640a694 100644 --- a/Sources/MapboxMaps/Foundation/BaseMapView.swift +++ b/Sources/MapboxMaps/Foundation/BaseMapView.swift @@ -33,12 +33,8 @@ open class BaseMapView: UIView { /// Pointer HashTable for holding camera animators private var cameraAnimatorsSet = WeakCameraAnimatorSet() - internal func addCameraAnimator(_ cameraAnimator: CameraAnimatorInterface) { - cameraAnimatorsSet.add(cameraAnimator) - } - /// List of animators currently alive - public var cameraAnimators: [CameraAnimator] { + internal var cameraAnimators: [CameraAnimator] { return cameraAnimatorsSet.allObjects } @@ -248,6 +244,11 @@ open class BaseMapView: UIView { } } + // Add an animator to the `cameraAnimatorsSet` + internal func addCameraAnimator(_ cameraAnimator: CameraAnimatorInterface) { + cameraAnimatorsSet.add(cameraAnimator) + } + func updateDisplayLinkPreferredFramesPerSecond() { if let displayLink = displayLink { diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift b/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift index 67a6f08d6ff..f7e2324ce42 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift @@ -21,6 +21,15 @@ public class CameraManager { /// Used to set up camera specific configuration public internal(set) var mapCameraOptions: MapCameraOptions + /// List of animators currently alive + public var cameraAnimators: [CameraAnimator] { + guard let mapView = mapView else { + return [] + } + + return mapView.cameraAnimators + } + /// Used to update the map's camera options and pass them to the core Map. internal func updateMapCameraOptions(newOptions: MapCameraOptions) { let boundOptions = BoundOptions(__bounds: newOptions.restrictedCoordinateBounds ?? nil, From ef5a8ef3f010a281f1f6710f15cd308f3210b8e8 Mon Sep 17 00:00:00 2001 From: Nishant Karajgikar Date: Tue, 27 Apr 2021 13:17:45 -0400 Subject: [PATCH 39/40] Add FlyToAnimatorTests --- Mapbox/MapboxMaps.xcodeproj/project.pbxproj | 4 + .../Camera/FlyToCameraAnimator.swift | 10 ++- .../Foundation/Camera/FlyToInterpolator.swift | 1 - .../MapboxMaps/Foundation/DateProvider.swift | 13 +++ .../Camera/FlyToAnimatorTests.swift | 85 +++++++++++++++++-- 5 files changed, 104 insertions(+), 9 deletions(-) create mode 100644 Sources/MapboxMaps/Foundation/DateProvider.swift diff --git a/Mapbox/MapboxMaps.xcodeproj/project.pbxproj b/Mapbox/MapboxMaps.xcodeproj/project.pbxproj index fb910a17aef..b431f9e7b2d 100644 --- a/Mapbox/MapboxMaps.xcodeproj/project.pbxproj +++ b/Mapbox/MapboxMaps.xcodeproj/project.pbxproj @@ -34,6 +34,7 @@ 07E816DA256D72E500ACFA73 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 07182014256C1F6100F22489 /* Assets.xcassets */; }; 0C01C0B925486E7200E4AA46 /* ExpressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C01C08825486E6100E4AA46 /* ExpressionTests.swift */; }; 0C088AC026386D2700107B5E /* WeakCameraAnimatorSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C088ABF26386D2700107B5E /* WeakCameraAnimatorSet.swift */; }; + 0C088AC526387EA400107B5E /* DateProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C088AC426387EA400107B5E /* DateProvider.swift */; }; 0C1AF560244E47C1008D2A10 /* GestureOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C1AF559244DF4DF008D2A10 /* GestureOptions.swift */; }; 0C26425524EECD14001FE2E3 /* AllExpressions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C26425424EECD14001FE2E3 /* AllExpressions.swift */; }; 0C2CD9F625D19D75006D068F /* OptionsIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C2CD9E225D19D30006D068F /* OptionsIntegrationTests.swift */; }; @@ -429,6 +430,7 @@ 0C01C08825486E6100E4AA46 /* ExpressionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpressionTests.swift; sourceTree = ""; }; 0C07948D2452331A0006A9C4 /* MapCameraOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapCameraOptions.swift; sourceTree = ""; }; 0C088ABF26386D2700107B5E /* WeakCameraAnimatorSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeakCameraAnimatorSet.swift; sourceTree = ""; }; + 0C088AC426387EA400107B5E /* DateProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateProvider.swift; sourceTree = ""; }; 0C1AF559244DF4DF008D2A10 /* GestureOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GestureOptions.swift; sourceTree = ""; }; 0C26425424EECD14001FE2E3 /* AllExpressions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AllExpressions.swift; sourceTree = ""; }; 0C2CD9E225D19D30006D068F /* OptionsIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionsIntegrationTests.swift; sourceTree = ""; }; @@ -1163,6 +1165,7 @@ 1F11C1102421B05600F8397B /* MapboxMapsFoundation */ = { isa = PBXGroup; children = ( + 0C088AC426387EA400107B5E /* DateProvider.swift */, B521FC92262F626900B9A446 /* MapboxMap.swift */, B529341A2624E9D6003B181C /* DelegatingMapClient.swift */, B529341B2624E9D6003B181C /* DelegatingObserver.swift */, @@ -1977,6 +1980,7 @@ B5B55D4C260E4D2900EBB589 /* MBXEdgeInsets.swift in Sources */, 0C479F90251CEC340025EC4F /* Snapshotter.swift in Sources */, 0CD62F182458865A006421D1 /* CameraView.swift in Sources */, + 0C088AC526387EA400107B5E /* DateProvider.swift in Sources */, B5B55D47260E4D1500EBB589 /* CameraManager+CameraAnimatorDelegate.swift in Sources */, 07BA35CE251578DD003E1B55 /* AnnotationInteractionDelegate.swift in Sources */, 2B8637E62463F36400698135 /* DistanceFormatter.swift in Sources */, diff --git a/Sources/MapboxMaps/Foundation/Camera/FlyToCameraAnimator.swift b/Sources/MapboxMaps/Foundation/Camera/FlyToCameraAnimator.swift index ae7c55416c4..3f2f5d0d683 100644 --- a/Sources/MapboxMaps/Foundation/Camera/FlyToCameraAnimator.swift +++ b/Sources/MapboxMaps/Foundation/Camera/FlyToCameraAnimator.swift @@ -18,12 +18,15 @@ public class FlyToCameraAnimator: NSObject, CameraAnimator, CameraAnimatorInterf private var completionBlocks = [AnimationCompletion]() + private let dateProvider: DateProvider + internal init?(inital: CameraOptions, final: CameraOptions, owner: AnimationOwner, duration: TimeInterval? = nil, mapSize: CGSize, - delegate: CameraAnimatorDelegate) { + delegate: CameraAnimatorDelegate, + dateProvider: DateProvider = DefaultDateProvider()) { guard let flyToInterpolator = FlyToInterpolator(from: inital, to: final, size: mapSize) else { return nil } @@ -37,6 +40,7 @@ public class FlyToCameraAnimator: NSObject, CameraAnimator, CameraAnimatorInterf self.owner = owner self.finalCameraOptions = final self.duration = duration ?? flyToInterpolator.duration() + self.dateProvider = dateProvider } public func stopAnimation() { @@ -46,7 +50,7 @@ public class FlyToCameraAnimator: NSObject, CameraAnimator, CameraAnimatorInterf internal func startAnimation() { state = .active - start = Date() + start = dateProvider.now } internal func addCompletion(_ completion: @escaping AnimationCompletion) { @@ -67,7 +71,7 @@ public class FlyToCameraAnimator: NSObject, CameraAnimator, CameraAnimatorInterf guard state == .active, let start = start else { return nil } - let fractionComplete = min(Date().timeIntervalSince(start) / duration, 1) + let fractionComplete = min(dateProvider.now.timeIntervalSince(start) / duration, 1) guard fractionComplete < 1 else { state = .stopped scheduleCompletionIfNecessary(position: .end) diff --git a/Sources/MapboxMaps/Foundation/Camera/FlyToInterpolator.swift b/Sources/MapboxMaps/Foundation/Camera/FlyToInterpolator.swift index fb475ae9889..045e3dbbf9d 100644 --- a/Sources/MapboxMaps/Foundation/Camera/FlyToInterpolator.swift +++ b/Sources/MapboxMaps/Foundation/Camera/FlyToInterpolator.swift @@ -58,7 +58,6 @@ internal struct FlyToInterpolator { let sourceZoomParam = source.zoom, let sourcePitchParam = source.pitch, let sourceBearingParam = source.bearing else { - preconditionFailure("Source camera should have valid, non optional, parameters") return nil } diff --git a/Sources/MapboxMaps/Foundation/DateProvider.swift b/Sources/MapboxMaps/Foundation/DateProvider.swift new file mode 100644 index 00000000000..0c37a821e21 --- /dev/null +++ b/Sources/MapboxMaps/Foundation/DateProvider.swift @@ -0,0 +1,13 @@ +import Foundation + +internal protocol DateProvider { + + // Provides the current date + var now: Date { get } +} + +internal struct DefaultDateProvider: DateProvider { + var now: Date { + return Date() + } +} diff --git a/Tests/MapboxMapsTests/Foundation/Camera/FlyToAnimatorTests.swift b/Tests/MapboxMapsTests/Foundation/Camera/FlyToAnimatorTests.swift index b207b06acbd..83335c5e22b 100644 --- a/Tests/MapboxMapsTests/Foundation/Camera/FlyToAnimatorTests.swift +++ b/Tests/MapboxMapsTests/Foundation/Camera/FlyToAnimatorTests.swift @@ -21,7 +21,7 @@ final class FlyToAnimatorTests: XCTestCase { bearing: 10, pitch: 10) - let animationOwner = AnimationOwner.custom(id: "") + let animationOwner = AnimationOwner.custom(id: "fly-to") let duration: TimeInterval = 10 var flyToAnimator: FlyToCameraAnimator! @@ -29,16 +29,20 @@ final class FlyToAnimatorTests: XCTestCase { // swiftlint:disable weak_delegate var cameraAnimatorDelegate: CameraAnimatorDelegateMock! + fileprivate var dateProvider: MockDateProvider! + override func setUp() { super.setUp() cameraAnimatorDelegate = CameraAnimatorDelegateMock() + dateProvider = MockDateProvider() flyToAnimator = FlyToCameraAnimator( inital: initalCameraOptions, final: finalCameraOptions, - owner: .custom(id: ""), + owner: .custom(id: "fly-to"), duration: duration, mapSize: CGSize(width: 500, height: 500), - delegate: cameraAnimatorDelegate) + delegate: cameraAnimatorDelegate, + dateProvider: dateProvider) } override func tearDown() { @@ -55,35 +59,106 @@ final class FlyToAnimatorTests: XCTestCase { } func testInitializationWithANegativeDurationReturnsNil() { + XCTAssertNil( + FlyToCameraAnimator( + inital: initalCameraOptions, + final: finalCameraOptions, + owner: .custom(id: "fly-to"), + duration: -1, + mapSize: CGSize(width: 500, height: 500), + delegate: cameraAnimatorDelegate) + ) } func testInitializationWithANilDurationSetsDurationToCalculatedValue() { + let animator = FlyToCameraAnimator( + inital: initalCameraOptions, + final: finalCameraOptions, + owner: .custom(id: "fly-to"), + duration: nil, + mapSize: CGSize(width: 500, height: 500), + delegate: cameraAnimatorDelegate) + XCTAssertNotNil(animator?.duration) } func testInitializationWithInvalidCameraOptionsReturnsNil() { + XCTAssertNil( + FlyToCameraAnimator( + inital: CameraOptions(), + final: finalCameraOptions, + owner: .custom(id: "fly-to"), + duration: -1, + mapSize: CGSize(width: 500, height: 500), + delegate: cameraAnimatorDelegate) + ) } func testStartAnimationChangesStateToActive() { flyToAnimator.startAnimation() - XCTAssertEqual(flyToAnimator.state, .active) } func testAnimationBlocksAreScheduledWhenAnimationIsComplete() { + flyToAnimator.addCompletion({ (_) in + () // no-op + }) + + flyToAnimator.startAnimation() + dateProvider.mockValue = Date(timeIntervalSinceReferenceDate: 20) + + let currentCameraOptions = flyToAnimator.currentCameraOptions + XCTAssertEqual(currentCameraOptions, finalCameraOptions) + XCTAssertEqual(flyToAnimator.state, .stopped) + XCTAssertEqual(cameraAnimatorDelegate.schedulePendingCompletionStub.invocations.count, 1) + XCTAssertEqual(cameraAnimatorDelegate.schedulePendingCompletionStub.invocations.first?.parameters.animatingPosition, .end) + } func testAnimationBlocksAreScheduledWhenStopAnimationIsInvoked() { + + flyToAnimator.addCompletion({ (_) in + () // no-op + }) + + flyToAnimator.startAnimation() + flyToAnimator.stopAnimation() + + XCTAssertEqual(cameraAnimatorDelegate.schedulePendingCompletionStub.invocations.count, 1) + XCTAssertEqual(cameraAnimatorDelegate.schedulePendingCompletionStub.invocations.first?.parameters.animatingPosition, .current) + } func testStopAnimationChangesStateToStopped() { + flyToAnimator.startAnimation() + flyToAnimator.stopAnimation() + + XCTAssertEqual(flyToAnimator.state, .stopped) } func testCurrentCameraOptionsReturnsNilIfAnimationIsNotRunning() { + XCTAssertEqual(flyToAnimator.state, .inactive) + XCTAssertNil(flyToAnimator.currentCameraOptions) } func testCurrentCameraOptionsReturnsInterpolatedValueIfAnimationIsRunning() { + + flyToAnimator.startAnimation() + dateProvider.mockValue = Date(timeIntervalSinceReferenceDate: 5) + + let interpolatedCamera = flyToAnimator.currentCameraOptions + XCTAssertNotNil(interpolatedCamera) + } +} + +private class MockDateProvider: DateProvider { + + var mockValue: Date + + var now: Date { + return mockValue } - func testCurrentCameraOptionsReturnsFinalCameraOptionsIfAnimationIsComplete() { + init(mockValue: Date = Date(timeIntervalSinceReferenceDate: 0)) { + self.mockValue = mockValue } } From 73aaef26395678e352bc95e9204af8ed6a8bb0da Mon Sep 17 00:00:00 2001 From: Nishant Karajgikar Date: Tue, 27 Apr 2021 14:24:00 -0400 Subject: [PATCH 40/40] Rename CameraManager -> CameraAnimationsManager [run device tests] --- Mapbox/MapboxMaps.xcodeproj/project.pbxproj | 16 ++++++++-------- ...imationsManager+CameraAnimatorDelegate.swift} | 2 +- ...nager.swift => CameraAnimationsManager.swift} | 2 +- Sources/MapboxMaps/Gestures/GestureManager.swift | 2 +- .../MapboxMaps/MapView/MapView+Managers.swift | 4 ++-- Sources/MapboxMaps/MapView/MapView.swift | 2 +- .../Camera/MapboxMapsCameraTests.swift | 2 +- .../Integration Tests/FeatureQueryingTest.swift | 4 ++-- 8 files changed, 17 insertions(+), 17 deletions(-) rename Sources/MapboxMaps/Foundation/Camera/{CameraManager+CameraAnimatorDelegate.swift => CameraAnimationsManager+CameraAnimatorDelegate.swift} (99%) rename Sources/MapboxMaps/Foundation/Camera/{CameraManager.swift => CameraAnimationsManager.swift} (99%) diff --git a/Mapbox/MapboxMaps.xcodeproj/project.pbxproj b/Mapbox/MapboxMaps.xcodeproj/project.pbxproj index b431f9e7b2d..9f32702eddf 100644 --- a/Mapbox/MapboxMaps.xcodeproj/project.pbxproj +++ b/Mapbox/MapboxMaps.xcodeproj/project.pbxproj @@ -35,6 +35,8 @@ 0C01C0B925486E7200E4AA46 /* ExpressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C01C08825486E6100E4AA46 /* ExpressionTests.swift */; }; 0C088AC026386D2700107B5E /* WeakCameraAnimatorSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C088ABF26386D2700107B5E /* WeakCameraAnimatorSet.swift */; }; 0C088AC526387EA400107B5E /* DateProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C088AC426387EA400107B5E /* DateProvider.swift */; }; + 0C088ACB26388E3400107B5E /* CameraAnimationsManager+CameraAnimatorDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C088AC926388E3400107B5E /* CameraAnimationsManager+CameraAnimatorDelegate.swift */; }; + 0C088ACC26388E3400107B5E /* CameraAnimationsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C088ACA26388E3400107B5E /* CameraAnimationsManager.swift */; }; 0C1AF560244E47C1008D2A10 /* GestureOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C1AF559244DF4DF008D2A10 /* GestureOptions.swift */; }; 0C26425524EECD14001FE2E3 /* AllExpressions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C26425424EECD14001FE2E3 /* AllExpressions.swift */; }; 0C2CD9F625D19D75006D068F /* OptionsIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C2CD9E225D19D30006D068F /* OptionsIntegrationTests.swift */; }; @@ -143,7 +145,6 @@ 0CD62F1024588530006421D1 /* GestureManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CC8BD2C242405AA00288A9B /* GestureManager.swift */; }; 0CD62F1124588532006421D1 /* GestureHandlerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CC8BD2D242405AA00288A9B /* GestureHandlerDelegate.swift */; }; 0CD62F15245885B6006421D1 /* MapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F11C12E2421B23700F8397B /* MapView.swift */; }; - 0CD62F1624588647006421D1 /* CameraManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FC8DA51243BECC400A19318 /* CameraManager.swift */; }; 0CD62F182458865A006421D1 /* CameraView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FC8DA55243BED0500A19318 /* CameraView.swift */; }; 0CD62F1924588661006421D1 /* MapCameraOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C07948D2452331A0006A9C4 /* MapCameraOptions.swift */; }; 0CD62F1A245886E7006421D1 /* LocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FF65A67244E34000069B561 /* LocationManager.swift */; }; @@ -200,7 +201,6 @@ B5A6922B2627566A00A03412 /* MapInitOptionsTests.xib in Resources */ = {isa = PBXBuildFile; fileRef = B5A6922A2627566A00A03412 /* MapInitOptionsTests.xib */; }; B5B55D45260E4D1500EBB589 /* AnimationOwner.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B55D41260E4D1500EBB589 /* AnimationOwner.swift */; }; B5B55D46260E4D1500EBB589 /* CameraAnimationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B55D42260E4D1500EBB589 /* CameraAnimationDelegate.swift */; }; - B5B55D47260E4D1500EBB589 /* CameraManager+CameraAnimatorDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B55D43260E4D1500EBB589 /* CameraManager+CameraAnimatorDelegate.swift */; }; B5B55D4C260E4D2900EBB589 /* MBXEdgeInsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B55D4B260E4D2900EBB589 /* MBXEdgeInsets.swift */; }; B5B55D51260E4D4500EBB589 /* GestureManager+GestureHandlerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B55D50260E4D4500EBB589 /* GestureManager+GestureHandlerDelegate.swift */; }; B5B55D5F260E4D7B00EBB589 /* CameraAnimatorDelegateMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B55D56260E4D7500EBB589 /* CameraAnimatorDelegateMock.swift */; }; @@ -431,6 +431,8 @@ 0C07948D2452331A0006A9C4 /* MapCameraOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapCameraOptions.swift; sourceTree = ""; }; 0C088ABF26386D2700107B5E /* WeakCameraAnimatorSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeakCameraAnimatorSet.swift; sourceTree = ""; }; 0C088AC426387EA400107B5E /* DateProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateProvider.swift; sourceTree = ""; }; + 0C088AC926388E3400107B5E /* CameraAnimationsManager+CameraAnimatorDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CameraAnimationsManager+CameraAnimatorDelegate.swift"; sourceTree = ""; }; + 0C088ACA26388E3400107B5E /* CameraAnimationsManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraAnimationsManager.swift; sourceTree = ""; }; 0C1AF559244DF4DF008D2A10 /* GestureOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GestureOptions.swift; sourceTree = ""; }; 0C26425424EECD14001FE2E3 /* AllExpressions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AllExpressions.swift; sourceTree = ""; }; 0C2CD9E225D19D30006D068F /* OptionsIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionsIntegrationTests.swift; sourceTree = ""; }; @@ -555,7 +557,6 @@ 1F11C12E2421B23700F8397B /* MapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapView.swift; sourceTree = ""; }; 1F2AD73C2421C462006592F4 /* GestureManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GestureManagerTests.swift; sourceTree = ""; }; 1FC8DA47243BE4A400A19318 /* MapboxMapsCameraTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapboxMapsCameraTests.swift; sourceTree = ""; }; - 1FC8DA51243BECC400A19318 /* CameraManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraManager.swift; sourceTree = ""; }; 1FC8DA55243BED0500A19318 /* CameraView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraView.swift; sourceTree = ""; }; 1FECC9DF2474519D00B63910 /* Expression.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Expression.swift; sourceTree = ""; }; 1FF65A67244E34000069B561 /* LocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationManager.swift; sourceTree = ""; }; @@ -625,7 +626,6 @@ B5A6922A2627566A00A03412 /* MapInitOptionsTests.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = MapInitOptionsTests.xib; sourceTree = ""; }; B5B55D41260E4D1500EBB589 /* AnimationOwner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimationOwner.swift; sourceTree = ""; }; B5B55D42260E4D1500EBB589 /* CameraAnimationDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraAnimationDelegate.swift; sourceTree = ""; }; - B5B55D43260E4D1500EBB589 /* CameraManager+CameraAnimatorDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CameraManager+CameraAnimatorDelegate.swift"; sourceTree = ""; }; B5B55D4B260E4D2900EBB589 /* MBXEdgeInsets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MBXEdgeInsets.swift; sourceTree = ""; }; B5B55D50260E4D4500EBB589 /* GestureManager+GestureHandlerDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "GestureManager+GestureHandlerDelegate.swift"; sourceTree = ""; }; B5B55D55260E4D7500EBB589 /* CameraAnimatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraAnimatorTests.swift; sourceTree = ""; }; @@ -1241,16 +1241,16 @@ 1F4B7D7A2464C36E00745F05 /* Camera */ = { isa = PBXGroup; children = ( + 0C088ACA26388E3400107B5E /* CameraAnimationsManager.swift */, + 0C088AC926388E3400107B5E /* CameraAnimationsManager+CameraAnimatorDelegate.swift */, 0C088ABF26386D2700107B5E /* WeakCameraAnimatorSet.swift */, 0C350D81263278090090FA74 /* BasicCameraAnimator.swift */, 0C350D82263278090090FA74 /* CameraTransition.swift */, 0C350D80263278090090FA74 /* FlyToCameraAnimator.swift */, B5B55D41260E4D1500EBB589 /* AnimationOwner.swift */, B5B55D42260E4D1500EBB589 /* CameraAnimationDelegate.swift */, - B5B55D43260E4D1500EBB589 /* CameraManager+CameraAnimatorDelegate.swift */, 1FC8DA55243BED0500A19318 /* CameraView.swift */, 07729A5724623E9B00440187 /* CameraOptions.swift */, - 1FC8DA51243BECC400A19318 /* CameraManager.swift */, 0C07948D2452331A0006A9C4 /* MapCameraOptions.swift */, CA355C06256EEA7C00FD1DB7 /* FlyToInterpolator.swift */, ); @@ -1902,7 +1902,6 @@ CABCDF422620E09E00D61635 /* MapConfig.swift in Sources */, C64994AB258D5ADE0052C21C /* Puck.swift in Sources */, 0CD62F24245887CD006421D1 /* OrnamentSupportableView.swift in Sources */, - 0CD62F1624588647006421D1 /* CameraManager.swift in Sources */, CA355C07256EEA7C00FD1DB7 /* FlyToInterpolator.swift in Sources */, 0C708F4F24EB23C7003CE791 /* VectorSource.swift in Sources */, 0C946D1324DC5F8E003F114A /* Event.swift in Sources */, @@ -1973,6 +1972,7 @@ 07BA35E825157A0E003E1B55 /* AnnotationSupportableMap.swift in Sources */, 0C55010B2476D83A00AE019A /* Light.swift in Sources */, B5B55D45260E4D1500EBB589 /* AnimationOwner.swift in Sources */, + 0C088ACC26388E3400107B5E /* CameraAnimationsManager.swift in Sources */, 0CD34FE8242AAE0900943687 /* MapView+Managers.swift in Sources */, CA6245D52627E72A00C79547 /* TileRegionLoadOptions+MapboxMaps.swift in Sources */, 5A9648FE246429C3001FF05D /* MapboxCompassOrnamentView.swift in Sources */, @@ -1981,12 +1981,12 @@ 0C479F90251CEC340025EC4F /* Snapshotter.swift in Sources */, 0CD62F182458865A006421D1 /* CameraView.swift in Sources */, 0C088AC526387EA400107B5E /* DateProvider.swift in Sources */, - B5B55D47260E4D1500EBB589 /* CameraManager+CameraAnimatorDelegate.swift in Sources */, 07BA35CE251578DD003E1B55 /* AnnotationInteractionDelegate.swift in Sources */, 2B8637E62463F36400698135 /* DistanceFormatter.swift in Sources */, CA0C426926028ED30054D9D0 /* AnnotationOptions.swift in Sources */, 0C708F2F24EB1EE2003CE791 /* RasterLayer.swift in Sources */, 0C088AC026386D2700107B5E /* WeakCameraAnimatorSet.swift in Sources */, + 0C088ACB26388E3400107B5E /* CameraAnimationsManager+CameraAnimatorDelegate.swift in Sources */, 0C35750225DD8D960085D775 /* StyleErrors.swift in Sources */, 0CE3D1B425816BD6000585A2 /* MapView+Supportable.swift in Sources */, 0C9DE369252C263600880CC8 /* GeoJSONSourceData.swift in Sources */, diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraManager+CameraAnimatorDelegate.swift b/Sources/MapboxMaps/Foundation/Camera/CameraAnimationsManager+CameraAnimatorDelegate.swift similarity index 99% rename from Sources/MapboxMaps/Foundation/Camera/CameraManager+CameraAnimatorDelegate.swift rename to Sources/MapboxMaps/Foundation/Camera/CameraAnimationsManager+CameraAnimatorDelegate.swift index 681e02dca85..10a0cca61cf 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraManager+CameraAnimatorDelegate.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraAnimationsManager+CameraAnimatorDelegate.swift @@ -1,7 +1,7 @@ import UIKit // MARK: Camera Animation -extension CameraManager: CameraAnimatorDelegate { +extension CameraAnimationsManager: CameraAnimatorDelegate { // MARK: Animator Functions diff --git a/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift b/Sources/MapboxMaps/Foundation/Camera/CameraAnimationsManager.swift similarity index 99% rename from Sources/MapboxMaps/Foundation/Camera/CameraManager.swift rename to Sources/MapboxMaps/Foundation/Camera/CameraAnimationsManager.swift index f7e2324ce42..a81fe84d032 100644 --- a/Sources/MapboxMaps/Foundation/Camera/CameraManager.swift +++ b/Sources/MapboxMaps/Foundation/Camera/CameraAnimationsManager.swift @@ -16,7 +16,7 @@ internal protocol CameraAnimatorInterface: CameraAnimator { } /// An object that manages a camera's view lifecycle. -public class CameraManager { +public class CameraAnimationsManager { /// Used to set up camera specific configuration public internal(set) var mapCameraOptions: MapCameraOptions diff --git a/Sources/MapboxMaps/Gestures/GestureManager.swift b/Sources/MapboxMaps/Gestures/GestureManager.swift index 71359e73c79..86678c9766b 100644 --- a/Sources/MapboxMaps/Gestures/GestureManager.swift +++ b/Sources/MapboxMaps/Gestures/GestureManager.swift @@ -120,7 +120,7 @@ internal protocol CameraManagerProtocol: AnyObject { func cancelAnimations() } -extension CameraManager: CameraManagerProtocol { } +extension CameraAnimationsManager: CameraManagerProtocol { } public final class GestureManager: NSObject { diff --git a/Sources/MapboxMaps/MapView/MapView+Managers.swift b/Sources/MapboxMaps/MapView/MapView+Managers.swift index ad3f084ba15..0cee16b9ce2 100644 --- a/Sources/MapboxMaps/MapView/MapView+Managers.swift +++ b/Sources/MapboxMaps/MapView/MapView+Managers.swift @@ -60,7 +60,7 @@ extension MapView { metalView?.presentsWithTransaction = newOptions.presentsWithTransaction } - internal func setupGestures(with view: UIView, options: GestureOptions, cameraManager: CameraManager) { + internal func setupGestures(with view: UIView, options: GestureOptions, cameraManager: CameraAnimationsManager) { gestures = GestureManager(for: view, options: options, cameraManager: cameraManager) } @@ -69,7 +69,7 @@ extension MapView { } internal func setupCamera(for view: MapView, options: MapCameraOptions) { - camera = CameraManager(for: view, with: mapConfig.camera) + camera = CameraAnimationsManager(for: view, with: mapConfig.camera) } internal func updateCamera(with newOptions: MapCameraOptions) { diff --git a/Sources/MapboxMaps/MapView/MapView.swift b/Sources/MapboxMaps/MapView/MapView.swift index 7fc78124945..774311379d3 100644 --- a/Sources/MapboxMaps/MapView/MapView.swift +++ b/Sources/MapboxMaps/MapView/MapView.swift @@ -15,7 +15,7 @@ open class MapView: BaseMapView { internal var ornaments: OrnamentsManager! /// The `camera` object manages a camera's view lifecycle.. - public internal(set) var camera: CameraManager! + public internal(set) var camera: CameraAnimationsManager! /// The `location`object handles location events of the map. public internal(set) var location: LocationManager! diff --git a/Tests/MapboxMapsTests/Foundation/Camera/MapboxMapsCameraTests.swift b/Tests/MapboxMapsTests/Foundation/Camera/MapboxMapsCameraTests.swift index af270d73d7b..6214c89f215 100644 --- a/Tests/MapboxMapsTests/Foundation/Camera/MapboxMapsCameraTests.swift +++ b/Tests/MapboxMapsTests/Foundation/Camera/MapboxMapsCameraTests.swift @@ -3,7 +3,7 @@ import XCTest internal class CameraManagerIntegrationTests: MapViewIntegrationTestCase { - var cameraManager: CameraManager { + var cameraManager: CameraAnimationsManager { guard let mapView = mapView else { fatalError("MapView must not be nil") } diff --git a/Tests/MapboxMapsTests/MapView/Integration Tests/FeatureQueryingTest.swift b/Tests/MapboxMapsTests/MapView/Integration Tests/FeatureQueryingTest.swift index b0dee8cfd89..cb6b8dc7d05 100644 --- a/Tests/MapboxMapsTests/MapView/Integration Tests/FeatureQueryingTest.swift +++ b/Tests/MapboxMapsTests/MapView/Integration Tests/FeatureQueryingTest.swift @@ -22,7 +22,7 @@ internal class FeatureQueryingTest: MapViewIntegrationTestCase { let featureQueryExpectation = XCTestExpectation(description: "Wait for features to be queried.") didFinishLoadingStyle = { mapView in - let cameraManager = CameraManager(for: mapView, with: MapCameraOptions()) + let cameraManager = CameraAnimationsManager(for: mapView, with: MapCameraOptions()) cameraManager.setCamera(to: CameraOptions(center: self.centerCoordinate, zoom: 15.0)) } @@ -56,7 +56,7 @@ internal class FeatureQueryingTest: MapViewIntegrationTestCase { let featureQueryExpectation = XCTestExpectation(description: "Wait for features to be queried.") didFinishLoadingStyle = { mapView in - let cameraManager = CameraManager(for: mapView, with: MapCameraOptions()) + let cameraManager = CameraAnimationsManager(for: mapView, with: MapCameraOptions()) cameraManager.setCamera(to: CameraOptions(center: self.centerCoordinate, zoom: 15.0)) }