1

I have three different types of custom UITableCells. I have an if statement that sets them up:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {                
    if somePosts[indexPath.row].typeOfPost == .linkPost {
        let cell: LinkTableViewCell = self.tableView.dequeueReusableCell(withIdentifier: "linkTableViewCell") as! LinkTableViewCell

    } else if somePosts[indexPath.row].typeOfPost == .picturePost {
        let cell: PictureTableViewCell = self.tableView.dequeueReusableCell(withIdentifier: "pictureTableViewCell") as! PictureTableViewCell

    } else if somePosts[indexPath.row].typeOfPost == .textPost {
        let cell: TextTableViewCell = self.tableView.dequeueReusableCell(withIdentifier: "textTableViewCell") as! TextTableViewCell


    } else {
        print("Type of post is not link, picture, or text")
    }
}

Each of the custom cells has similar labels such as title and time. I would like to set these labels using the same line of code, such as:

cell.titleLabel.text = "Some title here"

However, in this example, I get an error saying I am using an unresolved identifier "cell," obviously because my variables are being declared non-globally. Is there a way around this since swift is strongly typed? Thanks!

mfaani
  • 33,269
  • 19
  • 164
  • 293
SomeGuy
  • 3,725
  • 3
  • 19
  • 23
  • 2
    Not related but retrieving the type three times from the data source array is unnecessarily expensive. You should use a switch statement `switch somePosts[indexPath.row].typeOfPost { case .linkPost ... ` – vadian Jan 13 '17 at 18:14
  • Thanks, that's helpful. I'm pretty new to this, so any advice is always appreciated – SomeGuy Jan 13 '17 at 18:20
  • There is so much repeated code here. Stop the madness. – Alexander Jan 13 '17 at 18:27
  • Other than what vadian said? All advice appreciated; I'm not the best coder. Make me better! – SomeGuy Jan 13 '17 at 18:30
  • @SomeGuy See my comments with Alexander's answer. They will help u better understand POP – mfaani Jan 13 '17 at 22:36

3 Answers3

2

Make a protocol that your TableViewCell classes extend, and store cell as a variable of that type.

protocol MyTableViewCell {
    var titleLabel: UILabel { get }
    // ...
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    let identifier: String

    switch somePosts[indexPath.row].typeOfPost {
        case .linkPost: identifier = "linkTableViewCell"
        case .picturePost: identifier = "pictureTableViewCell"
        case .textPost: identifier = "textTableViewCell"
        default: fatalError("Type of post is not link, picture, or text")
    }

    guard let cell = self.tableView.dequeueReusableCell(withIdentifier: identifier) as? MyTableViewCell else {
        fatalError("Cell isn't castable to MyTableViewCell")
    }

