How to do an if-else comparison on enums with arguments
The trick is to not actually check with == but rather use the case
keyword in conjunction with a single = in your if statement. This is a little counter intuitive in the beginning but just like if let
, you get used to it pretty fast:
enum Normal {
case one
case two, three
}
enum NormalRaw: Int {
case one = 1
case two, three
}
enum NormalArg {
case one(Int)
case two, three
}
let normalOne = Normal.one
let normalRawOne = NormalRaw.one
let normalArgOne = NormalArg.one(1)
if case .one = normalOne {
print("A normal one") //prints "A normal one"
}
if case .one = normalRawOne {
print("A normal \(normalRawOne.rawValue)") //prints "A normal 1"
}
if case .one(let value) = normalArgOne {
print("A normal \(value)") //prints "A normal 1"
}
The point is that in Swift you only get equation of enums for free if your enum uses a raw type or if you have no associated values (try it out, you can't have both at the same time). Swift however does not know how to compare cases with associated values - I mean how could it? Let's look at this example:
Normal.one == .one //true
Normal.one == .two //false
NormalRaw.one == .one //true
NormalRaw.one == .two //false
NormalArg.one(1) == .one(1) //Well...?
NormalArg.one(2) == .one(1) //Well...?
NormalArg.one(1) == .two //Well...?
Maybe this makes it clearer why this cannot work out of the box:
class Special {
var name: String?
var special: Special?
}
enum SpecialEnum {
case one(Special)
case two
}
var special1 = Special()
special1.name = "Hello"
var special2 = Special()
special2.name = "World"
special2.special = special1
SpecialEnum.one(special1) == SpecialEnum.one(special2) //Well...?
So if you want enums with associated values, you'll have to implement Equatable protocol in your enum by yourself:
enum NormalArg: Equatable {
case one(Int)
case two
static func ==(lhs: NormalArg, rhs: NormalArg) -> Bool {
switch (lhs, rhs) {
case (let .one(a1), let .one(a2)):
return a1 == a2
case (.two,.two):
return true
default:
return false
}
}
}
Enum if-else Comparisons
It's important to note that currentLocation
is not a Location
, it's a Location?
(a.k.a. Optional<Location>
). So you have to pattern match against the cases of Optional
first, not of Location
. Then, within the patterns of Optional
's cases, you can match against the various cases of Location
.
Here is the progression of syntactic sugar, starting with the most verbose, and arriving at the most succinct, common way of doing this:
if case Optional.some(.ontop) = currentLocation { ... }
if case .some(.ontop) = currentLocation { ... }
- And finally, the preferred way:
if case .ontop? = currentLocation { ... }
if case
is only really ideal if you want to check for a very small subset of a large set of cases. If you need to check multiple cases, it's better to use a switch
. The case patterns are the same:
switch currentLocation {
case .onTop?: // ...
case .inside?: // ...
case .underneath?: // ...
case nil: // ...
}
How to compare enum with associated values by ignoring its associated value in Swift?
Edit: As Etan points out, you can omit the (_)
wildcard match to use this more cleanly:
let number = CardRank.Number(5)
if case .Number = number {
// Is a number
} else {
// Something else
}
Unfortunately, I don't believe that there's an easier way than your switch
approach in Swift 1.2.
In Swift 2, however, you can use the new if-case
pattern match:
let number = CardRank.Number(5)
if case .Number(_) = number {
// Is a number
} else {
// Something else
}
If you're looking to avoid verbosity, you might consider adding an isNumber
computed property to your enum that implements your switch statement.
How to test equality of Swift enums with associated values
Swift 4.1+
As @jedwidz has helpfully pointed out, from Swift 4.1 (due to SE-0185, Swift also supports synthesizing Equatable
and Hashable
for enums with associated values.
So if you're on Swift 4.1 or newer, the following will automatically synthesize the necessary methods such that XCTAssert(t1 == t2)
works. The key is to add the Equatable
protocol to your enum.
enum SimpleToken: Equatable {
case Name(String)
case Number(Int)
}
let t1 = SimpleToken.Number(123)
let t2 = SimpleToken.Number(123)
Before Swift 4.1
As others have noted, Swift doesn't synthesize the necessary equality operators automatically. Let me propose a cleaner (IMHO) implementation, though:
enum SimpleToken: Equatable {
case Name(String)
case Number(Int)
}
public func ==(lhs: SimpleToken, rhs: SimpleToken) -> Bool {
switch (lhs, rhs) {
case let (.Name(a), .Name(b)),
let (.Number(a), .Number(b)):
return a == b
default:
return false
}
}
It's far from ideal — there's a lot of repetition — but at least you don't need to do nested switches with if-statements inside.
Swift - How to compare enum with associated values?
You can use case
to access the associated values of an enum.
switch (lhs, rhs) {
case (.perfectMatch(let a, let b), .perfectMatch(let c, let d):
// check equality of associated values
return a == c && b == d
// other cases...
}
You can also access associated values like this using an if
statement:
if case .perfectMatch(let a, let b) = handler.matchType {
// do something with a and b
}
Swift enums with arguments: how to compare them?
It is not possible write one function that will work for all enums with all kinds of cases. Which basically is what you want.
The reason is discusses here. The second answer shows a method that can be used with enums that have a rawValue.
This is because an Enum of mixed types loses it's rawValue.
You can write a switch to get a rawValue (you have to ignore the associated values). But this can't be done automatically.
With a Struct or Class you also can not write a method that automatically creates a sequence/set of all the var's let's declared in it. Just like an Enum is not able to make a sequence/set out of it's cases.
enum Message: ErrorType {
case MessageWithInfo(info:String?)
case MessageDidFail
case MessageDidSend(info:String)
case InvalidMessageData
case MessageWithDelay(delay:Double)
var rawValue : Int {
get {
switch self {
case .MessageWithInfo(info: _) : return 0
case .MessageDidFail : return 1
case .MessageDidSend(info: _) : return 2
case .InvalidMessageData : return 3
case .MessageWithDelay(delay: _) : return 4
}
}
}
}
func ==(lhs:Message,rhs:Message) -> Bool {
return (lhs.rawValue == rhs.rawValue)
}
Comparing Java enum members: == or equals()?
Both are technically correct. If you look at the source code for .equals()
, it simply defers to ==
.
I use ==
, however, as that will be null safe.
Comparing two enum variables regardless of their associated values
Updated approach:
I think there's no native support for this. But you can achieve it by defining a custom operator (preferrably by using a protocol, but you can do it directly as well). Something like this:
protocol EnumTypeEquatable {
static func ~=(lhs: Self, rhs: Self) -> Bool
}
extension DataType: EnumTypeEquatable {
static func ~=(lhs: DataType, rhs: DataType) -> Bool {
switch (lhs, rhs) {
case (.one, .one),
(.two, .two):
return true
default:
return false
}
}
}
And then use it like:
let isTypeEqual = DataType.One(value: 1) ~= DataType.One(value: 2)
print (isTypeEqual) // true
Old approach:
protocol EnumTypeEquatable {
var enumCaseIdentifier: String { get }
}
extension DataType: EnumTypeEquatable {
var enumCaseIdentifier: String {
switch self {
case .one: return "ONE"
case .two: return "TWO"
}
}
}
func ~=<T>(lhs: T, rhs: T) -> Bool where T: EnumTypeEquatable {
return lhs.enumCaseIdentifier == rhs.enumCaseIdentifier
}
The older version depends on Runtime and might be provided with default enumCaseIdentifier
implementation depending on String(describing: self)
which is not recommended. (since String(describing: self)
is working with CustromStringConvertible
protocol and can be altered)
How to Compare Enum Ignoring Associated Values?
Use the below if case
statement instead:
enum Example {
case one(value: String)
case two(otherValue: Int)
}
var test = Example.one(value: "A String")
if case Example.one(value: _) = test { // Works
// Do Something
}
Swift using if on an enum resulting in error not convertible to '_ArrayCastKind'
Swift 2.x allows this via the if case pattern match: https://www.natashatherobot.com/swift-2-pattern-matching-with-if-case/
if case let .LoggedIn(name,password) = status {
print( "\(name) Logged in!" )
}
Related Topics
Firebase Completion Listeners in Swift
Swiftui - in Sheet Have a Fixed Continue Button That Is Not Scrollable
Create View Based Nstableview Programmatically Using Bindings in Swift
Wrapping a Generic Method in a Class Extension
If the Swift 'Guard' Statement Must Exit Scope, What Is the Definition of Scope
Swift Can't Infer Generic Type When Generic Type Is Being Passed Through a Parameter
Missing Argument for Parameter 'From' in Call When Creating Instance of Codable Class
How to Handle Hash Collisions for Dictionaries in Swift
How to Set Exit Code Value for a Command Line Utility in Swift
Is There a Daylight Savings Check in Swift
Extend Generic Array<T> to Adopt Protocol
Difference Between Text("") and Text(Verbatim: "") Initializers in Swiftui
Use Multiple Codingkeys for a Single Property
Swift Encode Tuple Using Nscoding