From d7a9bccf08dda6b193b56b2803f8992c91c4812c Mon Sep 17 00:00:00 2001 From: p-x9 <50244599+p-x9@users.noreply.github.com> Date: Sun, 7 Apr 2024 00:32:31 +0900 Subject: [PATCH 01/17] move --- .../IndirectlyCodable/{ => Layer}/CAGradientLayer+ICodable.swift | 0 .../IndirectlyCodable/{ => Layer}/CALayer+ICodable.swift | 0 .../{ => Layer}/CAReplicatorLayer+ICodable.swift | 0 .../IndirectlyCodable/{ => Layer}/CAScrollLayer+ICodable.swift | 0 .../IndirectlyCodable/{ => Layer}/CAShapeLayer+ICodable.swift | 0 .../IndirectlyCodable/{ => Layer}/CATextLayer+ICodable.swift | 0 .../IndirectlyCodable/{ => Layer}/CATiledLayer+ICodable.swift | 0 .../IndirectlyCodable/{ => Layer}/CATransformLayer+ICodable.swift | 0 8 files changed, 0 insertions(+), 0 deletions(-) rename Sources/SDCALayer/Extension/IndirectlyCodable/{ => Layer}/CAGradientLayer+ICodable.swift (100%) rename Sources/SDCALayer/Extension/IndirectlyCodable/{ => Layer}/CALayer+ICodable.swift (100%) rename Sources/SDCALayer/Extension/IndirectlyCodable/{ => Layer}/CAReplicatorLayer+ICodable.swift (100%) rename Sources/SDCALayer/Extension/IndirectlyCodable/{ => Layer}/CAScrollLayer+ICodable.swift (100%) rename Sources/SDCALayer/Extension/IndirectlyCodable/{ => Layer}/CAShapeLayer+ICodable.swift (100%) rename Sources/SDCALayer/Extension/IndirectlyCodable/{ => Layer}/CATextLayer+ICodable.swift (100%) rename Sources/SDCALayer/Extension/IndirectlyCodable/{ => Layer}/CATiledLayer+ICodable.swift (100%) rename Sources/SDCALayer/Extension/IndirectlyCodable/{ => Layer}/CATransformLayer+ICodable.swift (100%) diff --git a/Sources/SDCALayer/Extension/IndirectlyCodable/CAGradientLayer+ICodable.swift b/Sources/SDCALayer/Extension/IndirectlyCodable/Layer/CAGradientLayer+ICodable.swift similarity index 100% rename from Sources/SDCALayer/Extension/IndirectlyCodable/CAGradientLayer+ICodable.swift rename to Sources/SDCALayer/Extension/IndirectlyCodable/Layer/CAGradientLayer+ICodable.swift diff --git a/Sources/SDCALayer/Extension/IndirectlyCodable/CALayer+ICodable.swift b/Sources/SDCALayer/Extension/IndirectlyCodable/Layer/CALayer+ICodable.swift similarity index 100% rename from Sources/SDCALayer/Extension/IndirectlyCodable/CALayer+ICodable.swift rename to Sources/SDCALayer/Extension/IndirectlyCodable/Layer/CALayer+ICodable.swift diff --git a/Sources/SDCALayer/Extension/IndirectlyCodable/CAReplicatorLayer+ICodable.swift b/Sources/SDCALayer/Extension/IndirectlyCodable/Layer/CAReplicatorLayer+ICodable.swift similarity index 100% rename from Sources/SDCALayer/Extension/IndirectlyCodable/CAReplicatorLayer+ICodable.swift rename to Sources/SDCALayer/Extension/IndirectlyCodable/Layer/CAReplicatorLayer+ICodable.swift diff --git a/Sources/SDCALayer/Extension/IndirectlyCodable/CAScrollLayer+ICodable.swift b/Sources/SDCALayer/Extension/IndirectlyCodable/Layer/CAScrollLayer+ICodable.swift similarity index 100% rename from Sources/SDCALayer/Extension/IndirectlyCodable/CAScrollLayer+ICodable.swift rename to Sources/SDCALayer/Extension/IndirectlyCodable/Layer/CAScrollLayer+ICodable.swift diff --git a/Sources/SDCALayer/Extension/IndirectlyCodable/CAShapeLayer+ICodable.swift b/Sources/SDCALayer/Extension/IndirectlyCodable/Layer/CAShapeLayer+ICodable.swift similarity index 100% rename from Sources/SDCALayer/Extension/IndirectlyCodable/CAShapeLayer+ICodable.swift rename to Sources/SDCALayer/Extension/IndirectlyCodable/Layer/CAShapeLayer+ICodable.swift diff --git a/Sources/SDCALayer/Extension/IndirectlyCodable/CATextLayer+ICodable.swift b/Sources/SDCALayer/Extension/IndirectlyCodable/Layer/CATextLayer+ICodable.swift similarity index 100% rename from Sources/SDCALayer/Extension/IndirectlyCodable/CATextLayer+ICodable.swift rename to Sources/SDCALayer/Extension/IndirectlyCodable/Layer/CATextLayer+ICodable.swift diff --git a/Sources/SDCALayer/Extension/IndirectlyCodable/CATiledLayer+ICodable.swift b/Sources/SDCALayer/Extension/IndirectlyCodable/Layer/CATiledLayer+ICodable.swift similarity index 100% rename from Sources/SDCALayer/Extension/IndirectlyCodable/CATiledLayer+ICodable.swift rename to Sources/SDCALayer/Extension/IndirectlyCodable/Layer/CATiledLayer+ICodable.swift diff --git a/Sources/SDCALayer/Extension/IndirectlyCodable/CATransformLayer+ICodable.swift b/Sources/SDCALayer/Extension/IndirectlyCodable/Layer/CATransformLayer+ICodable.swift similarity index 100% rename from Sources/SDCALayer/Extension/IndirectlyCodable/CATransformLayer+ICodable.swift rename to Sources/SDCALayer/Extension/IndirectlyCodable/Layer/CATransformLayer+ICodable.swift From 2d98ab04e682b681ebf7e9a897e4801e1fbbb7e4 Mon Sep 17 00:00:00 2001 From: p-x9 <50244599+p-x9@users.noreply.github.com> Date: Sun, 7 Apr 2024 00:34:18 +0900 Subject: [PATCH 02/17] make `CAMediaTimingFunction` conform to `IndirectlyCodable` --- .../Extension/CAMediaTimingFunction+.swift | 35 ++++++++ .../CAMediaTimingFunction+ICodable.swift | 18 ++++ .../Model/JCAMediaTimingFunction.swift | 88 +++++++++++++++++++ 3 files changed, 141 insertions(+) create mode 100644 Sources/SDCALayer/Extension/CAMediaTimingFunction+.swift create mode 100644 Sources/SDCALayer/Extension/IndirectlyCodable/CAMediaTimingFunction+ICodable.swift create mode 100644 Sources/SDCALayer/Model/JCAMediaTimingFunction.swift diff --git a/Sources/SDCALayer/Extension/CAMediaTimingFunction+.swift b/Sources/SDCALayer/Extension/CAMediaTimingFunction+.swift new file mode 100644 index 0000000..748e8fe --- /dev/null +++ b/Sources/SDCALayer/Extension/CAMediaTimingFunction+.swift @@ -0,0 +1,35 @@ +// +// CAMediaTimingFunction+.swift +// +// +// Created by p-x9 on 2024/04/06. +// +// + +import QuartzCore + +extension CAMediaTimingFunction { + var controlPoints: [CGPoint] { + var controlPoints = [CGPoint]() + for index in 1..<3 { + let controlPoint = UnsafeMutablePointer.allocate(capacity: 2) + getControlPoint(at: Int(index), values: controlPoint) + let x: Float = controlPoint[0] + let y: Float = controlPoint[1] + controlPoint.deallocate() + controlPoints.append(CGPoint(x: CGFloat(x), y: CGFloat(y))) + } + return controlPoints + } + + var name: CAMediaTimingFunctionName? { + switch description { + case "linear": .linear + case "easeIn": .easeIn + case "easeOut": .easeOut + case "easeInEaseOut": .easeInEaseOut + case "default": .default + default: nil + } + } +} diff --git a/Sources/SDCALayer/Extension/IndirectlyCodable/CAMediaTimingFunction+ICodable.swift b/Sources/SDCALayer/Extension/IndirectlyCodable/CAMediaTimingFunction+ICodable.swift new file mode 100644 index 0000000..a1bde8a --- /dev/null +++ b/Sources/SDCALayer/Extension/IndirectlyCodable/CAMediaTimingFunction+ICodable.swift @@ -0,0 +1,18 @@ +// +// CAMediaTimingFunction+ICodable.swift +// +// +// Created by p-x9 on 2024/04/07. +// +// + +import QuartzCore +import IndirectlyCodable + +extension CAMediaTimingFunction: IndirectlyCodable { + public typealias Model = JCAMediaTimingFunction + + public func codable() -> Model? { + .init(with: self) + } +} diff --git a/Sources/SDCALayer/Model/JCAMediaTimingFunction.swift b/Sources/SDCALayer/Model/JCAMediaTimingFunction.swift new file mode 100644 index 0000000..f510c14 --- /dev/null +++ b/Sources/SDCALayer/Model/JCAMediaTimingFunction.swift @@ -0,0 +1,88 @@ +// +// JCAMediaTimingFunction.swift +// +// +// Created by p-x9 on 2024/04/07. +// +// + +import QuartzCore +import IndirectlyCodable + +public enum JCAMediaTimingFunction: IndirectlyCodableModel, Codable { + public typealias Target = CAMediaTimingFunction + + case linear + case easeIn + case easeOut + case easeInEaseOut + case `default` + case other(c1: CGPoint, c2: CGPoint) + + public init(with target: Target) { + if let name = target.name { + switch name { + case .linear: self = .linear + case .easeIn: self = .easeIn + case .easeOut: self = .easeOut + case .easeInEaseOut: self = .easeInEaseOut + case .default: self = .default + default: + break + } + } + let points = target.controlPoints + self = .other(c1: points[0], c2: points[1]) + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + if let string = try? container.decode(String.self) { + switch string { + case "linear": self = .linear + case "easeIn": self = .easeIn + case "easeOut": self = .easeOut + case "easeInEaseOut": self = .easeInEaseOut + case "default": self = .default + default: throw DecodingError.dataCorruptedError( + in: container, + debugDescription: "Invalid function name: \(string)" + ) + } + } + let points = try container.decode([CGPoint].self) + self = .other(c1: points[0], c2: points[1]) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + switch self { + case .linear: + try container.encode("linear") + case .easeIn: + try container.encode("easeIn") + case .easeOut: + try container.encode("easeOut") + case .easeInEaseOut: + try container.encode("easeInEaseOut") + case .default: + try container.encode("default") + case let .other(c1: c1, c2: c2): + try container.encode([ + c1, c2 + ]) + } + } + + public func converted() -> CAMediaTimingFunction? { + switch self { + case .linear: return .init(name: .linear) + case .easeIn: return .init(name: .easeIn) + case .easeOut: return .init(name: .easeOut) + case .easeInEaseOut: return .init(name: .easeInEaseOut) + case .default: return .init(name: .default) + case .other(let c1, let c2): + return .init(controlPoints: Float(c1.x), Float(c1.y), Float(c2.x), Float(c2.y)) + } + } +} From 00bd031edc87e591d981d2b42a51645a730f529f Mon Sep 17 00:00:00 2001 From: p-x9 <50244599+p-x9@users.noreply.github.com> Date: Sun, 7 Apr 2024 01:19:52 +0900 Subject: [PATCH 03/17] make `CAAnimation` conform to `IndirectlyCodable` --- .../Animation/CAAnimation+ICodable.swift | 27 ++++++++++ .../Model/Animation/JCAAnimation.swift | 51 +++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 Sources/SDCALayer/Extension/IndirectlyCodable/Animation/CAAnimation+ICodable.swift create mode 100644 Sources/SDCALayer/Model/Animation/JCAAnimation.swift diff --git a/Sources/SDCALayer/Extension/IndirectlyCodable/Animation/CAAnimation+ICodable.swift b/Sources/SDCALayer/Extension/IndirectlyCodable/Animation/CAAnimation+ICodable.swift new file mode 100644 index 0000000..db246bc --- /dev/null +++ b/Sources/SDCALayer/Extension/IndirectlyCodable/Animation/CAAnimation+ICodable.swift @@ -0,0 +1,27 @@ +// +// CAAnimation+ICodable.swift +// +// +// Created by p-x9 on 2024/04/06. +// +// + +import QuartzCore +import IndirectlyCodable + +extension CAAnimation: IndirectlyCodable { + public typealias Model = JCAAnimation + + public func codable() -> JCAAnimation? { + guard let animationClass = NSClassFromString(Self.codableTypeName) as? JCAAnimation.Type else { + return nil + } + + return animationClass.init(with: self) + } + + @objc + open class var codableTypeName: String { + String(reflecting: Model.self) + } +} diff --git a/Sources/SDCALayer/Model/Animation/JCAAnimation.swift b/Sources/SDCALayer/Model/Animation/JCAAnimation.swift new file mode 100644 index 0000000..9a20ffb --- /dev/null +++ b/Sources/SDCALayer/Model/Animation/JCAAnimation.swift @@ -0,0 +1,51 @@ +// +// JCAAnimation.swift +// +// +// Created by p-x9 on 2024/04/06. +// +// + +import Foundation +import QuartzCore +import KeyPathValue +import IndirectlyCodable + +open class JCAAnimation: IndirectlyCodableModel, Codable { + public typealias Target = CAAnimation + + public static var targetTypeName: String { + String(reflecting: Target.self) + } + + static private let propertyMap: PropertyMap = .init([ + .init(\.timingFunction, \.timingFunction), + .init(\.isRemovedOnCompletion, \.isRemovedOnCompletion) + ]) + + public var timingFunction: JCAMediaTimingFunction? + public var isRemovedOnCompletion: Bool? + + public init() {} + + public required convenience init(with object: Target) { + self.init() + applyProperties(with: object) + } + + open func applyProperties(to target: Target) { + Self.propertyMap.apply(to: target, from: self) + } + + open func applyProperties(with target: Target) { + Self.propertyMap.apply(to: self, from: target) + } + + public func converted() -> Target? { + let animation = CAAnimation() + + self.applyProperties(to: animation) + return animation + } +} + From 24918de4a1a1404140cd8eb32143a3b119d2263a Mon Sep 17 00:00:00 2001 From: p-x9 <50244599+p-x9@users.noreply.github.com> Date: Sun, 7 Apr 2024 01:54:13 +0900 Subject: [PATCH 04/17] make `CAValueFunction` conform to `IndirectlyCodable` --- .../CAValueFunction+ICodable.swift | 18 ++++++ .../SDCALayer/Model/JCAValueFunction.swift | 61 +++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 Sources/SDCALayer/Extension/IndirectlyCodable/CAValueFunction+ICodable.swift create mode 100644 Sources/SDCALayer/Model/JCAValueFunction.swift diff --git a/Sources/SDCALayer/Extension/IndirectlyCodable/CAValueFunction+ICodable.swift b/Sources/SDCALayer/Extension/IndirectlyCodable/CAValueFunction+ICodable.swift new file mode 100644 index 0000000..25556ff --- /dev/null +++ b/Sources/SDCALayer/Extension/IndirectlyCodable/CAValueFunction+ICodable.swift @@ -0,0 +1,18 @@ +// +// CAValueFunction+ICodable.swift +// +// +// Created by p-x9 on 2024/04/07. +// +// + +import QuartzCore +import IndirectlyCodable + +extension CAValueFunction: IndirectlyCodable { + public typealias Model = JCAValueFunction + + public func codable() -> Model? { + .init(with: self) + } +} diff --git a/Sources/SDCALayer/Model/JCAValueFunction.swift b/Sources/SDCALayer/Model/JCAValueFunction.swift new file mode 100644 index 0000000..9772604 --- /dev/null +++ b/Sources/SDCALayer/Model/JCAValueFunction.swift @@ -0,0 +1,61 @@ +// +// JCAValueFunction.swift +// +// +// Created by p-x9 on 2024/04/07. +// +// + +import QuartzCore +import IndirectlyCodable + +public enum JCAValueFunction: String, IndirectlyCodableModel, Codable { + public typealias Target = CAValueFunction + + case rotateX + case rotateY + case rotateZ + + case scale + case scaleX + case scaleY + case scaleZ + + case translate + case translateX + case translateY + case translateZ + + public init(with target: CAValueFunction) { + switch target.name { + case .rotateX: self = .rotateX + case .rotateY: self = .rotateY + case .rotateZ: self = .rotateZ + case .scale: self = .scale + case .scaleX: self = .scaleX + case .scaleY: self = .scaleY + case .scaleZ: self = .scaleZ + case .translate: self = .translate + case .translateX: self = .translateX + case .translateY: self = .translateY + case .translateZ: self = .translateZ + default: + fatalError() + } + } + public func converted() -> CAValueFunction? { + switch self { + case .rotateX: .init(name: .rotateX) + case .rotateY: .init(name: .rotateY) + case .rotateZ: .init(name: .rotateZ) + case .scale: .init(name: .scale) + case .scaleX: .init(name: .scaleX) + case .scaleY: .init(name: .scaleY) + case .scaleZ: .init(name: .scaleZ) + case .translate: .init(name: .translate) + case .translateX: .init(name: .translateX) + case .translateY: .init(name: .translateY) + case .translateZ: .init(name: .translateZ) + } + } +} From d7d9873e0061d5b6402468ed39f7fee2b696dfe3 Mon Sep 17 00:00:00 2001 From: p-x9 <50244599+p-x9@users.noreply.github.com> Date: Sun, 7 Apr 2024 01:54:52 +0900 Subject: [PATCH 05/17] make `CAPropertyAnimation` conform to `IndirectlyCodable` --- .../CAPropertyAnimation+ICodable.swift | 17 ++++ .../Model/Animation/JCAAnimation.swift | 2 +- .../Animation/JCAPropertyAnimation.swift | 91 +++++++++++++++++++ 3 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 Sources/SDCALayer/Extension/IndirectlyCodable/Animation/CAPropertyAnimation+ICodable.swift create mode 100644 Sources/SDCALayer/Model/Animation/JCAPropertyAnimation.swift diff --git a/Sources/SDCALayer/Extension/IndirectlyCodable/Animation/CAPropertyAnimation+ICodable.swift b/Sources/SDCALayer/Extension/IndirectlyCodable/Animation/CAPropertyAnimation+ICodable.swift new file mode 100644 index 0000000..376536d --- /dev/null +++ b/Sources/SDCALayer/Extension/IndirectlyCodable/Animation/CAPropertyAnimation+ICodable.swift @@ -0,0 +1,17 @@ +// +// CAPropertyAnimation+ICodable.swift +// +// +// Created by p-x9 on 2024/04/07. +// +// + +import QuartzCore + +extension CAPropertyAnimation { + public typealias Model = JCAPropertyAnimation + + open override class var codableTypeName: String { + String(reflecting: Model.self) + } +} diff --git a/Sources/SDCALayer/Model/Animation/JCAAnimation.swift b/Sources/SDCALayer/Model/Animation/JCAAnimation.swift index 9a20ffb..4d4853e 100644 --- a/Sources/SDCALayer/Model/Animation/JCAAnimation.swift +++ b/Sources/SDCALayer/Model/Animation/JCAAnimation.swift @@ -14,7 +14,7 @@ import IndirectlyCodable open class JCAAnimation: IndirectlyCodableModel, Codable { public typealias Target = CAAnimation - public static var targetTypeName: String { + open class var targetTypeName: String { String(reflecting: Target.self) } diff --git a/Sources/SDCALayer/Model/Animation/JCAPropertyAnimation.swift b/Sources/SDCALayer/Model/Animation/JCAPropertyAnimation.swift new file mode 100644 index 0000000..be7a8d8 --- /dev/null +++ b/Sources/SDCALayer/Model/Animation/JCAPropertyAnimation.swift @@ -0,0 +1,91 @@ +// +// JCAPropertyAnimation.swift +// +// +// Created by p-x9 on 2024/04/07. +// +// + +import QuartzCore + +open class JCAPropertyAnimation: JCAAnimation { + public typealias Target = CAPropertyAnimation + + private enum CodingKeys: String, CodingKey { + case keyPath + case isAdditive + case isCumulative + case valueFunction + } + + open override class var targetTypeName: String { + String(reflecting: Target.self) + } + + static private let propertyMap: PropertyMap = .init([ + .init(\.keyPath, \.keyPath), + .init(\.isAdditive, \.isAdditive), + .init(\.isCumulative, \.isCumulative), + .init(\.valueFunction, \.valueFunction), + ]) + + public var keyPath: String? + public var isAdditive: Bool? + public var isCumulative: Bool? + public var valueFunction: JCAValueFunction? + + public override init() { + super.init() + } + + public required init(from decoder: Decoder) throws { + try super.init(from: decoder) + + let container = try decoder.container(keyedBy: CodingKeys.self) + + keyPath = try container.decodeIfPresent(String.self, forKey: .keyPath) + isAdditive = try container.decodeIfPresent(Bool.self, forKey: .isAdditive) + isCumulative = try container.decodeIfPresent(Bool.self, forKey: .isCumulative) + valueFunction = try container.decodeIfPresent(JCAValueFunction.self, forKey: .valueFunction) + } + + open override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encodeIfPresent(keyPath, forKey: .keyPath) + try container.encodeIfPresent(isAdditive, forKey: .isAdditive) + try container.encodeIfPresent(isCumulative, forKey: .isCumulative) + try container.encodeIfPresent(valueFunction, forKey: .valueFunction) + } + + public required convenience init(with object: Target) { + self.init() + + applyProperties(with: object) + } + + open override func applyProperties(to target: CAAnimation) { + super.applyProperties(to: target) + + guard let target = target as? CAPropertyAnimation else { return } + + Self.propertyMap.apply(to: target, from: self) + } + + open override func applyProperties(with target: CAAnimation) { + super.applyProperties(with: target) + + guard let target = target as? CAPropertyAnimation else { return } + + Self.propertyMap.apply(to: self, from: target) + } + + open override func converted() -> CAAnimation? { + let animation = CAPropertyAnimation() + + self.applyProperties(to: animation) + return animation + } +} From 1279b068b531d6119ba21aabb1d26909e8acf536 Mon Sep 17 00:00:00 2001 From: p-x9 <50244599+p-x9@users.noreply.github.com> Date: Sun, 7 Apr 2024 04:14:58 +0900 Subject: [PATCH 06/17] implemented `JCAAnimationAny` --- Sources/SDCALayer/Model/JCAAnimationAny.swift | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 Sources/SDCALayer/Model/JCAAnimationAny.swift diff --git a/Sources/SDCALayer/Model/JCAAnimationAny.swift b/Sources/SDCALayer/Model/JCAAnimationAny.swift new file mode 100644 index 0000000..afee738 --- /dev/null +++ b/Sources/SDCALayer/Model/JCAAnimationAny.swift @@ -0,0 +1,117 @@ +// +// JCAAnimationAny.swift +// +// +// Created by p-x9 on 2024/04/07. +// +// + +import QuartzCore +import IndirectlyCodable + +public struct JCAAnimationAny: Codable { + public enum ValueType: String, Codable { + case int + case float + case point + case rect + case color + } + var type: ValueType + var value: Any + + public enum CodingKeys: String, CodingKey { + case type + case value + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + type = try container.decode(ValueType.self, forKey: .type) + var value: Any + switch type { + case .int: + value = try container.decode(Int.self, forKey: .value) + case .float: + value = try container.decode(Double.self, forKey: .value) + case .point: + value = try container.decode(CGPoint.self, forKey: .value) + case .rect: + value = try container.decode(CGRect.self, forKey: .value) + case .color: + value = try container.decode(JCGColor.self, forKey: .value) + } + if let v = value as? any IndirectlyCodableModel, + let converted = v.converted() { + value = converted + } + self.value = value + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(type, forKey: .type) + + switch value { + case let v as any FixedWidthInteger: + try container.encode(Int(v), forKey: .value) + case let v as any BinaryFloatingPoint: + try container.encode(Double(v), forKey: .value) + case let v as CGPoint: + try container.encode(v, forKey: .value) + case let v as CGRect: + try container.encode(v, forKey: .value) + case let v as CGColor where CFGetTypeID(v) == CGColor.typeID: + try container.encodeIfPresent(v.codable(), forKey: .value) + default: + throw EncodingError.invalidValue( + value, + .init(codingPath: [CodingKeys.value], debugDescription: "Unsupported Value Type") + ) + } + } +} + +extension JCAAnimationAny { + init?(_ value: Any?) { + guard var value = value else { return nil } + value = value as Any + if let v = value as? any IndirectlyCodable, + let codable = v.codable() { + value = codable + } + + switch value { + case let v as any FixedWidthInteger: + self.value = v + self.type = .int + + case let v as any BinaryFloatingPoint: + self.value = v + self.type = .float + + case let v as CGPoint: + self.value = v + self.type = .point + + case let v as CGRect: + self.value = v + self.type = .rect + + case let v as NSInteger: + self.value = Int(v) + self.type = .int + + case let v as NSNumber: + self.value = v.doubleValue + self.type = .float + + case let v as CGColor: + self.value = v + self.type = .color + default: + return nil + } + } +} + From 50eb6f73daa1fae739803dfe0d80cd7aa092d646 Mon Sep 17 00:00:00 2001 From: p-x9 <50244599+p-x9@users.noreply.github.com> Date: Sun, 7 Apr 2024 04:19:45 +0900 Subject: [PATCH 07/17] make `CABasicAnimation` conform to `IndirectlyCodable` --- .../Animation/CABasicAnimation+ICodable.swift | 18 +++++ .../Model/Animation/JCABasicAnimation.swift | 80 +++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 Sources/SDCALayer/Extension/IndirectlyCodable/Animation/CABasicAnimation+ICodable.swift create mode 100644 Sources/SDCALayer/Model/Animation/JCABasicAnimation.swift diff --git a/Sources/SDCALayer/Extension/IndirectlyCodable/Animation/CABasicAnimation+ICodable.swift b/Sources/SDCALayer/Extension/IndirectlyCodable/Animation/CABasicAnimation+ICodable.swift new file mode 100644 index 0000000..54341b9 --- /dev/null +++ b/Sources/SDCALayer/Extension/IndirectlyCodable/Animation/CABasicAnimation+ICodable.swift @@ -0,0 +1,18 @@ +// +// CABasicAnimation+ICodable.swift +// +// +// Created by p-x9 on 2024/04/07. +// +// + +import QuartzCore + +extension CABasicAnimation { + public typealias Model = JCABasicAnimation + + open override class var codableTypeName: String { + String(reflecting: Model.self) + } +} + diff --git a/Sources/SDCALayer/Model/Animation/JCABasicAnimation.swift b/Sources/SDCALayer/Model/Animation/JCABasicAnimation.swift new file mode 100644 index 0000000..3a8671a --- /dev/null +++ b/Sources/SDCALayer/Model/Animation/JCABasicAnimation.swift @@ -0,0 +1,80 @@ +// +// JCABasicAnimation.swift +// +// +// Created by p-x9 on 2024/04/07. +// +// + +import QuartzCore + +open class JCABasicAnimation: JCAPropertyAnimation { + public typealias Target = CABasicAnimation + + private enum CodingKeys: String, CodingKey { + case fromValue + case toValue + case byValue + } + + open override class var targetTypeName: String { + String(reflecting: Target.self) + } + + public var fromValue: JCAAnimationAny? + public var toValue: JCAAnimationAny? + public var byValue: JCAAnimationAny? + + public override init() { + super.init() + } + + public required init(from decoder: Decoder) throws { + try super.init(from: decoder) + + let container = try decoder.container(keyedBy: CodingKeys.self) + fromValue = try container.decodeIfPresent(JCAAnimationAny.self, forKey: .fromValue) + toValue = try container.decodeIfPresent(JCAAnimationAny.self, forKey: .toValue) + byValue = try container.decodeIfPresent(JCAAnimationAny.self, forKey: .byValue) + } + + open override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(fromValue, forKey: .fromValue) + try container.encodeIfPresent(toValue, forKey: .toValue) + try container.encodeIfPresent(byValue, forKey: .byValue) + } + + public required convenience init(with object: Target) { + self.init() + + applyProperties(with: object) + } + + open override func applyProperties(to target: CAAnimation) { + super.applyProperties(to: target) + + guard let target = target as? CABasicAnimation else { return } + target.fromValue = fromValue?.value + target.toValue = toValue?.value + target.byValue = byValue?.value + } + + open override func applyProperties(with target: CAAnimation) { + super.applyProperties(with: target) + + guard let target = target as? CABasicAnimation else { return } + fromValue = .init(target.fromValue) + toValue = .init(target.toValue) + byValue = .init(target.byValue) + } + + open override func converted() -> CAAnimation? { + let animation = CABasicAnimation() + + self.applyProperties(to: animation) + return animation + } +} From 6cd9e9f49d8f6efbe54d5b126ae15cb3a97f350d Mon Sep 17 00:00:00 2001 From: p-x9 <50244599+p-x9@users.noreply.github.com> Date: Sun, 7 Apr 2024 04:49:31 +0900 Subject: [PATCH 08/17] fix color space --- Sources/SDCALayer/Extension/CGColor+.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SDCALayer/Extension/CGColor+.swift b/Sources/SDCALayer/Extension/CGColor+.swift index bf813ef..3d2bff0 100644 --- a/Sources/SDCALayer/Extension/CGColor+.swift +++ b/Sources/SDCALayer/Extension/CGColor+.swift @@ -35,7 +35,7 @@ extension CGColor { } private var rgbaComponents: [CGFloat] { - guard let colorSpace = CGColorSpace(name: CGColorSpace.extendedSRGB), + guard let colorSpace = CGColorSpace(name: CGColorSpace.sRGB), let converted = self.converted(to: colorSpace, intent: .defaultIntent, options: nil), let components = converted.components, converted.numberOfComponents == 4 else { From d762b2f5b2ea5d47857e2d2f8a06a50f5771594d Mon Sep 17 00:00:00 2001 From: p-x9 <50244599+p-x9@users.noreply.github.com> Date: Sun, 7 Apr 2024 04:50:39 +0900 Subject: [PATCH 09/17] fix initialization of JCAMediaTimingFunction --- .../SDCALayer/Model/JCAMediaTimingFunction.swift | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Sources/SDCALayer/Model/JCAMediaTimingFunction.swift b/Sources/SDCALayer/Model/JCAMediaTimingFunction.swift index f510c14..1e28a71 100644 --- a/Sources/SDCALayer/Model/JCAMediaTimingFunction.swift +++ b/Sources/SDCALayer/Model/JCAMediaTimingFunction.swift @@ -28,11 +28,13 @@ public enum JCAMediaTimingFunction: IndirectlyCodableModel, Codable { case .easeInEaseOut: self = .easeInEaseOut case .default: self = .default default: - break + let points = target.controlPoints + self = .other(c1: points[0], c2: points[1]) } + } else { + let points = target.controlPoints + self = .other(c1: points[0], c2: points[1]) } - let points = target.controlPoints - self = .other(c1: points[0], c2: points[1]) } public init(from decoder: Decoder) throws { @@ -49,9 +51,10 @@ public enum JCAMediaTimingFunction: IndirectlyCodableModel, Codable { debugDescription: "Invalid function name: \(string)" ) } + } else { + let points = try container.decode([CGPoint].self) + self = .other(c1: points[0], c2: points[1]) } - let points = try container.decode([CGPoint].self) - self = .other(c1: points[0], c2: points[1]) } public func encode(to encoder: Encoder) throws { From 873b10a7aca82b9185dece73872cee7aa12c8524 Mon Sep 17 00:00:00 2001 From: p-x9 <50244599+p-x9@users.noreply.github.com> Date: Sun, 7 Apr 2024 05:13:45 +0900 Subject: [PATCH 10/17] add `CAAnimationConvertible` --- .../Animation/CABasicAnimation+ICodable.swift | 1 - .../Model/Animation/JCAAnimation.swift | 4 ++-- .../Model/Animation/JCABasicAnimation.swift | 2 +- .../Animation/JCAPropertyAnimation.swift | 2 +- Sources/SDCALayer/Model/JCAAnimationAny.swift | 1 - .../Protocol/CAAnimationConvertible.swift | 20 +++++++++++++++++++ 6 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 Sources/SDCALayer/Protocol/CAAnimationConvertible.swift diff --git a/Sources/SDCALayer/Extension/IndirectlyCodable/Animation/CABasicAnimation+ICodable.swift b/Sources/SDCALayer/Extension/IndirectlyCodable/Animation/CABasicAnimation+ICodable.swift index 54341b9..ef6772b 100644 --- a/Sources/SDCALayer/Extension/IndirectlyCodable/Animation/CABasicAnimation+ICodable.swift +++ b/Sources/SDCALayer/Extension/IndirectlyCodable/Animation/CABasicAnimation+ICodable.swift @@ -15,4 +15,3 @@ extension CABasicAnimation { String(reflecting: Model.self) } } - diff --git a/Sources/SDCALayer/Model/Animation/JCAAnimation.swift b/Sources/SDCALayer/Model/Animation/JCAAnimation.swift index 4d4853e..4e548f7 100644 --- a/Sources/SDCALayer/Model/Animation/JCAAnimation.swift +++ b/Sources/SDCALayer/Model/Animation/JCAAnimation.swift @@ -11,7 +11,7 @@ import QuartzCore import KeyPathValue import IndirectlyCodable -open class JCAAnimation: IndirectlyCodableModel, Codable { +open class JCAAnimation: CAAnimationConvertible, Codable { public typealias Target = CAAnimation open class var targetTypeName: String { @@ -41,7 +41,7 @@ open class JCAAnimation: IndirectlyCodableModel, Codable { Self.propertyMap.apply(to: self, from: target) } - public func converted() -> Target? { + public func convertToAnimation() -> Target? { let animation = CAAnimation() self.applyProperties(to: animation) diff --git a/Sources/SDCALayer/Model/Animation/JCABasicAnimation.swift b/Sources/SDCALayer/Model/Animation/JCABasicAnimation.swift index 3a8671a..aa1ab92 100644 --- a/Sources/SDCALayer/Model/Animation/JCABasicAnimation.swift +++ b/Sources/SDCALayer/Model/Animation/JCABasicAnimation.swift @@ -71,7 +71,7 @@ open class JCABasicAnimation: JCAPropertyAnimation { byValue = .init(target.byValue) } - open override func converted() -> CAAnimation? { + open override func convertToAnimation() -> CAAnimation? { let animation = CABasicAnimation() self.applyProperties(to: animation) diff --git a/Sources/SDCALayer/Model/Animation/JCAPropertyAnimation.swift b/Sources/SDCALayer/Model/Animation/JCAPropertyAnimation.swift index be7a8d8..53d2fd6 100644 --- a/Sources/SDCALayer/Model/Animation/JCAPropertyAnimation.swift +++ b/Sources/SDCALayer/Model/Animation/JCAPropertyAnimation.swift @@ -82,7 +82,7 @@ open class JCAPropertyAnimation: JCAAnimation { Self.propertyMap.apply(to: self, from: target) } - open override func converted() -> CAAnimation? { + open override func convertToAnimation() -> CAAnimation? { let animation = CAPropertyAnimation() self.applyProperties(to: animation) diff --git a/Sources/SDCALayer/Model/JCAAnimationAny.swift b/Sources/SDCALayer/Model/JCAAnimationAny.swift index afee738..0437a10 100644 --- a/Sources/SDCALayer/Model/JCAAnimationAny.swift +++ b/Sources/SDCALayer/Model/JCAAnimationAny.swift @@ -114,4 +114,3 @@ extension JCAAnimationAny { } } } - diff --git a/Sources/SDCALayer/Protocol/CAAnimationConvertible.swift b/Sources/SDCALayer/Protocol/CAAnimationConvertible.swift new file mode 100644 index 0000000..33e2b0a --- /dev/null +++ b/Sources/SDCALayer/Protocol/CAAnimationConvertible.swift @@ -0,0 +1,20 @@ +// +// CAAnimationConvertible.swift +// +// +// Created by p-x9 on 2024/04/07. +// +// + +import QuartzCore + +public protocol CAAnimationConvertible: IndirectlyCodableModel where Target: CAAnimation { + func convertToAnimation() -> Target? +} + +public extension CAAnimationConvertible { + func converted() -> Target? { + self.convertToAnimation() + } +} + From a06efc5ee73988cbace0e5ace11988132272819f Mon Sep 17 00:00:00 2001 From: p-x9 <50244599+p-x9@users.noreply.github.com> Date: Sun, 7 Apr 2024 05:14:08 +0900 Subject: [PATCH 11/17] implemented `JCAAnimationGroup` --- .../Animation/CAAnimationGroup+ICodable.swift | 17 +++++ .../Model/Animation/JCAAnimationGroup.swift | 72 +++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 Sources/SDCALayer/Extension/IndirectlyCodable/Animation/CAAnimationGroup+ICodable.swift create mode 100644 Sources/SDCALayer/Model/Animation/JCAAnimationGroup.swift diff --git a/Sources/SDCALayer/Extension/IndirectlyCodable/Animation/CAAnimationGroup+ICodable.swift b/Sources/SDCALayer/Extension/IndirectlyCodable/Animation/CAAnimationGroup+ICodable.swift new file mode 100644 index 0000000..1898858 --- /dev/null +++ b/Sources/SDCALayer/Extension/IndirectlyCodable/Animation/CAAnimationGroup+ICodable.swift @@ -0,0 +1,17 @@ +// +// CAAnimationGroup+ICodable.swift +// +// +// Created by p-x9 on 2024/04/07. +// +// + +import QuartzCore + +extension CAAnimationGroup { + public typealias Model = JCAAnimationGroup + + open override class var codableTypeName: String { + String(reflecting: Model.self) + } +} diff --git a/Sources/SDCALayer/Model/Animation/JCAAnimationGroup.swift b/Sources/SDCALayer/Model/Animation/JCAAnimationGroup.swift new file mode 100644 index 0000000..b59dd9c --- /dev/null +++ b/Sources/SDCALayer/Model/Animation/JCAAnimationGroup.swift @@ -0,0 +1,72 @@ +// +// JCAAnimationGroup.swift +// +// +// Created by p-x9 on 2024/04/07. +// +// + +import QuartzCore + +open class JCAAnimationGroup: JCAAnimation { + public typealias Target = CAAnimationGroup + + private enum CodingKeys: String, CodingKey { + case animations + } + + open override class var targetTypeName: String { + String(reflecting: Target.self) + } + + public var animations: [SDCAAnimation]? + + public override init() { + super.init() + } + + public required init(from decoder: Decoder) throws { + try super.init(from: decoder) + + let container = try decoder.container(keyedBy: CodingKeys.self) + animations = try container.decodeIfPresent([SDCAAnimation].self, forKey: .animations) + } + + open override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(animations, forKey: .animations) + } + + public required convenience init(with object: Target) { + self.init() + + applyProperties(with: object) + } + + open override func applyProperties(to target: CAAnimation) { + super.applyProperties(to: target) + + guard let target = target as? CAAnimationGroup else { return } + target.animations = animations?.compactMap { + $0.converted() + } + } + + open override func applyProperties(with target: CAAnimation) { + super.applyProperties(with: target) + + guard let target = target as? CAAnimationGroup else { return } + animations = target.animations?.compactMap { + .init(model: $0.codable()) + } + } + + open override func convertToAnimation() -> CAAnimation? { + let animation = CAAnimationGroup() + + self.applyProperties(to: animation) + return animation + } +} From f04c2fd80a44eea809dbdbe22815078645952226 Mon Sep 17 00:00:00 2001 From: p-x9 <50244599+p-x9@users.noreply.github.com> Date: Sun, 7 Apr 2024 07:47:32 +0900 Subject: [PATCH 12/17] CALayerConvertible.Target must be a subclass of CALayer --- Sources/SDCALayer/Protocol/CALayerConvertible.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/SDCALayer/Protocol/CALayerConvertible.swift b/Sources/SDCALayer/Protocol/CALayerConvertible.swift index 3868782..cf0b724 100644 --- a/Sources/SDCALayer/Protocol/CALayerConvertible.swift +++ b/Sources/SDCALayer/Protocol/CALayerConvertible.swift @@ -6,9 +6,9 @@ // // -import Foundation +import QuartzCore -public protocol CALayerConvertible: IndirectlyCodableModel { +public protocol CALayerConvertible: IndirectlyCodableModel where Target: CALayer { func convertToLayer() -> Target? } From 0d34800f127a4beae4d7a851efc72081ec8b8252 Mon Sep 17 00:00:00 2001 From: p-x9 <50244599+p-x9@users.noreply.github.com> Date: Sun, 7 Apr 2024 21:58:01 +0900 Subject: [PATCH 13/17] implemented `SDCAAnimation` --- Sources/SDCALayer/Model/Layer/JCALayer.swift | 23 +++++ Sources/SDCALayer/SDCAAnimation.swift | 94 ++++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 Sources/SDCALayer/SDCAAnimation.swift diff --git a/Sources/SDCALayer/Model/Layer/JCALayer.swift b/Sources/SDCALayer/Model/Layer/JCALayer.swift index 8aa2e4d..fade2df 100644 --- a/Sources/SDCALayer/Model/Layer/JCALayer.swift +++ b/Sources/SDCALayer/Model/Layer/JCALayer.swift @@ -137,6 +137,8 @@ open class JCALayer: CALayerConvertible, Codable { public var name: String? + public var animations: [String: SDCAAnimation]? + public init() {} public required convenience init(with object: CALayer) { @@ -153,6 +155,14 @@ open class JCALayer: CALayerConvertible, Codable { .forEach { target.addSublayer($0) } + + if let animations { + for (key, animation) in animations { + if let animation = animation.converted() { + target.add(animation, forKey: key) + } + } + } } open func applyProperties(with target: CALayer) { @@ -162,6 +172,19 @@ open class JCALayer: CALayerConvertible, Codable { self.sublayers = target.sublayers?.compactMap { SDCALayer(model: $0.codable()) } + + if let animationKeys = target.animationKeys(), + !animationKeys.isEmpty { + var animations: [String: SDCAAnimation] = [:] + for key in animationKeys { + guard let animation = target.animation(forKey: key), + let model = animation.codable() else { + continue + } + animations[key] = .init(model: model) + } + self.animations = animations + } } open func convertToLayer() -> CALayer? { diff --git a/Sources/SDCALayer/SDCAAnimation.swift b/Sources/SDCALayer/SDCAAnimation.swift new file mode 100644 index 0000000..f84268e --- /dev/null +++ b/Sources/SDCALayer/SDCAAnimation.swift @@ -0,0 +1,94 @@ +// +// SDCAAnimation.swift +// +// +// Created by p-x9 on 2024/04/07. +// +// + +import QuartzCore +import IndirectlyCodable + +public class SDCAAnimation: CAAnimationConvertible { + public typealias Target = CAAnimation + + enum CodingKeys: String, CodingKey { + case `class`, animationModel + } + + public var `class`: String? + public var animationModel: (any CAAnimationConvertible)? + + static func animationModelClass(for className: String?) -> Codable.Type? { + guard let className, + let layerClass = NSClassFromString(className) as? any IndirectlyCodable.Type, + let layerModelClass = NSClassFromString(layerClass.codableTypeName) as? any Codable.Type else { + return nil + } + return layerModelClass + } + + public static func load(fromJSON json: String) -> SDCAAnimation? { + SDCAAnimation.value(fromJSON: json) + } + + public static func load(fromYAML yaml: String) -> SDCAAnimation? { + SDCAAnimation.value(fromYAML: yaml) + } + + public var json: String? { + self.jsonString + } + + public var yaml: String? { + self.yamlString + } + + public init(model: (any CAAnimationConvertible)) { + self.class = type(of: model).targetTypeName + self.animationModel = model + } + + public convenience init?(model: (any CAAnimationConvertible)?) { + guard let model else { return nil } + + self.init(model: model) + } + + public init(class: String?, model: any CAAnimationConvertible) { + self.class = `class` + self.animationModel = model + } + + public required convenience init(with object: CAAnimation) { + guard let model = object.codable() else { + fatalError("not supported Layer type") + } + self.init(model: model) + } + + required public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + `class` = try container.decodeIfPresent(String.self, forKey: .class) + guard let layerClass = Self.animationModelClass(for: `class`) as? any CAAnimationConvertible.Type else { + return + } + + animationModel = try container.decodeIfPresent(layerClass, forKey: .animationModel) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(`class`, forKey: .class) + + guard let animationModel else { + return + } + try container.encode(animationModel, forKey: .animationModel) + } + + public func convertToAnimation() -> Target? { + animationModel?.converted() as? CAAnimation + } +} From 26b18bb81e4fe51560a1b1899aa5bb73734f26f9 Mon Sep 17 00:00:00 2001 From: p-x9 <50244599+p-x9@users.noreply.github.com> Date: Mon, 8 Apr 2024 03:40:32 +0900 Subject: [PATCH 14/17] Add properties defined in `CAMediaTiming` --- .../Animation/CAAnimation+ICodable.swift | 4 +++ .../Model/Animation/JCAAnimation.swift | 27 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/Sources/SDCALayer/Extension/IndirectlyCodable/Animation/CAAnimation+ICodable.swift b/Sources/SDCALayer/Extension/IndirectlyCodable/Animation/CAAnimation+ICodable.swift index db246bc..e253cee 100644 --- a/Sources/SDCALayer/Extension/IndirectlyCodable/Animation/CAAnimation+ICodable.swift +++ b/Sources/SDCALayer/Extension/IndirectlyCodable/Animation/CAAnimation+ICodable.swift @@ -25,3 +25,7 @@ extension CAAnimation: IndirectlyCodable { String(reflecting: Model.self) } } + +extension CAMediaTimingFillMode: RawIndirectlyCodable { + public typealias Model = JCAMediaTimingFillMode +} diff --git a/Sources/SDCALayer/Model/Animation/JCAAnimation.swift b/Sources/SDCALayer/Model/Animation/JCAAnimation.swift index 4e548f7..c6b1e38 100644 --- a/Sources/SDCALayer/Model/Animation/JCAAnimation.swift +++ b/Sources/SDCALayer/Model/Animation/JCAAnimation.swift @@ -19,10 +19,29 @@ open class JCAAnimation: CAAnimationConvertible, Codable { } static private let propertyMap: PropertyMap = .init([ + .init(\.beginTime, \.beginTime), + .init(\.duration, \.duration), + .init(\.speed, \.speed), + .init(\.timeOffset, \.timeOffset), + .init(\.repeatCount, \.repeatCount), + .init(\.repeatDuration, \.repeatDuration), + .init(\.autoreverses, \.autoreverses), + .init(\.fillMode, \.fillMode), + .init(\.timingFunction, \.timingFunction), .init(\.isRemovedOnCompletion, \.isRemovedOnCompletion) ]) + /* CAMediaTiming */ + public var beginTime: CFTimeInterval? + public var duration: CFTimeInterval? + public var speed: Float? + public var timeOffset: CFTimeInterval? + public var repeatCount: Float? + public var repeatDuration: CFTimeInterval? + public var autoreverses: Bool? + public var fillMode: JCAMediaTimingFillMode? + public var timingFunction: JCAMediaTimingFunction? public var isRemovedOnCompletion: Bool? @@ -49,3 +68,11 @@ open class JCAAnimation: CAAnimationConvertible, Codable { } } +public final class JCAMediaTimingFillMode: RawIndirectlyCodableModel { + public typealias Target = CAMediaTimingFillMode + + public var rawValue: Target.RawValue + public required init(rawValue: Target.RawValue) { + self.rawValue = rawValue + } +} From 0eb8f04cd6ec7a3f885b2d93e2732b6781d32d0b Mon Sep 17 00:00:00 2001 From: p-x9 <50244599+p-x9@users.noreply.github.com> Date: Tue, 9 Apr 2024 12:54:59 +0900 Subject: [PATCH 15/17] Support `CAKeyframeAnimation` --- .../CAKeyframeAnimation+ICodable.swift | 25 +++ .../Animation/JCAKeyframeAnimation.swift | 150 ++++++++++++++++++ Sources/SDCALayer/Util/PropertyMap.swift | 8 + 3 files changed, 183 insertions(+) create mode 100644 Sources/SDCALayer/Extension/IndirectlyCodable/Animation/CAKeyframeAnimation+ICodable.swift create mode 100644 Sources/SDCALayer/Model/Animation/JCAKeyframeAnimation.swift diff --git a/Sources/SDCALayer/Extension/IndirectlyCodable/Animation/CAKeyframeAnimation+ICodable.swift b/Sources/SDCALayer/Extension/IndirectlyCodable/Animation/CAKeyframeAnimation+ICodable.swift new file mode 100644 index 0000000..fb1f815 --- /dev/null +++ b/Sources/SDCALayer/Extension/IndirectlyCodable/Animation/CAKeyframeAnimation+ICodable.swift @@ -0,0 +1,25 @@ +// +// CAKeyframeAnimation+ICodable.swift +// +// +// Created by p-x9 on 2024/04/07. +// +// + +import QuartzCore + +extension CAKeyframeAnimation { + public typealias Model = JCAKeyframeAnimation + + open override class var codableTypeName: String { + String(reflecting: Model.self) + } +} + +extension CAAnimationCalculationMode: RawIndirectlyCodable { + public typealias Model = JCAAnimationCalculationMode +} + +extension CAAnimationRotationMode: RawIndirectlyCodable { + public typealias Model = JCAAnimationRotationMode +} diff --git a/Sources/SDCALayer/Model/Animation/JCAKeyframeAnimation.swift b/Sources/SDCALayer/Model/Animation/JCAKeyframeAnimation.swift new file mode 100644 index 0000000..e63c380 --- /dev/null +++ b/Sources/SDCALayer/Model/Animation/JCAKeyframeAnimation.swift @@ -0,0 +1,150 @@ +// +// JCAKeyframeAnimation.swift +// +// +// Created by p-x9 on 2024/04/07. +// +// + +import QuartzCore + +open class JCAKeyframeAnimation: JCAPropertyAnimation { + public typealias Target = CAKeyframeAnimation + + private enum CodingKeys: String, CodingKey { + case values + case path + case keyTimes + case timingFunctions + case calculationMode + case tensionValues + case continuityValues + case biasValues + case rotationMode + } + + open override class var targetTypeName: String { + String(reflecting: Target.self) + } + + static private let propertyMap: PropertyMap = .init([ +// .init(\.values, \.values), + .init(\.path, \.path), +// .init(\.keyTimes, \.keyTimes), + .init(\.timingFunctions, \.timingFunctions), + .init(\.calculationMode, \.calculationMode), +// .init(\.tensionValues, \.tensionValues), +// .init(\.continuityValues, \.continuityValues), +// .init(\.biasValues, \.biasValues), + .init(\.rotationMode, \.rotationMode), + ]) + + public var values: [JCAAnimationAny]? + public var path: JCGPath? + public var keyTimes: [Float]? + public var timingFunctions: [JCAMediaTimingFunction]? + public var calculationMode: JCAAnimationCalculationMode? + + public var tensionValues: [Float]? + public var continuityValues: [Float]? + public var biasValues: [Float]? + + public var rotationMode: JCAAnimationRotationMode? + + public override init() { + super.init() + } + + public required init(from decoder: Decoder) throws { + try super.init(from: decoder) + + let container = try decoder.container(keyedBy: CodingKeys.self) + values = try container.decodeIfPresent([JCAAnimationAny].self, forKey: .values) + path = try container.decodeIfPresent(JCGPath.self, forKey: .path) + keyTimes = try container.decodeIfPresent([Float].self, forKey: .keyTimes) + timingFunctions = try container.decodeIfPresent([JCAMediaTimingFunction].self, forKey: .timingFunctions) + calculationMode = try container.decodeIfPresent(JCAAnimationCalculationMode.self, forKey: .calculationMode) + + tensionValues = try container.decodeIfPresent([Float].self, forKey: .tensionValues) + continuityValues = try container.decodeIfPresent([Float].self, forKey: .continuityValues) + biasValues = try container.decodeIfPresent([Float].self, forKey: .biasValues) + + rotationMode = try container.decodeIfPresent(JCAAnimationRotationMode.self, forKey: .rotationMode) + } + + open override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(values, forKey: .values) + try container.encodeIfPresent(path, forKey: .path) + try container.encodeIfPresent(keyTimes, forKey: .keyTimes) + try container.encodeIfPresent(timingFunctions, forKey: .timingFunctions) + try container.encodeIfPresent(calculationMode, forKey: .calculationMode) + + try container.encodeIfPresent(tensionValues, forKey: .tensionValues) + try container.encodeIfPresent(continuityValues, forKey: .continuityValues) + try container.encodeIfPresent(biasValues, forKey: .biasValues) + + try container.encodeIfPresent(rotationMode, forKey: .rotationMode) + } + + public required convenience init(with object: Target) { + self.init() + + applyProperties(with: object) + } + + open override func applyProperties(to target: CAAnimation) { + super.applyProperties(to: target) + + guard let target = target as? CAKeyframeAnimation else { return } + + Self.propertyMap.apply(to: target, from: self) + + target.values = values?.map(\.value) + target.keyTimes = keyTimes?.map { NSNumber(value: $0) } + target.tensionValues = tensionValues?.map { NSNumber(value: $0) } + target.continuityValues = continuityValues?.map { NSNumber(value: $0) } + target.biasValues = biasValues?.map { NSNumber(value: $0) } + } + + open override func applyProperties(with target: CAAnimation) { + super.applyProperties(with: target) + + guard let target = target as? CAKeyframeAnimation else { return } + + Self.propertyMap.apply(to: self, from: target) + + values = target.values?.compactMap { .init($0) } + keyTimes = target.keyTimes?.map { $0.floatValue } + tensionValues = target.tensionValues?.map { $0.floatValue } + continuityValues = target.continuityValues?.map { $0.floatValue } + biasValues = target.biasValues?.map { $0.floatValue } + } + + open override func convertToAnimation() -> CAAnimation? { + let animation = CAKeyframeAnimation() + + self.applyProperties(to: animation) + return animation + } +} + +public final class JCAAnimationCalculationMode: RawIndirectlyCodableModel { + public typealias Target = CAAnimationCalculationMode + + public var rawValue: Target.RawValue + public required init(rawValue: Target.RawValue) { + self.rawValue = rawValue + } +} + +public final class JCAAnimationRotationMode: RawIndirectlyCodableModel { + public typealias Target = CAAnimationRotationMode + + public var rawValue: Target.RawValue + public required init(rawValue: Target.RawValue) { + self.rawValue = rawValue + } +} diff --git a/Sources/SDCALayer/Util/PropertyMap.swift b/Sources/SDCALayer/Util/PropertyMap.swift index d2c5708..6d51c5c 100644 --- a/Sources/SDCALayer/Util/PropertyMap.swift +++ b/Sources/SDCALayer/Util/PropertyMap.swift @@ -77,6 +77,14 @@ extension PropertyMap.MappingElement { forwardMapElement = (sourceKeyPath, .init(destinationKeyPath)) reverseMapElement = (destinationKeyPath, .init(sourceKeyPath)) } + + public init( + _ sourceKeyPath: ReferenceWritableKeyPath, + _ destinationKeyPath: ReferenceWritableKeyPath + ) where SourceValue: Sequence, DestinationValue: Sequence, SourceValue.Element: IndirectlyCodable, DestinationValue.Element: IndirectlyCodableModel, SourceValue.Element.Model == DestinationValue.Element, SourceValue.Element == DestinationValue.Element.Target { + forwardMapElement = (sourceKeyPath, .init(destinationKeyPath)) + reverseMapElement = (destinationKeyPath, .init(sourceKeyPath)) + } } extension PropertyMap { From a666819c9047da9068dfad48f206c78f4b4ffae1 Mon Sep 17 00:00:00 2001 From: p-x9 <50244599+p-x9@users.noreply.github.com> Date: Tue, 9 Apr 2024 13:11:08 +0900 Subject: [PATCH 16/17] Support `CASpringAnimation` --- .../CASpringAnimation+ICodable.swift | 17 ++++ .../Model/Animation/JCASpringAnimation.swift | 94 +++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 Sources/SDCALayer/Extension/IndirectlyCodable/Animation/CASpringAnimation+ICodable.swift create mode 100644 Sources/SDCALayer/Model/Animation/JCASpringAnimation.swift diff --git a/Sources/SDCALayer/Extension/IndirectlyCodable/Animation/CASpringAnimation+ICodable.swift b/Sources/SDCALayer/Extension/IndirectlyCodable/Animation/CASpringAnimation+ICodable.swift new file mode 100644 index 0000000..90bb938 --- /dev/null +++ b/Sources/SDCALayer/Extension/IndirectlyCodable/Animation/CASpringAnimation+ICodable.swift @@ -0,0 +1,17 @@ +// +// CASpringAnimation+ICodable.swift +// +// +// Created by p-x9 on 2024/04/09. +// +// + +import QuartzCore + +extension CASpringAnimation { + public typealias Model = JCASpringAnimation + + open override class var codableTypeName: String { + String(reflecting: Model.self) + } +} diff --git a/Sources/SDCALayer/Model/Animation/JCASpringAnimation.swift b/Sources/SDCALayer/Model/Animation/JCASpringAnimation.swift new file mode 100644 index 0000000..2b06782 --- /dev/null +++ b/Sources/SDCALayer/Model/Animation/JCASpringAnimation.swift @@ -0,0 +1,94 @@ +// +// JCASpringAnimation.swift +// +// +// Created by p-x9 on 2024/04/09. +// +// + +import QuartzCore + +open class JCASpringAnimation: JCAAnimation { + public typealias Target = CASpringAnimation + + private enum CodingKeys: String, CodingKey { + case mass + case stiffness + case damping + case initialVelocity + } + + open override class var targetTypeName: String { + String(reflecting: Target.self) + } + + static private let propertyMap: PropertyMap = .init([ + .init(\.mass, \.mass), + .init(\.stiffness, \.stiffness), + .init(\.damping, \.damping), + .init(\.initialVelocity, \.initialVelocity), + ]) + + public var mass: CGFloat? + public var stiffness: CGFloat? + public var damping: CGFloat? + public var initialVelocity: CGFloat? + +// @available(iOS 17.0, *) +// public var allowsOverdamping: Bool? + + public override init() { + super.init() + } + + public required init(from decoder: Decoder) throws { + try super.init(from: decoder) + + let container = try decoder.container(keyedBy: CodingKeys.self) + + mass = try container.decodeIfPresent(CGFloat.self, forKey: .mass) + stiffness = try container.decodeIfPresent(CGFloat.self, forKey: .stiffness) + damping = try container.decodeIfPresent(CGFloat.self, forKey: .damping) + initialVelocity = try container.decodeIfPresent(CGFloat.self, forKey: .initialVelocity) + } + + open override func encode(to encoder: Encoder) throws { + try super.encode(to: encoder) + + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encodeIfPresent(mass, forKey: .mass) + try container.encodeIfPresent(stiffness, forKey: .stiffness) + try container.encodeIfPresent(damping, forKey: .damping) + try container.encodeIfPresent(initialVelocity, forKey: .initialVelocity) + } + + public required convenience init(with object: Target) { + self.init() + + applyProperties(with: object) + } + + open override func applyProperties(to target: CAAnimation) { + super.applyProperties(to: target) + + guard let target = target as? CASpringAnimation else { return } + + Self.propertyMap.apply(to: target, from: self) + } + + open override func applyProperties(with target: CAAnimation) { + super.applyProperties(with: target) + + guard let target = target as? CASpringAnimation else { return } + + Self.propertyMap.apply(to: self, from: target) + } + + open override func convertToAnimation() -> CAAnimation? { + let animation = CASpringAnimation() + + self.applyProperties(to: animation) + return animation + } +} From f0bfdd4803cf5a072802e8d35ea5b5df737871b6 Mon Sep 17 00:00:00 2001 From: p-x9 <50244599+p-x9@users.noreply.github.com> Date: Tue, 9 Apr 2024 13:37:03 +0900 Subject: [PATCH 17/17] Add example json file of animated layer --- Example/json/anim.json | 146 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 Example/json/anim.json diff --git a/Example/json/anim.json b/Example/json/anim.json new file mode 100644 index 0000000..56b543b --- /dev/null +++ b/Example/json/anim.json @@ -0,0 +1,146 @@ +{ + "class": "CAReplicatorLayer", + "layerModel": { + "sublayers": [ + { + "layerModel": { + "position": [ + 100, + 100 + ], + "bounds": [ + [ + 0, + 0 + ], + [ + 200, + 200 + ] + ], + "animations": { + "ripple": { + "class": "CAAnimationGroup", + "animationModel": { + "beginTime": 0, + "duration": 3, + "timeOffset": 0, + "autoreverses": false, + "fillMode": { + "rawValue": "removed" + }, + "isRemovedOnCompletion": false, + "speed": 1, + "repeatDuration": 0, + "repeatCount": 3.4028235e+38, + "animations": [ + { + "animationModel": { + "autoreverses": false, + "repeatDuration": 0, + "timeOffset": 0, + "fillMode": { + "rawValue": "forwards" + }, + "isRemovedOnCompletion": false, + "fromValue": { + "type": "float", + "value": 0 + }, + "speed": 1, + "isAdditive": false, + "duration": 3, + "repeatCount": 0, + "isCumulative": false, + "toValue": { + "value": 1, + "type": "int" + }, + "keyPath": "transform.scale.xy", + "beginTime": 0 + }, + "class": "CABasicAnimation" + }, + { + "class": "CAKeyframeAnimation", + "animationModel": { + "repeatCount": 0, + "repeatDuration": 0, + "keyPath": "opacity", + "fillMode": { + "rawValue": "forwards" + }, + "beginTime": 0, + "isCumulative": false, + "calculationMode": { + "rawValue": "linear" + }, + "autoreverses": false, + "isRemovedOnCompletion": false, + "keyTimes": [ + 0, + 0.5, + 1 + ], + "values": [ + { + "type": "float", + "value": 0.8 + }, + { + "type": "float", + "value": 0.4 + }, + { + "type": "float", + "value": 0 + } + ], + "isAdditive": false, + "speed": 1, + "timeOffset": 0, + "duration": 3 + } + } + ], + "timingFunction": "linear" + } + } + }, + "backgroundColor": { + "code": "FF00FFFF" + }, + "allowsEdgeAntialiasing": false, + "contentsFormat": { + "rawValue": "RGBA8" + }, + "cornerRadius": 100, + "opacity": 0, + "allowsGroupOpacity": true + }, + "class": "CALayer" + } + ], + "instanceCount": 3, + "contentsFormat": { + "rawValue": "RGBA8" + }, + "allowsEdgeAntialiasing": false, + "instanceDelay": 1, + "position": [ + 196.5, + 426 + ], + "bounds": [ + [ + 0, + 0 + ], + [ + 200, + 200 + ] + ], + "allowsGroupOpacity": true + } +}