Is there a shorthand boolean check for an enum case with associated value? - swift

I have this code snippet:
if case .voice = mode {
return true
} else {
return false
}
Is there a shorthand version maybe? For example:
.voice == mode ? true : false
mode is an enum with associated value:
enum Mode {
case `default`
case voice(VoiceMode)
}

There is no built-in method for checking enum case equality without also checking the equality of the associated values. In case your enum conforms to Equatable, you can use the equality operator to compare two enum values, however, you still couldn't do mode == Mode.voice, since Mode.voice isn't valid without a VoiceMode associated value.
You can define a computed property on the enum that only compares the cases though, not their associated values.
enum Mode {
case `default`
case voice(VoiceMode)
var isVoice: Bool {
if case .voice = self {
return true
} else {
return false
}
}
}

Related

Enum Associated Value Lookup

I have an enum with different associated value custom classes. Then trying to create a calculated value to determine if one particular type and return the value if so. The below works using a switch in the calculated value but seems a bit clumsy. Are there any cleaner approaches? Thanks.
private enum Situation: Equatable {
case inLocation(LocationMO)
case heldByBeing(BeingMO)
case inContainer(PhysicalObjectMO)
}
private var isInSituation: Situation
var isInLocation: LocationMO? {
switch isInSituation {
case .inLocation(let validLocation): return validLocation
default: return nil
}
}

I get an error 'String' cannot match values in my Switch statement using swift, why?

I am getting the following error when trying to use a switch statement on currentState which of type MJMaterialSwitchState in the delegate method switchStateChanged below.
Error: xpression pattern of type 'String' cannot match values of type 'MJMaterialSwitchState'
I am using a custom MJMaterialSwitch UI which works perfectly. It toggles between an on and off state
Function:
func switchStateChanged(_ switcher: MJMaterialSwitch, currentState: MJMaterialSwitchState) {
tapticGenerator.notificationOccurred(.success)
switch currentState{
case "on":
discoverable = true
case "off":
discoverable = false
default:
break
}
}
This is the MJMaterialSwitchState:
public enum MJMaterialSwitchState {
case on, off
}
You switch cases should be MJMaterialSwitchState instead of String values.
switch currentState {
case .on:
discoverable = true
case .off:
discoverable = false
}
Also, you do not need a default case if you cover all the cases in an enum.

Swift static var in switch case

switch-case for comparing static vars isn't working as expected. However, specifying a type or comparing directly works. Please see below:
class AnimalHelper {
func loadAnimal(_ animal: Animal) {
// Doesn't compile
switch animal {
case .squirrel, .deer: loadGrass()
case .dolphin: return // not implemented
default: loadMeat()
}
// Direct comparison works
if animal == .squirrel || animal == .deer {
loadGrass()
} else if animal == .dolphin {
return // not implemented
} else {
loadMeat()
}
// Specifying the type explicitly also works
switch animal {
case Animal.squirrel, Animal.deer: loadGrass()
case Animal.dolphin: return // not implemented
default: loadMeat()
}
}
func loadGrass() {}
func loadMeat() {}
}
class Animal {
let id = 6 // Will be generated
let hasFourLegs = true
let numberOfEyes = 2
// ... //
static var squirrel: Animal { return .init() }
static var dolphin: Animal { return .init() }
static var puma: Animal { return .init() }
static var deer: Animal { return .init() }
}
extension Animal: Equatable {
public static func ==(lhs: Animal, rhs: Animal) -> Bool {
return lhs.id == rhs.id
}
}
I'm sure something about the above isn't quite right because of which I'm getting the following compilation errors:
Enum case 'squirrel' not found in type 'Animal'
Enum case 'deer' not found in type 'Animal'
Enum case 'dolphin' not found in type 'Animal'
Please let me know how is checking for equality in a switch-case is different from that in an if condition.
In Swift, switch-case may use several different rules to match the switch-value and the case labels:
enum case matching
In this case, you can use dot-leaded case labels, but unfortunately, your Animal is not enum.
(This is not the same as equality below, as enum cases may have associated values.)
pattern matching operator ~=
Swift searches an overload for the type of the switch-value and the type of case-label, if found, applies the operator and uses the the Bool result as indicating matches.
For this purpose, Swift needs to infer the types of case-labels independent of the switch-value, thus Swift cannot infer the types of case-labels with dot-leaded notation.
equality ==
When the switch-value is Equatable, Swift uses the equality operator == for matching the switch-value to the case-labels.
(There may be more which I cannot think of now.)
The detailed behavior is not well defined, but it's difficult for compilers to resolve two rules -- pattern matching and equality. (You may want to define custom matching with ~= for Equatable types.)
So, in Swift 3, dot-leaded notation in the case-labels only works for enum types.
But, as far as I checked, Swift 4 have made it. Try Xcode 9 (currently the latest is beta 3), and your code would compile. This behavior may change in the release version of Xcode 9, but you know how to work around.

In Swift, is there a way of determining whether an enum is based on a certain type (eg. String)?

