Merge commit '7621e2f8dec938cf48181c8b10afc9b01f444e68' into beta

This commit is contained in:
Ilya Laktyushin
2025-12-06 02:17:48 +04:00
commit 8344b97e03
28070 changed files with 7995182 additions and 0 deletions
@@ -0,0 +1,113 @@
//
// Keyframe.swift
// lottie-swift
//
// Created by Brandon Withrow on 1/7/19.
//
import CoreGraphics
import Foundation
// MARK: - KeyframeData
/// A generic class used to parse and remap keyframe json.
///
/// Keyframe json has a couple of different variations and formats depending on the
/// type of keyframea and also the version of the JSON. By parsing the raw data
/// we can reconfigure it into a constant format.
final class KeyframeData<T> {
// MARK: Lifecycle
init(
startValue: T?,
endValue: T?,
time: AnimationFrameTime?,
hold: Int?,
inTangent: Vector2D?,
outTangent: Vector2D?,
spatialInTangent: Vector3D?,
spatialOutTangent: Vector3D?)
{
self.startValue = startValue
self.endValue = endValue
self.time = time
self.hold = hold
self.inTangent = inTangent
self.outTangent = outTangent
self.spatialInTangent = spatialInTangent
self.spatialOutTangent = spatialOutTangent
}
// MARK: Internal
enum CodingKeys: String, CodingKey {
case startValue = "s"
case endValue = "e"
case time = "t"
case hold = "h"
case inTangent = "i"
case outTangent = "o"
case spatialInTangent = "ti"
case spatialOutTangent = "to"
}
/// The start value of the keyframe
let startValue: T?
/// The End value of the keyframe. Note: Newer versions animation json do not have this field.
let endValue: T?
/// The time in frames of the keyframe.
let time: AnimationFrameTime?
/// A hold keyframe freezes interpolation until the next keyframe that is not a hold.
let hold: Int?
/// The in tangent for the time interpolation curve.
let inTangent: Vector2D?
/// The out tangent for the time interpolation curve.
let outTangent: Vector2D?
/// The spacial in tangent of the vector.
let spatialInTangent: Vector3D?
/// The spacial out tangent of the vector.
let spatialOutTangent: Vector3D?
var isHold: Bool {
if let hold = hold {
return hold > 0
}
return false
}
}
// MARK: Encodable
extension KeyframeData: Encodable where T: Encodable { }
// MARK: Decodable
extension KeyframeData: Decodable where T: Decodable { }
// MARK: DictionaryInitializable
extension KeyframeData: DictionaryInitializable where T: AnyInitializable {
convenience init(dictionary: [String: Any]) throws {
let startValue = try? dictionary[CodingKeys.startValue.rawValue].flatMap(T.init)
let endValue = try? dictionary[CodingKeys.endValue.rawValue].flatMap(T.init)
let time: AnimationFrameTime? = try? dictionary.value(for: CodingKeys.time)
let hold: Int? = try? dictionary.value(for: CodingKeys.hold)
let inTangent: Vector2D? = try? dictionary.value(for: CodingKeys.inTangent)
let outTangent: Vector2D? = try? dictionary.value(for: CodingKeys.outTangent)
let spatialInTangent: Vector3D? = try? dictionary.value(for: CodingKeys.spatialInTangent)
let spatialOutTangent: Vector3D? = try? dictionary.value(for: CodingKeys.spatialOutTangent)
self.init(
startValue: startValue,
endValue: endValue,
time: time,
hold: hold,
inTangent: inTangent,
outTangent: outTangent,
spatialInTangent: spatialInTangent,
spatialOutTangent: spatialOutTangent)
}
}
@@ -0,0 +1,197 @@
//
// 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)
}
}