How to Implement Default Associated Values with Swift Enums

How should I implement Default Associated Values with Swift Enums?

I know this is a bit old, but would this work for what you want?

typealias FilterIdentifier = String

enum DefaultAPIFilters: FilterIdentifier {
case Everyone = "everyone"
case Team = "team"
}

enum APIFilters {
case Default(DefaultAPIFilters)
case Custom(FilterIdentifier)
}

let everyoneFilter = APIFilters.Default(.Everyone)
let teamFilter = APIFilters.Default(.Team)
let clownFilter = APIFilters.Custom("clowns_only")

Enum with associated value of default type

In this situation it might be easier to define it like this:

enum DeviceType {
case phone
case watch
case tablet
}

struct Device {
var type: DeviceType
var name: String
... init, etc.
}

Then you can handle the type and string independently of each other, because if every single enum case has a string it sounds like maybe the string is not directly related to the enum value.

What is the best approach to provide default value for enum in Swift?

You can fix it by using the init(rawValue:) initializer for RawRepresentable enums. I also constrain the RawValue to String-only enums.

This does require you still mark the enum as having a String raw value in the enum itself.

Code:

protocol StringEnum: RawRepresentable, ExpressibleByStringLiteral where RawValue == String {
static var `default`: Self { get }
}

extension StringEnum {
init(stringLiteral value: String) {
guard let validValue = Self(rawValue: value) else {
self = Self.`default`
return
}

self = validValue
}
}

enum TrackType: String, StringEnum {
static let `default` = TrackType.unsupported

case video
case audio
case subtitles
case unsupported
}

Usage:

let value: TrackType = "foobar"
print(value)

With 'foobar' the result is 'unsupported'. With 'video', the result is 'video'. It's working correctly.

Default arguments in Swift enums

FYI: Default parameter values are now allowed after Swift 5.1. This answer only applies to versions of Swift before that.


This is a tricky one because you cannot have default values, you cannot have stored properties and you cannot repeat case names in enums. The best thing I can think to do is to create an init() and some semi-private cases.

enum SnapOperationQueuePriority {
case Highest, High, Low, Default
}

enum SnapOperationQueue {
case Highest, High, Normal, Low

case _Highest(lowerThreshold: SnapOperationQueuePriority)
case _High(lowerThreshold: SnapOperationQueuePriority, higherThreshold: SnapOperationQueuePriority)
case _Normal(lowerThreshold: SnapOperationQueuePriority, higherThreshold: SnapOperationQueuePriority)
case _Low(higherThreshold: SnapOperationQueuePriority)

init(queue:SnapOperationQueue, lowerThreshold:SnapOperationQueuePriority = .Default, higherThreshold:SnapOperationQueuePriority = .Default) {
switch queue {
case .Highest:
self = ._Highest(lowerThreshold: lowerThreshold == .Default ? .Highest : lowerThreshold)
case .High:
self = ._High(lowerThreshold: lowerThreshold == .Default ? .Low : lowerThreshold, higherThreshold: higherThreshold == .Default ? .High : higherThreshold)
case .Normal:
self = ._Normal(lowerThreshold: lowerThreshold == .Default ? .Low : lowerThreshold, higherThreshold: higherThreshold == .Default ? .High : higherThreshold)
case Low:
self = ._Low(higherThreshold: higherThreshold == .Default ? .Low : higherThreshold)
default:
self = queue

}
}
}

SnapOperationQueue.Normal
SnapOperationQueue(queue: .Normal)
SnapOperationQueue(queue: .High, lowerThreshold: .High, higherThreshold: .Highest)

This keeps old implementations valid and catches new ones made using init. In addition you might add a method like this to the enum:

func queue() -> SnapOperationQueue {
switch self {
case .Highest:
return SnapOperationQueue(queue: .Highest)
case .High:
return SnapOperationQueue(queue: .High)
case .Normal:
return SnapOperationQueue(queue: .Normal)
case Low:
return SnapOperationQueue(queue: .Low)
default:
return self

}

}

So that you can transform the old types of enum cases into the new, e.g. SnapOperationQueue.Normal.queue()

Swift Enum associated values conforming to single protocol

I've found the solution in case anyone will need this too.

enum ContentType {
case content1(Type1 & Refreshable)
case content2(Type2 & Refreshable)
case content3(someLabel: Type3 & Refreshable)

func refreshMe() {
let caseReflection = Mirror(reflecting: self).children.first!.value
(caseReflection as? Refreshable)?.refresh() //If associated type doesn't have label

let refreshable = Mirror(reflecting: caseReflection).children.first?.value as? Refreshable
refreshable?.refresh() //If associated type has label
}
}

