Skip to content

Commit

Permalink
Improve SwiftUI APIs for configuring value providers, image providers…
Browse files Browse the repository at this point in the history
…, etc (#2109)
  • Loading branch information
calda committed Jul 14, 2023
1 parent a3fb3e4 commit 173b119
Show file tree
Hide file tree
Showing 17 changed files with 228 additions and 38 deletions.
4 changes: 2 additions & 2 deletions Example/Example.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@
DEBUG_INFORMATION_FORMAT = dwarf;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = iOS/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand Down Expand Up @@ -460,7 +460,7 @@
CODE_SIGN_STYLE = Automatic;
GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = iOS/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
Expand Down
1 change: 1 addition & 0 deletions Example/Example/AnimationListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ struct AnimationListView: View {
case .animation(let animationName, _):
HStack {
LottieView(animation: .named(animationName, subdirectory: directory))
.imageProvider(.exampleAppSampleImages)
.frame(width: 50, height: 50)
.padding(EdgeInsets(top: 8, leading: 8, bottom: 8, trailing: 8))

Expand Down
7 changes: 7 additions & 0 deletions Example/Example/AnimationPreviewView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ struct AnimationPreviewView: View {
var body: some View {
VStack {
LottieView(animation: .named(animationName))
.imageProvider(.exampleAppSampleImages)
.resizable()
.looping()
}
Expand All @@ -33,3 +34,9 @@ extension Color {
#endif
}
}

extension AnimationImageProvider where Self == FilepathImageProvider {
static var exampleAppSampleImages: FilepathImageProvider {
FilepathImageProvider(filepath: Bundle.main.resourceURL!.appending(path: "Samples/Images"))
}
}
12 changes: 9 additions & 3 deletions Sources/Public/Animation/LottieAnimationLayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -296,16 +296,22 @@ public class LottieAnimationLayer: CALayer {

// MARK: Public

/// The configuration that this `LottieAnimationView` uses when playing its animation
public let configuration: LottieConfiguration

/// Value Providers that have been registered using `setValueProvider(_:keypath:)`
public private(set) var valueProviders = [AnimationKeypath: AnyValueProvider]()

/// A closure called when the animation layer has been loaded.
/// Will inform the receiver the type of rendering engine that is used for the layer.
public var animationLayerDidLoad:((_ animationLayer: LottieAnimationLayer, _ renderingEngine: RenderingEngineOption) -> Void)?

/// The configuration that this `LottieAnimationView` uses when playing its animation
public var configuration: LottieConfiguration {
didSet {
if configuration.renderingEngine != oldValue.renderingEngine {
makeAnimationLayer(usingEngine: configuration.renderingEngine)
}
}
}

/// The underlying CALayer created to display the content.
/// Use this property to change CALayer props like the content's transform, anchor point, etc.
public var animationLayer: CALayer? { rootAnimationLayer }
Expand Down
3 changes: 2 additions & 1 deletion Sources/Public/Animation/LottieAnimationView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,8 @@ open class LottieAnimationView: LottieAnimationViewBase {

/// The configuration that this `LottieAnimationView` uses when playing its animation
public var configuration: LottieConfiguration {
lottieAnimationLayer.configuration
get { lottieAnimationLayer.configuration }
set { lottieAnimationLayer.configuration = newValue }
}

/// Value Providers that have been registered using `setValueProvider(_:keypath:)`
Expand Down
126 changes: 94 additions & 32 deletions Sources/Public/Animation/LottieView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,9 @@ public struct LottieView: UIViewConfiguringSwiftUIView {

// MARK: Lifecycle

public init(
animation: LottieAnimation?,
imageProvider: AnimationImageProvider? = nil,
textProvider: AnimationTextProvider? = nil,
fontProvider: AnimationFontProvider? = nil,
configuration: LottieConfiguration = .shared,
accessibilityLabel: String? = nil)
{
/// Creates a `LottieView` that displays the given animation
public init(animation: LottieAnimation?) {
self.animation = animation
self.imageProvider = imageProvider
self.textProvider = textProvider
self.fontProvider = fontProvider
self.configuration = configuration
self.accessibilityLabel = accessibilityLabel
}

// MARK: Public
Expand All @@ -34,30 +23,18 @@ public struct LottieView: UIViewConfiguringSwiftUIView {
LottieAnimationView(
animation: animation,
imageProvider: imageProvider,
textProvider: textProvider ?? DefaultTextProvider(),
fontProvider: fontProvider ?? DefaultFontProvider(),
textProvider: textProvider,
fontProvider: fontProvider,
configuration: configuration)
}
.sizing(sizing)
.configure { context in
#if os(macOS)
context.view.setAccessibilityElement(accessibilityLabel != nil)
context.view.setAccessibilityLabel(accessibilityLabel)
#else
context.view.isAccessibilityElement = accessibilityLabel != nil
context.view.accessibilityLabel = accessibilityLabel
#endif

// We check referential equality of the animation before updating as updating the
// animation has a side-effect of rebuilding the animation layer, and it would be
// prohibitive to do so on every state update.
if animation !== context.view.animation {
context.view.animation = animation
}

// Technically the image provider, text provider, font provider, and Lottie configuration
// could also need to be updated here, but there's no performant way to check their equality,
// so we assume they are not.
}
.configurations(configurations)
}
Expand Down Expand Up @@ -99,17 +76,102 @@ public struct LottieView: UIViewConfiguringSwiftUIView {
}
}

/// Returns a copy of this view with its accessibility label updated to the given value.
public func accessibilityLabel(_ accessibilityLabel: String?) -> Self {
configure { view in
#if os(macOS)
view.setAccessibilityElement(accessibilityLabel != nil)
view.setAccessibilityLabel(accessibilityLabel)
#else
view.isAccessibilityElement = accessibilityLabel != nil
view.accessibilityLabel = accessibilityLabel
#endif
}
}

/// Returns a copt of this view with its `LottieConfiguration` updated to the given value.
public func configuration(_ configuration: LottieConfiguration) -> Self {
var copy = self
copy.configuration = configuration

copy = copy.configure { view in
if view.configuration != configuration {
view.configuration = configuration
}
}

return copy
}

/// Returns a copy of this view with its image provider updated to the given value.
/// The image provider must be `Equatable` to avoid unnecessary state updates / re-renders.
public func imageProvider<ImageProvider: AnimationImageProvider & Equatable>(_ imageProvider: ImageProvider) -> Self {
var copy = self
copy.imageProvider = imageProvider

copy = copy.configure { view in
if (view.imageProvider as? ImageProvider) != imageProvider {
view.imageProvider = imageProvider
}
}

return copy
}

/// Returns a copy of this view with its text provider updated to the given value.
/// The image provider must be `Equatable` to avoid unnecessary state updates / re-renders.
public func textProvider<TextProvider: AnimationTextProvider & Equatable>(_ textProvider: TextProvider) -> Self {
var copy = self
copy.textProvider = textProvider

copy = copy.configure { view in
if (view.textProvider as? TextProvider) != textProvider {
view.textProvider = textProvider
}
}

return copy
}

/// Returns a copy of this view with its image provider updated to the given value.
/// The image provider must be `Equatable` to avoid unnecessary state updates / re-renders.
public func fontProvider<FontProvider: AnimationFontProvider & Equatable>(_ fontProvider: FontProvider) -> Self {
var copy = self
copy.fontProvider = fontProvider

copy = configure { view in
if (view.fontProvider as? FontProvider) != fontProvider {
view.fontProvider = fontProvider
}
}

return copy
}

/// Returns a copy of this view using the given value provider for the given keypath.
/// The value provider must be `Equatable` to avoid unnecessary state updates / re-renders.
public func valueProvider<ValueProvider: AnyValueProvider & Equatable>(
_ valueProvider: ValueProvider,
for keypath: AnimationKeypath)
-> Self
{
configure { view in
if (view.valueProviders[keypath] as? ValueProvider) != valueProvider {
view.setValueProvider(valueProvider, keypath: keypath)
}
}
}

// MARK: Internal

var configurations = [SwiftUIView<LottieAnimationView, Void>.Configuration]()

// MARK: Private

private let accessibilityLabel: String?
private let animation: LottieAnimation?
private let imageProvider: Lottie.AnimationImageProvider?
private let textProvider: Lottie.AnimationTextProvider?
private let fontProvider: Lottie.AnimationFontProvider?
private let configuration: LottieConfiguration
private var imageProvider: AnimationImageProvider?
private var textProvider: AnimationTextProvider = DefaultTextProvider()
private var fontProvider: AnimationFontProvider = DefaultFontProvider()
private var configuration: LottieConfiguration = .shared
private var sizing = SwiftUIMeasurementContainerStrategy.automatic
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import CoreGraphics
import Foundation

// MARK: - ColorValueProvider

/// A `ValueProvider` that returns a CGColor Value
public final class ColorValueProvider: ValueProvider {

Expand All @@ -18,6 +20,7 @@ public final class ColorValueProvider: ValueProvider {
self.block = block
color = LottieColor(r: 0, g: 0, b: 0, a: 1)
keyframes = nil
identity = UUID()
}

/// Initializes with a single color.
Expand All @@ -26,6 +29,7 @@ public final class ColorValueProvider: ValueProvider {
block = nil
keyframes = nil
hasUpdate = true
identity = color
}

/// Initializes with multiple colors, with timing information
Expand All @@ -34,6 +38,7 @@ public final class ColorValueProvider: ValueProvider {
color = LottieColor(r: 0, g: 0, b: 0, a: 1)
block = nil
hasUpdate = true
identity = keyframes
}

// MARK: Public
Expand Down Expand Up @@ -81,4 +86,13 @@ public final class ColorValueProvider: ValueProvider {

private var block: ColorValueBlock?
private var keyframes: [Keyframe<LottieColor>]?
private var identity: AnyHashable
}

// MARK: Equatable

extension ColorValueProvider: Equatable {
public static func ==(_ lhs: ColorValueProvider, _ rhs: ColorValueProvider) -> Bool {
lhs.identity == rhs.identity
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import CoreGraphics
import Foundation

// MARK: - FloatValueProvider

/// A `ValueProvider` that returns a CGFloat Value
public final class FloatValueProvider: ValueProvider {

Expand All @@ -17,13 +19,15 @@ public final class FloatValueProvider: ValueProvider {
public init(block: @escaping CGFloatValueBlock) {
self.block = block
float = 0
identity = UUID()
}

/// Initializes with a single float.
public init(_ float: CGFloat) {
self.float = float
block = nil
hasUpdate = true
identity = float
}

// MARK: Public
Expand Down Expand Up @@ -67,4 +71,13 @@ public final class FloatValueProvider: ValueProvider {
private var hasUpdate = true

private var block: CGFloatValueBlock?
private var identity: AnyHashable
}

// MARK: Equatable

extension FloatValueProvider: Equatable {
public static func ==(_ lhs: FloatValueProvider, _ rhs: FloatValueProvider) -> Bool {
lhs.identity == rhs.identity
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import CoreGraphics
import Foundation

// MARK: - GradientValueProvider

/// A `ValueProvider` that returns a Gradient Color Value.
public final class GradientValueProvider: ValueProvider {

Expand All @@ -22,6 +24,7 @@ public final class GradientValueProvider: ValueProvider {
locationsBlock = locations
colors = []
self.locations = []
identity = UUID()
}

/// Initializes with an array of colors.
Expand All @@ -31,6 +34,7 @@ public final class GradientValueProvider: ValueProvider {
{
self.colors = colors
self.locations = locations
identity = [AnyHashable(colors), AnyHashable(locations)]
updateValueArray()
hasUpdate = true
}
Expand Down Expand Up @@ -93,6 +97,8 @@ public final class GradientValueProvider: ValueProvider {
private var locationsBlock: ColorLocationsBlock?
private var value: [Double] = []

private let identity: AnyHashable

private func value(from colors: [LottieColor], locations: [Double]) -> [Double] {
var colorValues = [Double]()
var alphaValues = [Double]()
Expand Down Expand Up @@ -120,4 +126,11 @@ public final class GradientValueProvider: ValueProvider {
private func updateValueArray() {
value = value(from: colors, locations: locations)
}

}

extension GradientValueProvider {
public static func ==(_ lhs: GradientValueProvider, _ rhs: GradientValueProvider) -> Bool {
lhs.identity == rhs.identity
}
}
Loading

0 comments on commit 173b119

Please sign in to comment.