Swift. Proper initialization of UITableViewCell hierarchy

35,688

Solution 1

I'm not sure I understand your question correctly, but it seems to be about inheritance between classes. So basically you have a "GenericCell" class that inherits from "UITableViewCell", and "CellOne", "CellTwo", and "CellThree" classes that each inherit from "GenericCell". If you want to go through init with style, one way to set this up would be like this:

class GenericCell: UITableViewCell {
    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        // code common to all your cells goes here
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}

class CellOne: GenericCell {
    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier) // the common code is executed in this super call
        // code unique to CellOne goes here
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}

You could then create instances of CellOne in your table view's data source like so:

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

    var cell = tableView.dequeueReusableCellWithIdentifier("cell")
    if (cell == nil) {
        cell = CellOne.init(style: .Default, reuseIdentifier: "cell")
    }
    return cell!
}

For each instance it will now first go through the common setup done in "GenericCell", and then through the unique setup in "CellOne". "CellTwo" and "CellThree" would be set up accordingly.

EDIT

A more concrete example of how to configure instances of all three Cell types:

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

        // you need to write a method like this to figure out which type you need:
        let cellID = self.cellIDForIndexPath(indexPath) // returns either "cell1", "cell2" or "cell3"

        // dequeue or init a cell of the approriate type
        var cell = tableView.dequeueReusableCellWithIdentifier(cellID)
        if (cell == nil) {
            switch cellID {
                case "cell1": cell = CellOne.init(style: .Default, reuseIdentifier: "cell")
                case "cell2": cell = CellTwo.init(style: .Default, reuseIdentifier: "cell")
                case "cell3": cell = CellThree.init(style: .Default, reuseIdentifier: "cell")
                default: cell = UITableViewCell()
            }

        }

        // configure the individual cell if needed (you need to implement methods + logic here that fit your data)
        (cell as! GenericCell).configureForData(self.dataForIndexPath(indexPath))

        return cell!
    }

Solution 2

This is how I would lay down the hierarchy mentioned by you:

Step 1 : Make Generic Cell class

class GenericCell : UITableViewCell {
    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        print("Generic Cell Initialization Done")
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}

Step 2 : Make Specific Cell 1 class:

class MyCell1 : GenericCell {
    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        print("MyCell1 Initialization Done")
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}

Step 3 : Make Specific Cell 2 class:

class MyCell2 : GenericCell {
    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        print("MyCell2 Initialization Done")
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}

Step 4 : Make Specific Cell 3 class:

class MyCell3 : GenericCell {
    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        print("MyCell3 Initialization Done")
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}

Step 5 : Finally use the cells like this:

let cell1 = MyCell1.init(style: UITableViewCellStyle.Default, reuseIdentifier: "cell1")
let cell2 = MyCell2.init(style: UITableViewCellStyle.Default, reuseIdentifier: "cell2")
let cell3 = MyCell3.init(style: UITableViewCellStyle.Default, reuseIdentifier: "cell3")

PS: This would guarantee setting the properties in generic cell as well in specific cells.

EDIT: This is how you would use cells in cellForRowAtIndexPath:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    if indexPath.section == 0 {
        let cell1 = tableView.dequeueReusableCellWithIdentifier("cell1", forIndexPath: indexPath) as MyCell1

        if cell1 == nil {
            cell1 = MyCell1.init(style: UITableViewCellStyle.Default, reuseIdentifier: "cell1")
        }

        // Do your cell property setting

        return cell1
    } else if indexPath.section == 1 {
        let cell2 = tableView.dequeueReusableCellWithIdentifier("cell2", forIndexPath: indexPath) as MyCell2

        if cell2 == nil {
            cell2 = MyCell2.init(style: UITableViewCellStyle.Default, reuseIdentifier: "cell2")
        }

        // Do your cell property setting

        return cell2
    } else {
        let cell3 = tableView.dequeueReusableCellWithIdentifier("cell3", forIndexPath: indexPath) as MyCell3

        if cell3 == nil {
            cell3 = MyCell3.init(style: UITableViewCellStyle.Default, reuseIdentifier: "cell3")
        }

        // Do your cell property setting

        return cell3
    }
}
Share:
35,688
drewpts
Author by

drewpts

Updated on July 09, 2022

Comments

  • drewpts
    drewpts almost 2 years

    [UITableViewCell] <- [genericCell] <- [Cell1], [Cell2], [Cell3]

    Hello. Please imagine hierarchy above. In my code I don't have objects exactly of type genericCell, but this class shares some properties.

    What design of inits should be in my code? I have following structure for genericCell:

    override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        //my stuff (initializing shared properties)
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    
    }
    

    But what about Cell1? How can I invoke init(style: UITableViewCellStyle, reuseIdentifier: String?) in genericCell for "my stuff" operations through initialisation of Cell1 instance? Now they doesn't perform.


    EDIT

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
            let typeOfCell = FbDataManager.sharedInstance.posts[indexPath.row][FbDataManager.sharedInstance.typeParameter] as! String
    
            switch typeOfCell {
                case self.linkTypeOfPost:
    
                    var cell = tableView.dequeueReusableCellWithIdentifier(self.linkCellIdentifier) as? FbLinkPostViewCell
                    if cell == nil {
                        cell = FbLinkPostViewCell.init(style: .Default, reuseIdentifier: self.linkCellIdentifier)
                    }
    //...
    

    Hi again. This is part from tableView's delegate, btw I copy-pasted Abhinav's inits to my code and again those inits aren't working. (no output to console)

  • drewpts
    drewpts over 8 years
    Thanks. Yep, I have the code just like you wrote. Seems like it is something around "dequeueReusableCellWithIdentifier" method. Please look at my comment on next answer.
  • drewpts
    drewpts over 8 years
    Thank you! I haven't if block in my code just like you wrote. So does dequeueReusableCellWithIdentifier invokes init like in if branch?
  • Gamma
    Gamma over 8 years
    No. The idea of reusable cells is that they are only initialized once and then reused. So init is only called if the cell hasn't previously been initialized (i.e. if we hit the content of the if). You would have to differentiate your cell classes with the identifier: check which class you need for the index, then check (with a unique identifier like "cell1", "cell2" and "cell3") if that particular type can be deqeued or needs to be initialised. If there are multiple cells of the same class (e.g. 3 "CellOne"s) that need to be configured individually, I would create a new method for that.
  • Gamma
    Gamma over 8 years
    Added some code to the answer that hopefully makes it more clear how this approach would work with your setup.
  • Abhinav
    Abhinav over 8 years
    I've updated my answer to include how it should look on cellForRowAtIndexPath:. When a UITableView instance calls for dequeueReusableTileWithIdentifier:, the cell is not reinitialized. Instead, in that call, the UITableViewCell that is dequeued will call prepareForReuse.
  • dpstart
    dpstart over 8 years
    How can I add a custom parameter to those inizializations?
  • Naishta
    Naishta almost 7 years
    cell = CellOne.init(style: .Default, reuseIdentifier: "cell") works ! I have been trying with let cell = WebTableViewCell(style: .default, reuseIdentifier: "webviewCell") without the .init and took me a while to get around. Thanks !