unioil-loyalty-rn-app/ios/Pods/lottie-ios/Sources/Private/Model/Keyframes/KeyframeGroup.swift

223 lines
6.8 KiB
Swift

//
// KeyframeGroup.swift
// lottie-swift
//
// Created by Brandon Withrow on 1/14/19.
//
import Foundation
// MARK: - KeyframeGroup
/// Used for coding/decoding a group of Keyframes by type.
///
/// Keyframe data is wrapped in a dictionary { "k" : KeyframeData }.
/// The keyframe data can either be an array of keyframes or, if no animation is present, the raw value.
/// This helper object is needed to properly decode the json.
final class KeyframeGroup<T> {
// MARK: Lifecycle
init(keyframes: ContiguousArray<Keyframe<T>>) {
self.keyframes = keyframes
}
init(_ value: T) {
keyframes = [Keyframe(value)]
}
// MARK: Internal
enum KeyframeWrapperKey: String, CodingKey {
case keyframeData = "k"
}
let keyframes: ContiguousArray<Keyframe<T>>
}
// MARK: Decodable
extension KeyframeGroup: Decodable where T: Decodable {
convenience init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: KeyframeWrapperKey.self)
if let keyframeData: T = try? container.decode(T.self, forKey: .keyframeData) {
/// Try to decode raw value; No keyframe data.
self.init(keyframes: [Keyframe<T>(keyframeData)])
} else {
// Decode and array of keyframes.
//
// Body Movin and Lottie deal with keyframes in different ways.
//
// A keyframe object in Body movin defines a span of time with a START
// and an END, from the current keyframe time to the next keyframe time.
//
// A keyframe object in Lottie defines a singular point in time/space.
// This point has an in-tangent and an out-tangent.
//
// To properly decode this we must iterate through keyframes while holding
// reference to the previous keyframe.
var keyframesContainer = try container.nestedUnkeyedContainer(forKey: .keyframeData)
var keyframes = ContiguousArray<Keyframe<T>>()
var previousKeyframeData: KeyframeData<T>?
while !keyframesContainer.isAtEnd {
// Ensure that Time and Value are present.
let keyframeData = try keyframesContainer.decode(KeyframeData<T>.self)
guard
let value: T = keyframeData.startValue ?? previousKeyframeData?.endValue,
let time = keyframeData.time else
{
/// Missing keyframe data. JSON must be corrupt.
throw DecodingError.dataCorruptedError(
forKey: KeyframeWrapperKey.keyframeData,
in: container,
debugDescription: "Missing keyframe data.")
}
keyframes.append(Keyframe<T>(
value: value,
time: AnimationFrameTime(time),
isHold: keyframeData.isHold,
inTangent: previousKeyframeData?.inTangent,
outTangent: keyframeData.outTangent,
spatialInTangent: previousKeyframeData?.spatialInTangent,
spatialOutTangent: keyframeData.spatialOutTangent))
previousKeyframeData = keyframeData
}
self.init(keyframes: keyframes)
}
}
}
// MARK: Encodable
extension KeyframeGroup: Encodable where T: Encodable {
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: KeyframeWrapperKey.self)
if keyframes.count == 1 {
let keyframe = keyframes[0]
try container.encode(keyframe.value, forKey: .keyframeData)
} else {
var keyframeContainer = container.nestedUnkeyedContainer(forKey: .keyframeData)
for i in 1..<keyframes.endIndex {
let keyframe = keyframes[i - 1]
let nextKeyframe = keyframes[i]
let keyframeData = KeyframeData<T>(
startValue: keyframe.value,
endValue: nextKeyframe.value,
time: keyframe.time,
hold: keyframe.isHold ? 1 : nil,
inTangent: nextKeyframe.inTangent,
outTangent: keyframe.outTangent,
spatialInTangent: nil,
spatialOutTangent: nil)
try keyframeContainer.encode(keyframeData)
}
}
}
}
// MARK: DictionaryInitializable
extension KeyframeGroup: DictionaryInitializable where T: AnyInitializable {
convenience init(dictionary: [String: Any]) throws {
var keyframes = ContiguousArray<Keyframe<T>>()
if
let rawValue = dictionary[KeyframeWrapperKey.keyframeData.rawValue],
let value = try? T(value: rawValue)
{
keyframes = [Keyframe<T>(value)]
} else {
var frameDictionaries: [[String: Any]]
if let singleFrameDictionary = dictionary[KeyframeWrapperKey.keyframeData.rawValue] as? [String: Any] {
frameDictionaries = [singleFrameDictionary]
} else {
frameDictionaries = try dictionary.value(for: KeyframeWrapperKey.keyframeData)
}
var previousKeyframeData: KeyframeData<T>?
for frameDictionary in frameDictionaries {
let data = try KeyframeData<T>(dictionary: frameDictionary)
guard
let value: T = data.startValue ?? previousKeyframeData?.endValue,
let time = data.time else
{
throw InitializableError.invalidInput
}
keyframes.append(Keyframe<T>(
value: value,
time: time,
isHold: data.isHold,
inTangent: previousKeyframeData?.inTangent,
outTangent: data.outTangent,
spatialInTangent: previousKeyframeData?.spatialInTangent,
spatialOutTangent: data.spatialOutTangent))
previousKeyframeData = data
}
}
self.init(keyframes: keyframes)
}
}
// MARK: Equatable
extension KeyframeGroup: Equatable where T: Equatable {
static func == (_ lhs: KeyframeGroup<T>, _ rhs: KeyframeGroup<T>) -> Bool {
lhs.keyframes == rhs.keyframes
}
}
// MARK: Hashable
extension KeyframeGroup: Hashable where T: Hashable {
func hash(into hasher: inout Hasher) {
hasher.combine(keyframes)
}
}
extension Keyframe {
/// Creates a copy of this `Keyframe` with the same timing data, but a different value
func withValue<Value>(_ newValue: Value) -> Keyframe<Value> {
Keyframe<Value>(
value: newValue,
time: time,
isHold: isHold,
inTangent: inTangent,
outTangent: outTangent,
spatialInTangent: spatialInTangent,
spatialOutTangent: spatialOutTangent)
}
}
extension KeyframeGroup {
/// Maps the values of each individual keyframe in this group
func map<NewValue>(_ transformation: (T) throws -> NewValue) rethrows -> KeyframeGroup<NewValue> {
KeyframeGroup<NewValue>(keyframes: ContiguousArray(try keyframes.map { keyframe in
keyframe.withValue(try transformation(keyframe.value))
}))
}
}
// MARK: - AnyKeyframeGroup
/// A type-erased wrapper for `KeyframeGroup`s
protocol AnyKeyframeGroup {
var untyped: KeyframeGroup<Any> { get }
}
// MARK: - KeyframeGroup + AnyKeyframeGroup
extension KeyframeGroup: AnyKeyframeGroup {
/// An untyped copy of these keyframes
var untyped: KeyframeGroup<Any> {
map { $0 as Any }
}
}