Accessing data from Array containing different types

Hi,

I have an Array that I’d like to contain different types (in order to populate a tableView)… the problem I have is accessing the elements inside that Array. I quickly threw together a playground that creates two different types of objects, then adds them to an Array of AnyObjects. I’m using an enum as the common thread between them to help distinguish their types when it comes time to populate the tableView to help with other conditional code within the cellForIndexPath.

Any hints on how to access the data within the elements? For instance, the variable “kind” within each object? A simple collection[0]. brings up nothing. Do I have to attempt to cast these as one of my types, then access the data within?

What would be the best way to do this casting in the cellForIndexPath method? I plan on having about 20 different types when it’s all said and done.

I’ve also tried creating an empty protocol, and having them both conform to it to avoid casting, but I still couldn’t access the data within each element when using a ‘for in loop’.

Thanks!

var collection = [AnyObject]()

enum Kind {
    case Car, Animal
}


class Car {
    var kind: Kind
    var model: String
    var year: Double
    var passengers: [String]
    var seats: Int
    
    init(kind: Kind, model: String, year: Double, passengers: [String], seats: Int) {
        self.kind = kind
        self.model = model
        self.year = year
        self.passengers = passengers
        self.seats = seats
    }
}

class Animal {
    var kind: Kind
    var legs: Int
    var domesticated: Bool
    
    init(kind: Kind, legs: Int, domesticated: Bool) {
        self.kind = kind
        self.legs = legs
        self.domesticated = domesticated
    }
}


let vw = Car(kind: .Car, model: "GTI", year: 2010, passengers: ["Mike", "Sarah", "Elise", "Henry"], seats: 4)
let coyote = Animal(kind: .Animal, legs: 4, domesticated: false)
let jeep = Car(kind: .Car, model: "Cherokee", year: 2014, passengers: ["Sarah", "Mike"], seats: 5)


collection.append(vw)
collection.append(coyote)
collection.append(jeep)

// the collection now contains [Car, Animal, Car], but I cannot access collection[0].kind unless I cast it as a Car.

If I’m understanding this correctly: you have an array containing a number of types, each of which has a corresponding UITableViewCell, right?

I can think of two ways of resolving this. The first involves making your enum a bit more complicated - add associated types to your ‘Kind’:

enum Kind {
    case car(Car), animal(Animal)
}

(I’m using the all lower-case names for the enum cases because otherwise Swift will think the Car inside the brackets is the enum state Car rather than the class… you could also use case Car(<Module>.Car) where Module is the name of your module so Swift can disambiguate it all, but I’m digressing…)
(I’ve just noticed that your Car and Animal classes have variables specifying that they’re respectively Cars or Animals. I think either this is redundant, or you’ll run into some confusion when you create a Car with a kind value of Animal, so to avoid recursive declarations I’m assuming you remove the ‘kind’ property from your types.)

So, having done that, your collection of [Kind] can be declared like this:
let collection : [Kind] = [.car(vw), .animal(coyote), .car(jeep)]

And then when you get to your table view you can have this:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) {
    switch collection[indexPath.row)
    {
        case .car(let car): let cell = tableView.dequeueCellWithReuseIdentifier("car"); // configure and return car cell
        case .animal(let animal): let cell = tableView.dequeueCellWithReuseIdentifier("animal"); // configure and return animal cell
    }
}

My second approach involves making a protocol - let’s call it TableViewCellConvertible - with a function called func displayInTableView(tableView: UITableView, atIndexPath:NSIndexPath) -> UITableViewCell, and you would extend your Car and Animal types to conform this protocol. The function would dequeue and configure an appropriate cell You would then have your collection of [TableViewCellConvertible], and the tableView(:cellForRowAtIndexPath:) function might then call return collection[indexPath.row].displayInTableView(tableView, atIndexPath:indexPath).

The first method is more restrictive, in that you have to have a finite list of types in your enum and that might make it harder to add more types in future; the second approach just means making sure each new displayable type conforms to the protocol.

1 Like

Awesome - great explanation… thanks for the tips!

Edit: Protocol is definitely the way to go here… it’s such a clean implementation. Thanks again.