1

I am trying to build a table view for events, like so:
enter image description here

I have two cell prototypes:

  • An event cell with identifier "event"
  • A separator cell with identifier "seperator"

Also, I have this class to represent a date:

class Event{

    var name:String = ""
    var date:NSDate? = nil
}

And this is the table controller:

class EventsController: UITableViewController {

    //...

    var eventsToday = [Event]()
    var eventsTomorrow = [Event]()
    var eventsNextWeek = [Event]()

    override func viewDidLoad() {
        super.viewDidLoad()

        //...

        self.fetchEvents()//Fetch events from server and put each event in the right property (today, tomorrow, next week)

        //...
    }

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        let event = tableView.dequeueReusableCellWithIdentifier("event", forIndexPath: indexPath) as EventTableViewCell
        let seperator = tableView.dequeueReusableCellWithIdentifier("seperator", forIndexPath: indexPath) as SeperatorTableViewCell

        //...

        return cell
    }
}

I have all the information I need at hand, but I can't figure out the right way to put it all together. The mechanics behind the dequeue func are unclear to me regrading multiple cell types.

I know the question's scope might seem a little too broad, but some lines of code to point out the right direction will be much appreciated. Also I think it will benefit a lot of users since I didn't found any Swift examples of this.

Thanks in advance!

5
  • Your code should work fine, all you need to do is use func registerClass(_ cellClass: AnyClass, forCellReuseIdentifier identifier: String) to register both "event" and "separator" cell identifiers with the table view some where, I do it in viewDidLoad Commented Mar 14, 2015 at 13:11
  • 1
    @user3435374 Obviously, if you use cell prototypes, that eliminates the need to manually register the identifiers. This code, though, doesn't make any sense. The cellForRowAtIndexPath should create only one cell, dependent upon the NSIndexPath. Commented Mar 14, 2015 at 14:09
  • @Rob I believe that is precisely the question, Rob. If the method should create only one cell, how would you go about returning two (or more) different types of cell? How would that look in code? Commented Apr 22, 2015 at 13:38
  • @LucasCerro - I illustrate how to do that in the first half of my answer below. The key observation is that while you want this to be able to instantiate two types of cells, this is called once for each row of the table, and each cell is only one type. So it's not a question of how to instantiate two types of cells, but, rather, more accurately, given a particular NSIndexPath, which of the two types of cells should be instantiated. Personally, I still think that's overly complicated approach, so in the second half of my answer, I walk through a simpler approach (in this case, at least). Commented Apr 22, 2015 at 14:13
  • 1
    Sorry, I read your comment before reading your solution. Your answer was perfect. The code was not confusing at all. On the contrary: you presented some elegant approaches to solving it in a DRY manner. Thank you for your answer. Extra credit for pointing me to the solution to other stuff, such as section titles. =) Commented Apr 22, 2015 at 14:15

1 Answer 1

4

The basic approach is that you must implement numberOfRowsInSection and cellForRowAtIndexPath (and if your table has multiple sections, numberOfSectionsInTableView, too). But each call to the cellForRowAtIndexPath will create only one cell, so you have to do this programmatically, looking at the indexPath to determine what type of cell it is. For example, to implement it like you suggested, it might look like:

override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    return 1
}

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return eventsToday.count + eventsTomorrow.count + eventsNextWeek.count + 3 // sum of the three array counts, plus 3 (one for each header)
}

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    var index = indexPath.row

    // see if we're the "today" header

    if index == 0 {
        let separator = tableView.dequeueReusableCellWithIdentifier("separator", forIndexPath: indexPath) as SeparatorTableViewCell

        // configure "today" header cell

        return separator
    }

    // if not, adjust index and now see if we're one of the `eventsToday` items

    index--

    if index < eventsToday.count {
        let eventCell = tableView.dequeueReusableCellWithIdentifier("event", forIndexPath: indexPath) as EventTableViewCell
        let event = eventsToday[index]

        // configure "today" `eventCell` cell using `event`

        return eventCell
    }

    // if not, adjust index and see if we're the "tomorrow" header

    index -= eventsToday.count

    if index == 0 {
        let separator = tableView.dequeueReusableCellWithIdentifier("separator", forIndexPath: indexPath) as SeparatorTableViewCell

        // configure "tomorrow" header cell

        return separator
    }

    // if not, adjust index and now see if we're one of the `eventsTomorrow` items

    index--

    if index < eventsTomorrow.count {
        let eventCell = tableView.dequeueReusableCellWithIdentifier("event", forIndexPath: indexPath) as EventTableViewCell
        let event = eventsTomorrow[index]

        // configure "tomorrow" `eventCell` cell using `event`

        return eventCell
    }

    // if not, adjust index and see if we're the "next week" header

    index -= eventsTomorrow.count

    if index == 0 {
        let separator = tableView.dequeueReusableCellWithIdentifier("separator", forIndexPath: indexPath) as SeparatorTableViewCell

        // configure "next week" header cell

        return separator
    }

    // if not, adjust index and now see if we're one of the `eventsToday` items

    index--

    assert (index < eventsNextWeek.count, "Whoops; something wrong; `indexPath.row` is too large")

    let eventCell = tableView.dequeueReusableCellWithIdentifier("event", forIndexPath: indexPath) as EventTableViewCell
    let event = eventsNextWeek[index]

    // configure "next week" `eventCell` cell using `event`

    return eventCell
}

Having said that, I really don't like that logic. I'd rather represent the "today", "tomorrow" and "next week" separator cells as headers, and use the section logic that table views have.

For example, rather than representing your table as a single table with 8 rows in it, you could implement that as a table with three sections, with 2, 1, and 2 items in each, respectively. That would look like:

override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    return 3
}

override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    switch section {
    case 0:
        return "Today"
    case 1:
        return "Tomorrow"
    case 2:
        return "Next week"
    default:
        return nil
    }
}

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    switch section {
    case 0:
        return eventsToday.count
    case 1:
        return eventsTomorrow.count
    case 2:
        return eventsNextWeek.count
    default:
        return 0
    }
}

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let eventCell = tableView.dequeueReusableCellWithIdentifier("event", forIndexPath: indexPath) as EventTableViewCell

    var event: Event!

    switch indexPath.section {
    case 0:
        event = eventsToday[indexPath.row]
    case 1:
        event = eventsTomorrow[indexPath.row]
    case 2:
        event = eventsNextWeek[indexPath.row]
    default:
        event = nil
    }

    // populate eventCell on the basis of `event` here

    return eventCell
}

The multiple section approach maps more logically from the table view to your underlying model, so I'd to adopt that pattern, but you have both approaches and you can decide.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.