How to make a Swift enum with associated values equatable

SE-0185 Synthesizing Equatable and Hashable conformance has been implemented in Swift 4.1, so that it suffices do declare conformance to the protocol (if all members are Equatable):

enum ViewModel: Equatable {
case heading(String)
case options(id: String, title: String, enabled: Bool)
}

For earlier Swift versions, a convenient way is to use that tuples can be compared with ==.

You many also want to enclose the compatibility code in a Swift version check, so that the automatic synthesis is used once the project is updated to Swift 4.1:

enum ViewModel: Equatable {
case heading(String)
case options(id: String, title: String, enabled: Bool)

#if swift(>=4.1)
#else
static func ==(lhs: ViewModel, rhs: ViewModel) -> Bool {
switch (lhs, rhs) {
case (let .heading(lhsString), let .heading(rhsString)):
return lhsString == rhsString
case (let .options(lhsId, lhsTitle, lhsEnabled), let .options(rhsId, rhsTitle, rhsEnabled)):
return (lhsId, lhsTitle, lhsEnabled) == (rhsId, rhsTitle, rhsEnabled)
default:
return false
}
}
#endif
}

Is there a way in Swift to get an associated value without using a switch statement?

You can use if case .<enum_case>(let value) as in TylerP's example,
or if case let .<enum_case>(value):

enum Foo {
case anInt(Int)
case aFloat(Float)
}

let aFoo: Foo = .anInt(9)

// Example of `if case .<enum_case)(let value)` syntax:
if case .anInt(let aValue) = aFoo {
print("aFoo = anInt(\(aValue))")

// Example of `if case let .enum_case(value)` syntax:
} else if case let .aFloat(aValue) = aFoo {
print("aFoo = aFloat(\(aValue))")
}

Both work. I'm not sure why the language includes both variants.

If you only care about one enum type, then either if syntax makes sense to me. If you are dealing with more than one possible enum value then the switch version seems cleaner.

Swift 2: Is there any way to use 'default' in the switch statement of enum with associated values?

While I agree with Paul's concern that it is odd to nest Location precisely this way, the basic problem is solvable. Personally, I wouldn't solve it with a default, I'd just simplify the code and use the tools Swift gives us (like CustomStringConvertible; I also put labels on your data; it was too confusing with just two Location elements that had completely different meanings):

indirect enum Location: CustomStringConvertible {
case Title(String?)
case Region(Location)
case Area(title: Location, parent: Location)
case City(title: Location, parent: Location)
case Settlement(title: Location, parent: Location)
case Street(title: Location, parent: Location)
case House(title: Location, parent: Location)

var description: String {

func format(locs: (Location, Location)) -> String {
return [locs.0, locs.1].map{$0.description}.filter{$0 != ""}.joinWithSeparator(", ")
}

switch self {
case .Title(let title): return title ?? ""

case .Region(let title): return "\(title)"

case .House(let data): return format(data)
case .Street(let data): return format(data)
case .Settlement(let data): return format(data)
case .City(let data): return format(data)
case .Area(let data): return format(data)
}
}
}

Notice how I unload the entire tuple into data. You don't have to break the tuple apart in pattern matching. Enumerations never have multiple associated data. They always have exactly one: a tuple. (The same is true of functions. All functions take one value and return one value. That value just might happen to be a tuple.)

But if you really wanted to get rid of that repeated return format(data), then you can through Mirror. (You can solve a rather shocking number of things through Mirror. You should be very careful before you do. This case is just duplicated typing, not duplicated logic. A little duplicated typing is not something you should create a lot of complexity to remove.)

Here's how you'd do it:

var description: String {
switch self {
case .Title(let title): return title ?? ""

case .Region(let title): return "\(title)"

default:
let m = Mirror(reflecting: self)
guard let locs = (m.children.first?.value as? (Location, Location)) else {
preconditionFailure("Unexpected data in enum. Probably missing a case somewhere.")
}
return [locs.0, locs.1].map{$0.description}.filter{$0 != ""}.joinWithSeparator(", ")
}
}

The lesson here is that the first child of an enum is a tuple of all of its data.

But using Mirror is much more fragile (notice I opened the possibility of crashing). And while an enum is possibly a great tool here, you still may want to rethink this data structure.



Related Topics



Leave a reply



Submit