Skip to content

Instantly share code, notes, and snippets.

@matt-curtis
Last active August 22, 2021 02:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save matt-curtis/825c16c5dd058e009bf996e93eee60c2 to your computer and use it in GitHub Desktop.
Save matt-curtis/825c16c5dd058e009bf996e93eee60c2 to your computer and use it in GitHub Desktop.
AnimatableMemberHaving: A protocol that enables types whose members are made up of Animatable or VectorArithmetic-conforming properties to become Animatable themselves by specifying those members
import SwiftUI
/// A protocol that enables types whose members are made up of `Animatable` or `VectorArithmetic`-conforming properties
/// to become `Animatable` themselves by specifying those members.
///
/// Here's an example of how to use this protocol:
///
/// ```
/// struct CornerRadii: AnimatableMemberHaving {
///
/// var topLeftCorner: Double
/// var topRightCorner: Double
/// var bottomRightCorner: Double
/// var bottomLeftCorner: Double
///
/// static let animatableMembers = AnimatableMembers
/// .append(\Self.topLeftCorner)
/// .append(\.topRightCorner)
/// .append(\.bottomRightCorner)
/// .append(\.bottomLeftCorner)
///
/// }
/// ```
protocol AnimatableMemberHaving: Animatable {
associatedtype AnimatableMemberData: VectorArithmetic
static var animatableMembers: AnimatableMembers<Self, AnimatableMemberData> { get }
}
extension AnimatableMemberHaving {
var animatableData: AnimatableMemberData {
get {
Self.animatableMembers.vectorArithmetic(from: self)
}
set {
Self.animatableMembers.setVectorArithmetic(newValue, on: &self)
}
}
}
/// Holds information about a group of members belonging to a specific type.
/// Uses nested `AnimatablePair`s to represent the combined `VectorArithmetic` of those members.
struct AnimatableMembers<Base, Value: VectorArithmetic> {
// MARK: - Properties
private let get: (Base) -> Value
private let set: (inout Base, Value) -> Void
// MARK: - Static append (single member)
static func append(_ keyPath: WritableKeyPath<Base, Value>) -> Self {
.init(
get: { $0[keyPath: keyPath] },
set: { $0[keyPath: keyPath] = $1 }
)
}
static func append<T: Animatable>(_ keyPath: WritableKeyPath<Base, T>) -> Self where Value == T.AnimatableData {
let keyPath = keyPath.appending(path: \.animatableData)
return .init(
get: { $0[keyPath: keyPath] },
set: { $0[keyPath: keyPath] = $1 }
)
}
// MARK: - Composite pairing
func append<T: VectorArithmetic>(_ keyPath: WritableKeyPath<Base, T>) -> AnimatableMembers<Base, AnimatablePair<Value, T>> {
.init(
get: { AnimatablePair(get($0), $0[keyPath: keyPath]) },
set: { set(&$0, $1.first); $0[keyPath: keyPath] = $1.second }
)
}
func append<T: Animatable>(_ keyPath: WritableKeyPath<Base, T>) -> AnimatableMembers<Base, AnimatablePair<Value, T.AnimatableData>> {
let keyPath = keyPath.appending(path: \.animatableData)
return .init(
get: { AnimatablePair(get($0), $0[keyPath: keyPath]) },
set: { set(&$0, $1.first); $0[keyPath: keyPath] = $1.second }
)
}
// MARK: - Getter & setter
func vectorArithmetic(from base: Base) -> Value {
get(base)
}
func setVectorArithmetic(_ value: Value, on base: inout Base) {
set(&base, value)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment