Last active
March 10, 2022 22:32
-
-
Save johnnewman/b2b76aae3cde66b1e3a0d77555647b93 to your computer and use it in GitHub Desktop.
CircleLayer rapid property update pulse
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// CircleManualUpdatePulse.swift | |
// | |
// Created by John Newman on 3/10/22. | |
// Copyright © 2022 Roadtrippers LLC. All rights reserved. | |
// | |
import Foundation | |
import MapboxMaps | |
class StyleLayerPulseRenderer { | |
private let pulseLayerIdentifier = "my-pulse-layer" | |
private let pulseSourceIdentifier = "my-pulse-source" | |
let pulseRepeatInterval: TimeInterval = 3 | |
let pulseDuration: TimeInterval = 1.5 | |
var pauseBeforeStarting: DispatchTime { | |
.now() + pulseRepeatInterval - pulseDuration | |
} | |
let desiredFramerate: Double = 60 | |
var frameRenderInterval: DispatchTime { | |
.now() + TimeInterval(1.0) / desiredFramerate | |
} | |
let strokeWidth = (3.0, 5.0) | |
let strokeOpacity = (1.0, 0.0) | |
let radius = (0.0, 62.0) | |
private var map: MapView | |
private var layerPosition: LayerPosition | |
init(map: MapView, layerPosition: LayerPosition) { | |
self.map = map | |
self.layerPosition = layerPosition | |
} | |
func startPulseAnimation(forAnnotation annotation: PointAnnotation) { | |
guard !map.mapboxMap.style.sourceExists(withId: pulseSourceIdentifier), | |
!map.mapboxMap.style.layerExists(withId: pulseLayerIdentifier) else { | |
print("Pulse layer/source already exist in style. Aborting.") | |
return | |
} | |
var pulseSource = GeoJSONSource() | |
pulseSource.data = .feature(.init(geometry: annotation.geometry)) | |
var pulseLayer = CircleLayer(id: pulseLayerIdentifier) | |
pulseLayer.source = pulseSourceIdentifier | |
// Set up starting values | |
pulseLayer.circleStrokeColor = .constant(StyleColor(UIColor.blue)) | |
pulseLayer.circleOpacity = .constant(0) | |
pulseLayer.circleRadius = .constant(0) | |
pulseLayer.circleStrokeWidth = .constant(0) | |
pulseLayer.circleStrokeOpacity = .constant(0) | |
// Avoid any Mapbox animations. | |
pulseLayer.circleBlurTransition = .init(duration: 0, delay: 0) | |
pulseLayer.circleColorTransition = .init(duration: 0, delay: 0) | |
pulseLayer.circleRadiusTransition = .init(duration: 0, delay: 0) | |
pulseLayer.circleOpacityTransition = .init(duration: 0, delay: 0) | |
pulseLayer.circleTranslateTransition = .init(duration: 0, delay: 0) | |
pulseLayer.circleStrokeColorTransition = .init(duration: 0, delay: 0) | |
pulseLayer.circleStrokeWidthTransition = .init(duration: 0, delay: 0) | |
pulseLayer.circleStrokeOpacityTransition = .init(duration: 0, delay: 0) | |
try! map.mapboxMap.style.addSource(pulseSource, id: pulseSourceIdentifier) | |
try! map.mapboxMap.style.addLayer(pulseLayer, layerPosition: layerPosition) | |
DispatchQueue.main.asyncAfter(deadline: pauseBeforeStarting, execute: newPulseIteration) | |
} | |
private func newPulseIteration() { | |
let animationStartTime = CACurrentMediaTime() | |
renderFrame() | |
func renderFrame() { | |
let completedAnimationDuration = CACurrentMediaTime() - animationStartTime | |
do { | |
let t = min(1, completedAnimationDuration / pulseDuration) | |
try map.mapboxMap.style.updateLayer(withId: pulseLayerIdentifier, type: CircleLayer.self) { layer in | |
layer.circleRadius?.lerp(radius, t: t) | |
layer.circleStrokeWidth?.lerp(strokeWidth, t: t) | |
layer.circleStrokeOpacity?.lerp(strokeOpacity, t: t) | |
} | |
if completedAnimationDuration >= pulseDuration { | |
DispatchQueue.main.asyncAfter(deadline: pauseBeforeStarting, execute: newPulseIteration) | |
} else { | |
DispatchQueue.main.asyncAfter(deadline: frameRenderInterval, execute: renderFrame) | |
} | |
} catch { | |
print("Error: \(error)") | |
} | |
} | |
} | |
} | |
extension Value where T == Double { | |
mutating func lerp(_ range: (T, T), t: Double) { | |
self = .constant((1 - t) * range.0 + t * range.1) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment