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 enum
s with associated values. There's no way to get the associated value without explicitly listing the enum
s.
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
Can Swift Playgrounds See Other Source Files in the Same Project
Why Can't I Divide Integers in Swift
Binary Operator '===' Cannot Be Applied to Operands of Type 'Any' and 'Uibarbuttonitem!'
How to Reload a UI View's Content Swift
Add Custom Header to Collection View Swift
Evaluate Bool Property of Optional Object in If Statement
How Is a Type-Erased Generic Wrapper Implemented
Swift: How to Change a Property's Value Without Calling Its Didset Function
Macos/Swift Capture Audio with Avcapturesession
How to Open/View iOS Oslogs Stored on Device
Saving Array to Realm in Swift
How to Get Unmanaged Object from Realm Query in Swift
Where Is the .Camera Anchorentity Located