unioil-loyalty-rn-app/ios/Pods/lottie-ios/Sources/Private/Utility/Interpolatable/KeyframeInterpolator.swift

251 lines
6.7 KiB
Swift

//
// KeyframeInterpolator.swift
// lottie-swift
//
// Created by Brandon Withrow on 1/15/19.
//
import CoreGraphics
import Foundation
// MARK: - KeyframeInterpolator
/// A value provider that produces a value at Time from a group of keyframes
final class KeyframeInterpolator<ValueType>: ValueProvider where ValueType: AnyInterpolatable {
// MARK: Lifecycle
init(keyframes: ContiguousArray<Keyframe<ValueType>>) {
self.keyframes = keyframes
}
// MARK: Internal
let keyframes: ContiguousArray<Keyframe<ValueType>>
var valueType: Any.Type {
ValueType.self
}
var storage: ValueProviderStorage<ValueType> {
.closure { [self] frame in
// First set the keyframe span for the frame.
updateSpanIndices(frame: frame)
lastUpdatedFrame = frame
// If only one keyframe return its value
let progress: CGFloat
let value: ValueType
if
let leading = leadingKeyframe,
let trailing = trailingKeyframe
{
/// We have leading and trailing keyframe.
progress = leading.interpolatedProgress(trailing, keyTime: frame)
value = leading.interpolate(to: trailing, progress: progress)
} else if let leading = leadingKeyframe {
progress = 0
value = leading.value
} else if let trailing = trailingKeyframe {
progress = 1
value = trailing.value
} else {
/// Satisfy the compiler.
progress = 0
value = keyframes[0].value
}
return value
}
}
/// Returns true to trigger a frame update for this interpolator.
///
/// An interpolator will be asked if it needs to update every frame.
/// If the interpolator needs updating it will be asked to compute its value for
/// the given frame.
///
/// Cases a keyframe should not be updated:
/// - If time is in span and leading keyframe is hold
/// - If time is after the last keyframe.
/// - If time is before the first keyframe
///
/// Cases for updating a keyframe:
/// - If time is in the span, and is not a hold
/// - If time is outside of the span, and there are more keyframes
/// - If a value delegate is set
/// - If leading and trailing are both nil.
func hasUpdate(frame: CGFloat) -> Bool {
if lastUpdatedFrame == nil {
return true
}
if
let leading = leadingKeyframe,
trailingKeyframe == nil,
leading.time < frame
{
/// Frame is after bounds of keyframes
return false
}
if
let trailing = trailingKeyframe,
leadingKeyframe == nil,
frame < trailing.time
{
/// Frame is before bounds of keyframes
return false
}
if
let leading = leadingKeyframe,
let trailing = trailingKeyframe,
leading.isHold,
leading.time < frame,
frame < trailing.time
{
return false
}
return true
}
// MARK: Fileprivate
fileprivate var lastUpdatedFrame: CGFloat?
fileprivate var leadingIndex: Int? = nil
fileprivate var trailingIndex: Int? = nil
fileprivate var leadingKeyframe: Keyframe<ValueType>? = nil
fileprivate var trailingKeyframe: Keyframe<ValueType>? = nil
/// Finds the appropriate Leading and Trailing keyframe index for the given time.
fileprivate func updateSpanIndices(frame: CGFloat) {
guard keyframes.count > 0 else {
leadingIndex = nil
trailingIndex = nil
leadingKeyframe = nil
trailingKeyframe = nil
return
}
// This function searches through the array to find the span of two keyframes
// that contain the current time.
//
// We could use Array.first(where:) but that would search through the entire array
// each frame.
// Instead we track the last used index and search either forwards or
// backwards from there. This reduces the iterations and complexity from
//
// O(n), where n is the length of the sequence to
// O(n), where n is the number of items after or before the last used index.
//
if keyframes.count == 1 {
/// Only one keyframe. Set it as first and move on.
leadingIndex = 0
trailingIndex = nil
leadingKeyframe = keyframes[0]
trailingKeyframe = nil
return
}
/// Sets the initial keyframes. This is often only needed for the first check.
if
leadingIndex == nil,
trailingIndex == nil
{
if frame < keyframes[0].time {
/// Time is before the first keyframe. Set it as the trailing.
trailingIndex = 0
} else {
/// Time is after the first keyframe. Set the keyframe and the trailing.
leadingIndex = 0
trailingIndex = 1
}
}
if
let currentTrailing = trailingIndex,
keyframes[currentTrailing].time <= frame
{
/// Time is after the current span. Iterate forward.
var newLeading = currentTrailing
var keyframeFound = false
while !keyframeFound {
leadingIndex = newLeading
trailingIndex = keyframes.validIndex(newLeading + 1)
guard let trailing = trailingIndex else {
/// We have reached the end of our keyframes. Time is after the last keyframe.
keyframeFound = true
continue
}
if frame < keyframes[trailing].time {
/// Keyframe in current span.
keyframeFound = true
continue
}
/// Advance the array.
newLeading = trailing
}
} else if
let currentLeading = leadingIndex,
frame < keyframes[currentLeading].time
{
/// Time is before the current span. Iterate backwards
var newTrailing = currentLeading
var keyframeFound = false
while !keyframeFound {
leadingIndex = keyframes.validIndex(newTrailing - 1)
trailingIndex = newTrailing
guard let leading = leadingIndex else {
/// We have reached the end of our keyframes. Time is after the last keyframe.
keyframeFound = true
continue
}
if keyframes[leading].time <= frame {
/// Keyframe in current span.
keyframeFound = true
continue
}
/// Step back
newTrailing = leading
}
}
if let keyFrame = leadingIndex {
leadingKeyframe = keyframes[keyFrame]
} else {
leadingKeyframe = nil
}
if let keyFrame = trailingIndex {
trailingKeyframe = keyframes[keyFrame]
} else {
trailingKeyframe = nil
}
}
}
extension Array {
fileprivate func validIndex(_ index: Int) -> Int? {
if 0 <= index, index < endIndex {
return index
}
return nil
}
}
extension ContiguousArray {
fileprivate func validIndex(_ index: Int) -> Int? {
if 0 <= index, index < endIndex {
return index
}
return nil
}
}