'How to make a Swift enum with associated values equatable

I have an enum of associated values which I would like to make equatable for testing purposes, but do not know how this pattern would work with an enum case with more than one argument.

For example, summarised below I know the syntax for making heading equatable. How would this work for options, which contains multiple values of different types?

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

func ==(lhs: ViewModel, rhs: ViewModel) -> Bool {
    switch (lhs, rhs) {
    case (let .heading(lhsString), let .heading(rhsString)):
        return lhsString == rhsString
    case options...
    default:
        return false
    }
}

I know Swift 4.1 can synthesize conformance for Equatable for us, but at present I am not able to update to this version.



Solution 1:[1]

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
}

Solution 2:[2]

You can add something like below, check this link for more information. Return statement for options depend on your needs.

#if swift(>=4.1)
#else
func ==(lhs: ViewModel, rhs: ViewModel) -> Bool {
    switch (lhs, rhs) {
    case (let .heading(lhsString), let .heading(rhsString)):
        return lhsString == rhsString

    case (let .options(id1, title1, enabled1),let .options(id2, title2, enabled2)):
        return id1 == id2 && title1 == title2 && enabled1 == enabled2
    default:
        return false
    }
}
#endif

Solution 3:[3]

Elegant way to work with associated value ( even if the enum is indirect):

first you need to have the value property:

indirect enum MyEnum {
    var value: String? {
        return String(describing: self).components(separatedBy: "(").first
    }
    case greeting(text: String)
    case goodbye(bool: Bool)
    case hey
    case none
}

print(MyEnum.greeting(text: "Howdy").value)
// prints : greeting

now you can use the value to implement Equatable like this:

    indirect enum MyEnum: Equatable {
     static func == (lhs: MyEnum, rhs: MyEnum) -> Bool {
        lhs.value == rhs.value
     }
    
     var value: String? {
        return String(describing: self).components(separatedBy: "(").first
     }
     case greeting(text: String)
     case goodbye(bool: Bool)
     case hey
     case none
   }

Solution 4:[4]

Maybe not relevant for the OP but this might help others:

Remember that if you only want to compare an enum value against a fixed value, you can simply use pattern matching:

if case let ViewModel.heading(title) = enumValueToCompare {
  // Do something with title
}

If you care about the associated value, you can add some conditions on it:

if case let ViewModel.heading(title) = enumValueToCompare, title == "SomeTitle" {
  // Do something with title
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3
Solution 4 Coconuts