Enum with Identical Cases Names with Associated Values of Different Types

Enum with identical cases names with associated values of different types

This is a Swift compiler bug. See SR-10077.

Swift enum associated value with different types

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]}.

Binding to an associated value of an enum that has different types of associated values

Indeed, having several very similar properties is a code smell, as well as the fact that those properties were added just because they are needed by the UI layer.

However, since you already have the switch over the value type, you can push the logic for the binding to that (UI) layer. Here's a possible implementation:

struct ValueView: View {
let name: String
@Binding var value: Value

var body: some View {
HStack {
switch value {
case .none:
Spacer()
case let .bool(bool):
Toggle(name, isOn: Binding(get: { bool }, set: { value = .bool($0) } ))
case let .int(int):
Stepper("\(int)", value: Binding(get: { int }, set: { value = .int($0) }))
case let .float(float):
Text(float.description)
case let .string(string):
Text(string)
}
}
}
}

I also took the liberty of extracting the code to a dedicated view and decoupling that view from the Node type, this is more idiomatic in SwiftUI and makes your code more readable and easier to maintain.

With the above in mind, ContentView simply becomes:

Usage:

struct ContentView: View {
@StateObject private var model = Model()

var body: some View {
VStack {
ScrollView {
Text(model.jsonString)
}

List($model.data, id: \.name, children: \.children) { $node in
ValueView(name: node.name, value: $node.value)
}
}
}
}

, and you can safely delete the "duplicated" properties from the Value enum.

swift enum get the associated value of multiple case with same parameters in single switch-case

You can put multiple enum values in the same case line, but you have to move the let into the ():

var value: Double {
switch self {
case .width(let value), .height(let value), .xxxxx1(let value):
return value
}
}

You might want to put each enum value on a separate line:

var value: Double {
switch self {
case .width(let value),
.height(let value),
.xxxxx1(let value):
return value
}
}

Unfortunately, that's about as elegant as it gets with enums with associated values. There's no way to get the associated value without explicitly listing the enums.

You can only combine values in the same line that have the same type of associated value, and all of the values have to bind to the same variable name. That way, in the code that follows, you know that the value is bound and what its type is no matter which enum pattern matched.

Is it possible group & represent multiple cases as another case within an enum in Swift?

You can used nested Enum and a case with parameter

enum Devices {
case phone(iPhone)
case tablet(iPad)

enum iPhone {
case phone7
case phoneX
}

enum iPad {
case mini
case pro
}
}

let randomDevice = Devices.phone(.phone7)

switch randomDevice {
case .phone:
print("Its a phone")
default:
break
}

// prints "Its a phone"


Related Topics



Leave a reply



Submit