In order to write generic code for an NSValueTransformer, I need to be able to check that an enum is of type String for example. Ie.:
enum TestEnum: String {
case Tall
case Short
}
I am expecially interested in a test that can be used with the guard statement. Something allong the line of:
guard let e = myEnum as <string based enum test> else {
// throw an error
}
Please note that not all enums have raw values. For eample:
enum Test2Enum {
case Fat
case Slim
}
Hence a check on the raw value type can not be used alone for this purpose.
EDIT
After some further investigation it has become clear that NSValueTransformer can not be used to transform Swift enums. Please see my second comment from matt's answer.
First, it's your enums, so you can't not know what type they are. Second, you're not going to receive an enum type, but an enum instance. Third, even if you insist on pretending not to know what type this enum is, it's easy to make a function that can be called only with an enum that has a raw value and check what type that raw value is:
enum E1 {
case One
case Two
}
enum E2 : String {
case One
case Two
}
enum E3 : Int {
case One
case Two
}
func f<T:RawRepresentable>(t:T) -> Bool {
return T.RawValue.self == String.self
}
f(E3.One) // false
f(E2.One) // true
f(E1.One) // compile error
Generics to the rescue :
func enumRawType<T>(of v:T)-> Any?
{ return nil }
func enumRawType<T:RawRepresentable>(of v:T)-> Any?
{
return type(of:v.rawValue)
}
enumRawType(of:E1.One) // nil
enumRawType(of:E2.One) // String.Type
enumRawType(of:E3.One) // Int.Type

Is there a way to write an `if case` statement as an expression?

Consider this code:
enum Type {
case Foo(Int)
case Bar(Int)
var isBar: Bool {
if case .Bar = self {
return true
} else {
return false
}
}
}
That's gross. I would like to write something like this instead:
enum Type {
case Foo(Int)
case Bar(Int)
var isBar: Bool {
return case .Bar = self
}
}
But such a construct does not seem to exist in Swift, or I cannot find it.
Since there's data associated with each case, I don't think it's possible to implement the ~= operator (or any other helper) in a way that's equivalent to the above expression. And in any case, if case statements exist for free for all enums, and don't need to be manually implemented.
Thus my question: is there any more concise/declarative/clean/idiomatic way to implement isBar than what I have above? Or, more directly, is there any way to express if case statements as Swift expressions?
UPDATE 2:
Another workaround... Create a var that returns an Int ONLY based on the case, then use a static (or instance, I thought static looked cleaner) method to test equivalence of just the case. It won't clash with Equatable, you don't have to overload an operator (unless you want to replace the static method with one), and you also wouldn't have to create separate var isFoo, var isBar, etc.
I know you used this example to ask a more generic question (how can I use 'if case' as an expression?) but if that's not possible, this may be a valid workaround. I apologize if this treats "the symptoms" not "the problem"
enum Something{
case Foo(Int)
case Bar(Int)
static func sameCase(a: Something, b: Something) -> Bool {
return a.caseValue == b.caseValue
}
var caseValue: Int {
switch self {
case .Foo(_):
return 0
case .Bar(_):
return 1
}
}
//if necessary
var isBar: Bool {
return Something.sameCase(self, b: Something.Bar(0))
}
}
Something.sameCase(.Bar(0), b: .Foo(0)) // false
Something.sameCase(.Bar(1), b: .Foo(2)) // false
Something.sameCase(.Foo(0), b: .Foo(0)) // true
Something.sameCase(.Bar(1), b: .Bar(2)) // true
Something.Bar(0).isBar // true
Something.Bar(5).isBar // true
Something.Foo(5).isBar // false
UPDATE 1:
Ok, so this seems to work. If you overload the == operator to ignore values and return true only when both enums are the same case, you can pass any value in your isFoo method and still determine the type.
I'm assuming you will need to customize this function to accommodate the the associated values, but it seems like a step in the right direction
enum Something {
case Foo(Int)
case Bar(Int)
var isFoo: Bool {
return self == .Foo(0) // number doesn't matter here... see below
}
}
func ==(a: Something, b: Something) -> Bool {
switch (a,b) {
case (.Bar(_), .Bar(_)):
return true
case (.Foo(_), .Foo(_)):
return true
default:
return false
}
}
let oneFoo = Something.Foo(1)
let twoFoo = Something.Foo(2)
let oneBar = Something.Bar(1)
let twoBar = Something.Bar(2)
oneFoo == twoFoo // true
oneFoo == oneFoo // true
oneFoo == oneBar // false
oneFoo == twoBar // false
OLD:
You can use self and the case name to directly check which case it is, you don't have to use the case keyword. Hopefully this will work for your situation:
enum Something{
case Foo(Int)
case Bar(Int)
var isFoo: Bool {
switch self {
case Foo:
return true
case Bar:
return false
}
}
}
So, there is a neater way, but requires a 3rd-party package: CasePaths
The idea is they work similarly to KeyPaths, and they come with a / operator to trigger it. There is also a ~= operator to check if a CasePath matches an instance.
So, you can achieve something like your original example like so:
import CasePaths
enum Type {
case Foo(Int)
case Bar(Int)
var isBar: Bool {
/Self.Bar ~= self
}
}
You can also get the value:
extension Type {
/// Returns the `Int` if this is a `Bar`, otherwise `nil`.
var barValue: Int? {
(/Self.Bar).extract(from: self)
}
}
You can do several other useful things with CasePaths as well, such as extracting the Foo values in an array of Type values:
let values: [Type] = [.Foo(1), .Bar(2), .Foo(3), .Foo(4), .Bar(5)]
let foos = values.compactMap(/Type.Foo) // [1, 3, 4]
let bars = values.compactMap(/Type.Bar) // [2, 5]
I'm sure there is somewhat of a performance cost, but it may not be an issue in your context.
I have a similar wondering, and I kept searching for some work arounds about this, and landed on this page. I came up with code like this to compromise.
fileprivate enum TypePrimitive {
case foo
case bar
}
enum Type {
case foo(Int)
case bar(Int)
fileprivate var primitiveType: TypePrimitive {
switch self {
case .foo(_): return .foo
case .bar(_): return .bar
}
}
var isFoo: Bool { self.primitiveType == .foo }
var isBar: Bool { self.primitiveType == .bar }
}
I hope Apple will provide better solution by adding some features in Swift language.
Are you looking for the ? operator ?
documentation is here under the Ternary Conditional Operator title.