Reputation: 6477
I have a Swift enum like this:
public enum AnimationType {
case position(Float)
case position([Keyframe<Float>])
case scale(Float)
case scale([Keyframe<Float>])
case rect(CGRect)
case rect([Keyframe<CGRect>])
case transform(CGAffineTransform)
case transform([Keyframe<CGAffineTransform>])
...
...
}
As we can see, for each type there are two values possible - fixed value of type T or an array of keyframes with value type T ([Keyframe]). I am wondering if there is anything I can do to avoid repetition of same name in the enum and merge the two enum case types? Or I am modelling it wrong way?
Upvotes: 2
Views: 1225
Reputation: 32779
I assume that at the lower end you are using some common code to exploit the two types, so you can benefit polymorphism by grouping them under some protocol:
public enum AnimatationType {
case position(PositionProtocol)
case scale(ScaleProtocol)
case rect(RectProtocol)
case transform(TransformProtocol)
...
...
}
And simply extend the types:
extension Float: PositionProtocol {
func someCommonGround() -> SomeCommonType { ... }
}
extension Keyframe: PositionProtocol where KeyframeGenericArgument == Float {
func someCommonGround() -> SomeCommonType { ... }
}
Upvotes: 1
Reputation: 30341
I would solve this with a Kind
enum type, for each kind of variation.
public enum AnimationType {
public enum Kind<Value> {
case scalar(Value)
case keyframes([Keyframe<Value>])
}
case position(Kind<Float>)
case scale(Kind<Float>)
case rect(Kind<CGRect>)
case transform(Kind<CGAffineTransform>)
}
Usage:
let anim1 = AnimationType.position(.scalar(10))
let anim2 = AnimationType.position(.keyframes([Keyframe(10)]))
Getting values:
switch anim1 {
case .position(let kind):
switch kind {
case .scalar(let value):
print("value: \(value)")
case .keyframes(let keyframes):
print("keyframes: \(keyframes)")
}
default: // You would implement the rest
break
}
switch anim1 {
case .position(.scalar(let value)):
print("value: \(value)")
case .position(.keyframes(let keyframes)):
print("keyframes: \(keyframes)")
default: // You would implement the rest
break
}
if case .position(.scalar(let value)) = anim1 {
print("value: \(value)")
}
You can also add Codable
conformance:
public struct Keyframe<Value: Codable> {
let value: Value
init(_ value: Value) {
self.value = value
}
}
extension Keyframe: Codable {
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(value)
}
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
value = try container.decode(Value.self)
}
}
public enum AnimationType {
public enum Kind<Value: Codable> {
case scalar(Value)
case keyframes([Keyframe<Value>])
}
case position(Kind<Float>)
case scale(Kind<Float>)
case rect(Kind<CGRect>)
case transform(Kind<CGAffineTransform>)
}
extension AnimationType.Kind: Codable {
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .scalar(let value): try container.encode(value)
case .keyframes(let keyframes): try container.encode(keyframes)
}
}
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let scalar = try? container.decode(Value.self) {
self = .scalar(scalar)
return
}
if let keyframes = try? container.decode([Keyframe<Value>].self) {
self = .keyframes(keyframes)
return
}
// You should throw error here instead
fatalError("Failed to decode")
}
}
extension AnimationType: Codable {
private enum CodingKeys: CodingKey {
case position
case scale
case rect
case transform
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case .position(let kind): try container.encode(kind, forKey: .position)
case .scale(let kind): try container.encode(kind, forKey: .scale)
case .rect(let kind): try container.encode(kind, forKey: .rect)
case .transform(let kind): try container.encode(kind, forKey: .transform)
}
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let position = try? container.decode(Kind<Float>.self, forKey: .position) {
self = .position(position)
return
}
if let scale = try? container.decode(Kind<Float>.self, forKey: .scale) {
self = .scale(scale)
return
}
if let rect = try? container.decode(Kind<CGRect>.self, forKey: .rect) {
self = .rect(rect)
return
}
if let transform = try? container.decode(Kind<CGAffineTransform>.self, forKey: .transform) {
self = .transform(transform)
return
}
// You should throw error here instead
fatalError("Failed to decode")
}
}
Example encoding:
do {
let data = try JSONEncoder().encode(anim1)
if let str = String(data: data, encoding: .utf8) {
print(str)
// Prints: {"position":10}
}
} catch {
print(error)
}
The same sort of thing with anim2
returns {"position":[10]}
.
Upvotes: 7
Reputation: 4037
@George's answer did solve this.
Swift's solution is of variety.
Here is my proposal:
public struct Keyframe<T> {
let v : T
}
public enum AnimaKind{
case simple
case series
}
public enum AnimatationType {
case position
case scale
case rect
case transform
}
extension AnimatationType{
func simple<T>(info: T) -> (type: AnimatationType, kind: AnimaKind, info: T){
return (self, .simple, info)
}
func series<T>(info: [T]) -> (type: AnimatationType, kind: AnimaKind, info: [Keyframe<T>]){
let result = info.map { x in Keyframe(v: x) }
return (self, .series, result)
}
}
as you can see, the way you unwrap is easier
func test(){
let animaTest = AnimatationType.position.simple(info: Float(10))
}
you get value from animaTest
is convenient,
tuple
vs enum nested
Upvotes: 1