Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix parsing issue, add support for DotLottieConfiguration in SwiftUI LottieView #2277

Merged
merged 7 commits into from
Jan 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ jobs:
- uses: actions/checkout@v2
- uses: ./.github/actions/setup
with:
xcode: '14.3' # Swift 5.8
xcode: '15.0' # Swift 5.9
- name: Test Package
run: bundle exec rake test:package
- name: Process test artifacts
Expand Down Expand Up @@ -143,7 +143,7 @@ jobs:
strategy:
matrix:
xcode:
- '14.3' # Swift 5.8
- '15.0' # Swift 5.9
steps:
- uses: actions/checkout@v2
- uses: ./.github/actions/setup
Expand All @@ -162,7 +162,7 @@ jobs:
with:
install-mint: true
install-carthage: true
xcode: '14.3' # Swift 5.8
xcode: '15.0' # Swift 5.9
- name: Test Carthage support
run: bundle exec rake test:carthage

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,7 @@ extension LayerModel {
case (.null, _):
return TransformLayer(layerModel: self)

default:
try context.logCompatibilityIssue("""
Unexpected layer type combination ("\(type)" and "\(Swift.type(of: self))")
""")

case (.unknown, _), (.precomp, _), (.solid, _), (.image, _), (.shape, _), (.text, _):
return nil
}
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Private/Model/Assets/Asset.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public class Asset: Codable, DictionaryInitializable {
} else if let id = dictionary[CodingKeys.id.rawValue] as? Int {
self.id = String(id)
} else {
throw InitializableError.invalidInput
throw InitializableError.invalidInput()
}
}

Expand Down
20 changes: 11 additions & 9 deletions Sources/Private/Model/Assets/AssetLibrary.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,17 @@ final class AssetLibrary: Codable, AnyInitializable, Sendable {
var imageAssets = [String : ImageAsset]()
var precompAssets = [String : PrecompAsset]()

while !container.isAtEnd {
let keyContainer = try containerForKeys.nestedContainer(keyedBy: PrecompAsset.CodingKeys.self)
if keyContainer.contains(.layers) {
let precompAsset = try container.decode(PrecompAsset.self)
while
!container.isAtEnd,
let keyContainer = try? containerForKeys.nestedContainer(keyedBy: PrecompAsset.CodingKeys.self)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add resilient decoder https://github.com/airbnb/ResilientDecoding in case we need the error at another time?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't use the Codable decoding implementation by default (the dictionary-based implementation is the default in Lottie 4.0+ since it's a lot faster), so no need to invest much in the Codable implementation

{
if
keyContainer.contains(.layers),
let precompAsset = try? container.decode(PrecompAsset.self)
{
decodedAssets[precompAsset.id] = precompAsset
precompAssets[precompAsset.id] = precompAsset
} else {
let imageAsset = try container.decode(ImageAsset.self)
} else if let imageAsset = try? container.decode(ImageAsset.self) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If an image asset fails to parse for some reason, we can recover gracefully instead of throwing an error and rejecting the animation

decodedAssets[imageAsset.id] = imageAsset
imageAssets[imageAsset.id] = imageAsset
}
Expand All @@ -37,7 +40,7 @@ final class AssetLibrary: Codable, AnyInitializable, Sendable {

init(value: Any) throws {
guard let dictionaries = value as? [[String: Any]] else {
throw InitializableError.invalidInput
throw InitializableError.invalidInput()
}
var decodedAssets = [String : Asset]()
var imageAssets = [String : ImageAsset]()
Expand All @@ -47,8 +50,7 @@ final class AssetLibrary: Codable, AnyInitializable, Sendable {
let asset = try PrecompAsset(dictionary: dictionary)
decodedAssets[asset.id] = asset
precompAssets[asset.id] = asset
} else {
let asset = try ImageAsset(dictionary: dictionary)
} else if let asset = try? ImageAsset(dictionary: dictionary) {
decodedAssets[asset.id] = asset
imageAssets[asset.id] = asset
}
Expand Down
22 changes: 16 additions & 6 deletions Sources/Private/Model/DictionaryInitializable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
// MARK: - InitializableError

enum InitializableError: Error {
case invalidInput
case invalidInput(file: StaticString = #file, line: UInt = #line)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding in file and line context makes it easier to debug where these errors are coming from

}

// MARK: - DictionaryInitializable
Expand All @@ -30,14 +30,24 @@ protocol AnyInitializable {
extension Dictionary {

@_disfavoredOverload
func value<T, KeyType: RawRepresentable>(for key: KeyType) throws -> T where KeyType.RawValue == Key {
func value<T, KeyType: RawRepresentable>(
for key: KeyType,
file: StaticString = #file,
line: UInt = #line)
throws -> T where KeyType.RawValue == Key
{
guard let value = self[key.rawValue] as? T else {
throw InitializableError.invalidInput
throw InitializableError.invalidInput(file: file, line: line)
}
return value
}

func value<T: AnyInitializable, KeyType: RawRepresentable>(for key: KeyType) throws -> T where KeyType.RawValue == Key {
func value<T: AnyInitializable, KeyType: RawRepresentable>(
for key: KeyType,
file: StaticString = #file,
line: UInt = #line)
throws -> T where KeyType.RawValue == Key
{
if let value = self[key.rawValue] as? T {
return value
}
Expand All @@ -46,7 +56,7 @@ extension Dictionary {
return try T(value: value)
}

throw InitializableError.invalidInput
throw InitializableError.invalidInput(file: file, line: line)
}

}
Expand All @@ -57,7 +67,7 @@ extension [Double]: AnyInitializable {

init(value: Any) throws {
guard let array = value as? [Double] else {
throw InitializableError.invalidInput
throw InitializableError.invalidInput()
}
self = array
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Private/Model/Keyframes/KeyframeGroup.swift
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ extension KeyframeGroup: DictionaryInitializable where T: AnyInitializable {
let value: T = data.startValue ?? previousKeyframeData?.endValue,
let time = data.time
else {
throw InitializableError.invalidInput
throw InitializableError.invalidInput()
}
keyframes.append(Keyframe<T>(
value: value,
Expand Down
11 changes: 9 additions & 2 deletions Sources/Private/Model/Layers/LayerModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ class LayerModel: Codable, DictionaryInitializable {
inFrame = try container.decode(Double.self, forKey: .inFrame)
outFrame = try container.decode(Double.self, forKey: .outFrame)
startTime = try container.decode(Double.self, forKey: .startTime)
transform = try container.decode(Transform.self, forKey: .transform)
transform = try container.decodeIfPresent(Transform.self, forKey: .transform) ?? .default
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of the individual properties in a Transform are optional and have default values, so we can easily make the Transform itself optional with a default

parent = try container.decodeIfPresent(Int.self, forKey: .parent)
blendMode = try container.decodeIfPresent(BlendMode.self, forKey: .blendMode) ?? .normal
masks = try container.decodeIfPresent([Mask].self, forKey: .masks)
Expand All @@ -119,8 +119,15 @@ class LayerModel: Codable, DictionaryInitializable {
inFrame = try dictionary.value(for: CodingKeys.inFrame)
outFrame = try dictionary.value(for: CodingKeys.outFrame)
startTime = try dictionary.value(for: CodingKeys.startTime)
transform = try Transform(dictionary: try dictionary.value(for: CodingKeys.transform))
parent = try? dictionary.value(for: CodingKeys.parent)
if
let transformDictionary: [String: Any] = try dictionary.value(for: CodingKeys.transform),
let transform = try? Transform(dictionary: transformDictionary)
{
self.transform = transform
} else {
transform = .default
}
if
let blendModeRawValue = dictionary[CodingKeys.blendMode.rawValue] as? Int,
let blendMode = BlendMode(rawValue: blendModeRawValue)
Expand Down
2 changes: 1 addition & 1 deletion Sources/Private/Model/Objects/DashPattern.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ final class DashElement: Codable, DictionaryInitializable {
init(dictionary: [String: Any]) throws {
let typeRawValue: String = try dictionary.value(for: CodingKeys.type)
guard let type = DashElementType(rawValue: typeRawValue) else {
throw InitializableError.invalidInput
throw InitializableError.invalidInput()
}
self.type = type
let valueDictionary: [String: Any] = try dictionary.value(for: CodingKeys.value)
Expand Down
53 changes: 46 additions & 7 deletions Sources/Private/Model/Objects/Transform.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ final class Transform: Codable, DictionaryInitializable {
{
self.anchorPoint = anchorPoint
} else {
anchorPoint = KeyframeGroup(LottieVector3D(x: Double(0), y: 0, z: 0))
anchorPoint = Transform.default.anchorPoint
}

if
Expand All @@ -110,7 +110,7 @@ final class Transform: Codable, DictionaryInitializable {
positionY = try KeyframeGroup<LottieVector1D>(dictionary: yDictionary)
position = nil
} else {
position = KeyframeGroup(LottieVector3D(x: Double(0), y: 0, z: 0))
position = Transform.default.position
positionX = nil
positionY = nil
}
Expand All @@ -121,7 +121,7 @@ final class Transform: Codable, DictionaryInitializable {
{
self.scale = scale
} else {
scale = KeyframeGroup(LottieVector3D(x: Double(100), y: 100, z: 100))
scale = Transform.default.scale
}

if
Expand All @@ -130,7 +130,7 @@ final class Transform: Codable, DictionaryInitializable {
{
rotationX = rotation
} else {
rotationX = KeyframeGroup(LottieVector1D(0))
rotationX = Transform.default.rotationX
}

if
Expand All @@ -139,7 +139,7 @@ final class Transform: Codable, DictionaryInitializable {
{
rotationY = rotation
} else {
rotationY = KeyframeGroup(LottieVector1D(0))
rotationY = Transform.default.rotationY
}

if
Expand All @@ -153,7 +153,7 @@ final class Transform: Codable, DictionaryInitializable {
{
rotationZ = rotation
} else {
rotationZ = KeyframeGroup(LottieVector1D(0))
rotationZ = Transform.default.rotationZ
}
rotation = nil
if
Expand All @@ -162,10 +162,34 @@ final class Transform: Codable, DictionaryInitializable {
{
self.opacity = opacity
} else {
opacity = KeyframeGroup(LottieVector1D(100))
opacity = Transform.default.opacity
}
}

init(
anchorPoint: KeyframeGroup<LottieVector3D>,
position: KeyframeGroup<LottieVector3D>?,
positionX: KeyframeGroup<LottieVector1D>?,
positionY: KeyframeGroup<LottieVector1D>?,
scale: KeyframeGroup<LottieVector3D>,
rotationX: KeyframeGroup<LottieVector1D>,
rotationY: KeyframeGroup<LottieVector1D>,
rotationZ: KeyframeGroup<LottieVector1D>,
opacity: KeyframeGroup<LottieVector1D>,
rotation: KeyframeGroup<LottieVector1D>?)
{
self.anchorPoint = anchorPoint
self.position = position
self.positionX = positionX
self.positionY = positionY
self.scale = scale
self.rotationX = rotationX
self.rotationY = rotationY
self.rotationZ = rotationZ
self.opacity = opacity
self.rotation = rotation
}

// MARK: Internal

enum CodingKeys: String, CodingKey {
Expand All @@ -187,6 +211,21 @@ final class Transform: Codable, DictionaryInitializable {
case positionY = "y"
}

/// Default transform values to use if no transform is provided
static var `default`: Transform {
Transform(
anchorPoint: KeyframeGroup(LottieVector3D(x: Double(0), y: 0, z: 0)),
position: KeyframeGroup(LottieVector3D(x: Double(0), y: 0, z: 0)),
positionX: nil,
positionY: nil,
scale: KeyframeGroup(LottieVector3D(x: Double(100), y: 100, z: 100)),
rotationX: KeyframeGroup(LottieVector1D(0)),
rotationY: KeyframeGroup(LottieVector1D(0)),
rotationZ: KeyframeGroup(LottieVector1D(0)),
opacity: KeyframeGroup(LottieVector1D(100)),
rotation: nil)
}

/// The anchor point of the transform.
let anchorPoint: KeyframeGroup<LottieVector3D>

Expand Down
2 changes: 1 addition & 1 deletion Sources/Private/Model/ShapeItems/GradientFill.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ final class GradientFill: ShapeItem {
endPoint = try KeyframeGroup<LottieVector3D>(dictionary: endPointDictionary)
let gradientRawType: Int = try dictionary.value(for: CodingKeys.gradientType)
guard let gradient = GradientType(rawValue: gradientRawType) else {
throw InitializableError.invalidInput
throw InitializableError.invalidInput()
}
gradientType = gradient
if let highlightLengthDictionary = dictionary[CodingKeys.highlightLength.rawValue] as? [String: Any] {
Expand Down
2 changes: 1 addition & 1 deletion Sources/Private/Model/ShapeItems/GradientStroke.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ final class GradientStroke: ShapeItem {
endPoint = try KeyframeGroup<LottieVector3D>(dictionary: endPointDictionary)
let gradientRawType: Int = try dictionary.value(for: CodingKeys.gradientType)
guard let gradient = GradientType(rawValue: gradientRawType) else {
throw InitializableError.invalidInput
throw InitializableError.invalidInput()
}
gradientType = gradient
if let highlightLengthDictionary = dictionary[CodingKeys.highlightLength.rawValue] as? [String: Any] {
Expand Down
2 changes: 1 addition & 1 deletion Sources/Private/Model/ShapeItems/Merge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ final class Merge: ShapeItem {
required init(dictionary: [String: Any]) throws {
let modeRawType: Int = try dictionary.value(for: CodingKeys.mode)
guard let mode = MergeMode(rawValue: modeRawType) else {
throw InitializableError.invalidInput
throw InitializableError.invalidInput()
}
self.mode = mode
try super.init(dictionary: dictionary)
Expand Down
2 changes: 1 addition & 1 deletion Sources/Private/Model/ShapeItems/Star.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ final class Star: ShapeItem {
points = try KeyframeGroup<LottieVector1D>(dictionary: pointsDictionary)
let starTypeRawValue: Int = try dictionary.value(for: CodingKeys.starType)
guard let starType = StarType(rawValue: starTypeRawValue) else {
throw InitializableError.invalidInput
throw InitializableError.invalidInput()
}
self.starType = starType
try super.init(dictionary: dictionary)
Expand Down
2 changes: 1 addition & 1 deletion Sources/Private/Model/ShapeItems/Trim.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ final class Trim: ShapeItem {
offset = try KeyframeGroup<LottieVector1D>(dictionary: offsetDictionary)
let trimTypeRawValue: Int = try dictionary.value(for: CodingKeys.trimType)
guard let trimType = TrimType(rawValue: trimTypeRawValue) else {
throw InitializableError.invalidInput
throw InitializableError.invalidInput()
}
self.trimType = trimType
try super.init(dictionary: dictionary)
Expand Down
4 changes: 2 additions & 2 deletions Sources/Private/Model/Text/TextDocument.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ final class TextDocument: Codable, DictionaryInitializable, AnyInitializable {
fontFamily = try dictionary.value(for: CodingKeys.fontFamily)
let justificationValue: Int = try dictionary.value(for: CodingKeys.justification)
guard let justification = TextJustification(rawValue: justificationValue) else {
throw InitializableError.invalidInput
throw InitializableError.invalidInput()
}
self.justification = justification
tracking = try dictionary.value(for: CodingKeys.tracking)
Expand Down Expand Up @@ -57,7 +57,7 @@ final class TextDocument: Codable, DictionaryInitializable, AnyInitializable {

convenience init(value: Any) throws {
guard let dictionary = value as? [String: Any] else {
throw InitializableError.invalidInput
throw InitializableError.invalidInput()
}
try self.init(dictionary: dictionary)
}
Expand Down
12 changes: 11 additions & 1 deletion Sources/Private/Utility/LottieAnimationSource.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,18 @@ extension LottieAnimationSource {
switch self {
case .lottieAnimation(let animation):
return animation
case .dotLottieFile:
return dotLottieAnimation?.animation
}
}

/// The `DotLottieFile.Animation`, if this is a dotLottie animation
var dotLottieAnimation: DotLottieFile.Animation? {
switch self {
case .lottieAnimation:
return nil
case .dotLottieFile(let dotLottieFile):
return dotLottieFile.animation()?.animation
return dotLottieFile.animation()
Comment on lines -24 to +34
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

}
}
}
Expand Down
Loading
Loading