Swift - find object in array of enums with associated values

2k Views Asked by At

I have this enum:

enum Animal {       
    case cat(CatModel)
    case dog(DogModel)
}

And an array of animals:

var animals: [Animal]

I need to find a Cat object in this array by a property that Dog doesn't have. litterBoxId for example.

let cat = animals.first(where: {$0.litterBoxId == 7})

This of course has an error:

Value of type 'MyViewController.Animal' has no member 'litterBoxId'

How can I accomplish this? I also tried

($0 as CatModel).litterBoxId
3

There are 3 best solutions below

4
gcharita On BEST ANSWER

You can use pattern matching to accomplish this with 2 ways.

Using switch:

let cat = animals.first(where: {
    switch $0 {
    case .cat(let catModel) where catModel.litterBoxId == 7:
        return true
    default:
        return false
    }
})

or if:

let cat = animals.first(where: {
    if case .cat(let catModel) = $0, catModel.litterBoxId == 7 {
        return true
    }
    return false
})

Update: As @Alexander-ReinstateMonica mentioned in his commnet, it would be more appropriate to hide this logic behind a function like this:

extension Animal {
    func matches(litterboxID: Int) -> Bool {
        switch self {
        case .cat(let catModel) where catModel.litterBoxId == 7:
            return true
        default:
            return false
        }
    }
}

and then your code will be much cleaner:

let cat = animals.first(where: { $0.matches(litterboxID: 7) })
2
Alexander On

This probably isn't a good use of enums, but here is how it could work:

struct CatModel {
    let litterBoxID: Int
}

struct DogModel {
    let litterBoxID: Int
}

enum Animal {       
    case cat(CatModel)
    case dog(DogModel)
    
    var litterBoxId: Int {
        switch self {
        case .cat(let cat): return cat.litterBoxID
        case .dog(let dog): return dog.litterBoxID
        }
    }
}

var animals: [Animal] = []
let cat = animals.first(where: { $0.litterBoxId == 7 })

You would be much better off using a protocol:

struct CatModel: Animal {
    let litterBoxID: Int
}

struct DogModel: Animal {
    let litterBoxID: Int
}

protocol Animal {
    var litterBoxID: Int { get }
}

var animals: [Animal] = []
let cat = animals.first(where: { $0.litterBoxID == 7 })
0
Jevon718 On

You could add an extension that gives you back an array of CatModel

extension Array where Element == Animal {
var cats: [CatModel] {
    var filteredCats = [CatModel]()
    self.forEach { animal in
        switch animal {
        case .cat(let catModel): filteredCats.append(catModel)
        case .dog: break
        }
    }
    return filteredCats
}

}

let cat = animals.cats.first(where: {$0.litterBoxId == 7})