// // CompatibleAnimationView.swift // Lottie_iOS // // Created by Tyler Hedrick on 3/6/19. // import Foundation #if os(iOS) || os(tvOS) || os(watchOS) || targetEnvironment(macCatalyst) import UIKit /// An Objective-C compatible wrapper around Lottie's Animation class. /// Use in tandem with CompatibleAnimationView when using Lottie in Objective-C @objc public final class CompatibleAnimation: NSObject { // MARK: Lifecycle @objc public init(name: String, bundle: Bundle = Bundle.main) { self.name = name self.bundle = bundle super.init() } // MARK: Internal internal var animation: Animation? { Animation.named(name, bundle: bundle) } @objc static func named(_ name: String) -> CompatibleAnimation { CompatibleAnimation(name: name) } // MARK: Private private let name: String private let bundle: Bundle } /// An Objective-C compatible wrapper around Lottie's AnimationView. @objc public final class CompatibleAnimationView: UIView { // MARK: Lifecycle @objc public init(compatibleAnimation: CompatibleAnimation) { animationView = AnimationView(animation: compatibleAnimation.animation) self.compatibleAnimation = compatibleAnimation super.init(frame: .zero) commonInit() } @objc public override init(frame: CGRect) { animationView = AnimationView() super.init(frame: frame) commonInit() } required init?(coder _: NSCoder) { fatalError("init(coder:) has not been implemented") } // MARK: Public @objc public var compatibleAnimation: CompatibleAnimation? { didSet { animationView.animation = compatibleAnimation?.animation } } @objc public var loopAnimationCount: CGFloat = 0 { didSet { animationView.loopMode = loopAnimationCount == -1 ? .loop : .repeat(Float(loopAnimationCount)) } } @objc public override var contentMode: UIView.ContentMode { set { animationView.contentMode = newValue } get { animationView.contentMode } } @objc public var shouldRasterizeWhenIdle: Bool { set { animationView.shouldRasterizeWhenIdle = newValue } get { animationView.shouldRasterizeWhenIdle } } @objc public var currentProgress: CGFloat { set { animationView.currentProgress = newValue } get { animationView.currentProgress } } @objc public var currentTime: TimeInterval { set { animationView.currentTime = newValue } get { animationView.currentTime } } @objc public var currentFrame: CGFloat { set { animationView.currentFrame = newValue } get { animationView.currentFrame } } @objc public var realtimeAnimationFrame: CGFloat { animationView.realtimeAnimationFrame } @objc public var realtimeAnimationProgress: CGFloat { animationView.realtimeAnimationProgress } @objc public var animationSpeed: CGFloat { set { animationView.animationSpeed = newValue } get { animationView.animationSpeed } } @objc public var respectAnimationFrameRate: Bool { set { animationView.respectAnimationFrameRate = newValue } get { animationView.respectAnimationFrameRate } } @objc public var isAnimationPlaying: Bool { animationView.isAnimationPlaying } @objc public func play() { play(completion: nil) } @objc public func play(completion: ((Bool) -> Void)?) { animationView.play(completion: completion) } @objc public func play( fromProgress: CGFloat, toProgress: CGFloat, completion: ((Bool) -> Void)? = nil) { animationView.play( fromProgress: fromProgress, toProgress: toProgress, loopMode: nil, completion: completion) } @objc public func play( fromFrame: CGFloat, toFrame: CGFloat, completion: ((Bool) -> Void)? = nil) { animationView.play( fromFrame: fromFrame, toFrame: toFrame, loopMode: nil, completion: completion) } @objc public func play( fromMarker: String, toMarker: String, completion: ((Bool) -> Void)? = nil) { animationView.play( fromMarker: fromMarker, toMarker: toMarker, completion: completion) } @objc public func play( marker: String, completion: ((Bool) -> Void)? = nil) { animationView.play( marker: marker, completion: completion) } @objc public func stop() { animationView.stop() } @objc public func pause() { animationView.pause() } @objc public func reloadImages() { animationView.reloadImages() } @objc public func forceDisplayUpdate() { animationView.forceDisplayUpdate() } @objc public func getValue( for keypath: CompatibleAnimationKeypath, atFrame: CGFloat) -> Any? { animationView.getValue( for: keypath.animationKeypath, atFrame: atFrame) } @objc public func logHierarchyKeypaths() { animationView.logHierarchyKeypaths() } @objc public func setColorValue(_ color: UIColor, forKeypath keypath: CompatibleAnimationKeypath) { var red: CGFloat = 0 var green: CGFloat = 0 var blue: CGFloat = 0 var alpha: CGFloat = 0 // TODO: Fix color spaces let colorspace = CGColorSpaceCreateDeviceRGB() let convertedColor = color.cgColor.converted(to: colorspace, intent: .defaultIntent, options: nil) if let components = convertedColor?.components, components.count == 4 { red = components[0] green = components[1] blue = components[2] alpha = components[3] } else { color.getRed(&red, green: &green, blue: &blue, alpha: &alpha) } let valueProvider = ColorValueProvider(Color(r: Double(red), g: Double(green), b: Double(blue), a: Double(alpha))) animationView.setValueProvider(valueProvider, keypath: keypath.animationKeypath) } @objc public func getColorValue(for keypath: CompatibleAnimationKeypath, atFrame: CGFloat) -> UIColor? { let value = animationView.getValue(for: keypath.animationKeypath, atFrame: atFrame) guard let colorValue = value as? Color else { return nil; } return UIColor( red: CGFloat(colorValue.r), green: CGFloat(colorValue.g), blue: CGFloat(colorValue.b), alpha: CGFloat(colorValue.a)) } @objc public func addSubview( _ subview: AnimationSubview, forLayerAt keypath: CompatibleAnimationKeypath) { animationView.addSubview( subview, forLayerAt: keypath.animationKeypath) } @objc public func convert( rect: CGRect, toLayerAt keypath: CompatibleAnimationKeypath?) -> CGRect { animationView.convert( rect, toLayerAt: keypath?.animationKeypath) ?? .zero } @objc public func convert( point: CGPoint, toLayerAt keypath: CompatibleAnimationKeypath?) -> CGPoint { animationView.convert( point, toLayerAt: keypath?.animationKeypath) ?? .zero } @objc public func progressTime(forMarker named: String) -> CGFloat { animationView.progressTime(forMarker: named) ?? 0 } @objc public func frameTime(forMarker named: String) -> CGFloat { animationView.frameTime(forMarker: named) ?? 0 } @objc public func durationFrameTime(forMarker named: String) -> CGFloat { animationView.durationFrameTime(forMarker: named) ?? 0 } // MARK: Private private let animationView: AnimationView private func commonInit() { translatesAutoresizingMaskIntoConstraints = false setUpViews() } private func setUpViews() { animationView.translatesAutoresizingMaskIntoConstraints = false addSubview(animationView) animationView.topAnchor.constraint(equalTo: topAnchor).isActive = true animationView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true animationView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true animationView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true } } #endif