    cell.titleLabel.text = "Some title here" 
    // ...
}
Alexander
  • 59,041
  • 12
  • 98
  • 151
  • Alright, I really suck at this and I apologize in advance. It keeps crashing on the second fatalError line. Not sure why though... Do all of the labels have to be the same? Some are different and some are the same in each custom cell. I was trying to do it this way so the ones that are the same don't need to be typed multiple times. – SomeGuy Jan 13 '17 at 18:46
  • @SomeGuy Did you forget to make your existing classes conform to this new protocol? – Alexander Jan 13 '17 at 18:59
  • 0. I didn't understand why he got the error. Your code looks almost the same 1. I think it would be better if you remove 'post' from each case. just write 'link' & also have a better name for the protocol, though can't think of a good one now. 2. while none of the classes subclass, they should all conform to indicate they have a label? That't the POPness of it. 3. And say I wanted to have a button, I would just add it in the protocol? 4. What about how to position them? Is there way you can make the layout protocol oriented? – mfaani Jan 13 '17 at 21:14
  • 5. about your comment to OP, since all your protocol is doing is *having a variable*, well can't he just do that without a protocol? I mean specifying the `: MyTableViewCell` is irrelevant to the compiler (we don't have any functions or a delegate), only that it makes our intent more clearer. right? – mfaani Jan 13 '17 at 21:14
  • 1
    @Honey **0.** I suspect he didn't make his classes (`LinkTableViewCell`, `TextTableViewCell`, etc.) conform to MyTableViewCell. **1.** Yes, the protocol does need a better name, but I don't know enough about the problem domain to be able to suggest one. **2.** Yes, his classes should conform to this protocol in order for it to be possible to reference instances of those classes as `MyTableViewCell`, without regard for their concrete types. **3.** Perhaps, if that button was a logical part of the protocol. **4.** Apple has a video on this. – Alexander Jan 13 '17 at 21:46
  • 1
    @Honey **5.** It's necessary in order to express to the compiler that no matter what concrete type (`LinkTableViewCell`, `Text...`, `Picture...`) is of an instance, we can always be sure that it'll be a type that has a `titleLabel`, which we can use without caring for any other details of the type. – Alexander Jan 13 '17 at 21:47
  • 1
    @Honey Suppose the protocol didn't exist, and `cell` had type `NSTableViewCell` instead. All of the instance we're dealing with here would be assignable to `cell`. However, when we say `cell.titleLabel`, the compiler will give an error. `NSTableViewCell` doesn't have a `titleLabel`, so there's no guarantee that the concrete types of the instances have a `titleLabel`. The protocol formalizes a guarantee that `titleLabel` will exist for `cell`, no matter its concrete type. – Alexander Jan 13 '17 at 21:51
  • **2.** ohhhhhh. so the primary type comes from the protocol type (similar to abstract) and the class itself becomes secondary. I now understand ALL the downcasting we do...to the concrete type. You just took my POP understanding to a whole new level **5.** it's only necessary if **ensuring** is important and we can also start with writing the protocols first. Otherwise we could manually make sure all tableViews have label, but I get it it makes things more interlocking. Super thanks Alexander – mfaani Jan 13 '17 at 22:00
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/133118/discussion-between-alexander-and-honey). – Alexander Jan 13 '17 at 22:06
  • Alexander, sorry to bother, can you take a look into this question [here](https://stackoverflow.com/questions/46653085/how-to-add-protocol-type-as-subview) I was trying to follow some of the things I learned from you here, but ran into some issues – mfaani Oct 09 '17 at 19:34
0

You have three basic solutions.

  1. Repeat cell.text = ... inside each block. But this isn't what you really want as stated in your question.
  2. Have your three custom cell classes all extend a common base class. Have this base class define any common properties.
  3. Define a protocol with the common properties and have each of your custom cell classes conform to the protocol.

For options 2 and 3 you would declare a variable of the base/protocol type before the first if statement. Then after the whole if/else block, you can assign any of the common properties.

If you need to update any cell type specific properties, you can do that inside the appropriate block as well.

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    var cell: BaseTableViewCell?
    if somePosts[indexPath.row].typeOfPost == .linkPost {
        cell = self.tableView.dequeueReusableCell(withIdentifier: "linkTableViewCell") as! LinkTableViewCell
    } else if somePosts[indexPath.row].typeOfPost == .picturePost {
        cell = self.tableView.dequeueReusableCell(withIdentifier: "pictureTableViewCell") as! PictureTableViewCell
    } else if somePosts[indexPath.row].typeOfPost == .textPost {
        cell = self.tableView.dequeueReusableCell(withIdentifier: "textTableViewCell") as! TextTableViewCell
    } else {
        print("Type of post is not link, picture, or text")
    }

    if let cell = cell {
        cell.commonProperty = ...

        return cell
    } else {
        return nil // this shouldn't happen but if it does, you have a bug to fix
    }
}
rmaddy
  • 314,917
  • 42
  • 532
  • 579
0

If the subclasses each have their own titleLabel property, you will need to make them all conform to a protocol. Let's call it ConfigurableCell.

protocol ConfigurableCell {
    var titleLabel: UILabel { get set }
}

Then, you can initialize your cells all the same way, but declare them as a ConfigurableCell:

var cell: ConfigurableCell? = nil    // not set yet
if somePosts[indexPath.row].typeOfPost == .linkPost {
    cell = self.tableView.dequeueReusableCell(withIdentifier: "linkTableViewCell") as! LinkTableViewCell
} else if somePosts[indexPath.row].typeOfPost == .picturePost {
    cell = self.tableView.dequeueReusableCell(withIdentifier: "pictureTableViewCell") as! PictureTableViewCell
} else if somePosts[indexPath.row].typeOfPost == .textPost {
    cell = self.tableView.dequeueReusableCell(withIdentifier: "textTableViewCell") as! TextTableViewCell
} 

guard let cell = cell else {
    // how to handle this error case is up to you
    print("Type of post is not link, picture, or text")
    return UITableViewCell()    
}

// now, cell is a ConfigurableCell with a titleLabel property, regardless of class
cell.titleLabel.text = "Some title"

Of course, UITableViewCell does have a built-in textLabel property, which you could try to utilize in your cell classes, and then a protocol wouldn't be necessary, because the property is in UITableViewCell.

Connor Neville
  • 7,291
  • 4
  • 28
  • 44
  • Hey, I'm new at this, so don't kill me if this is a stupid question, but when I declare the protocol, I get an error: "Property in protocol must have explicit {get} or {get set} specifier. – SomeGuy Jan 13 '17 at 18:21
  • Alright that fixed that error, but now I'm getting "Cannot assign value of type "LinkTableViewCell" (and all the other types of cell) to type ConfigurableCell?" – SomeGuy Jan 13 '17 at 18:25
  • Did you make your classes conform to the protocol? `class LinkTableViewCell: UITableViewCell, ConfigurableCell { ...` – Connor Neville Jan 13 '17 at 18:36
  • You need to add protocol conformance (either directly or with an extension) for all your table cell types. I highly recommend reading the entire [protocols section of the Swift language guide](https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html) as this is one of the most important parts of understanding how to write good Swift code. – jhabbott Jan 13 '17 at 18:38