简体   繁体   中英

Swift - Type Casting

I am building a custom UITableView with custom cells. Each of the custom cells are a subclass of FormItemTableViewCell

I am attempting to populate the cell data in cellForRowAtIndexPath

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

        var cell = FormItemTableViewCell();

        if(indexPath.row == 1){
            cell = tableView.dequeueReusableCellWithIdentifier(twoOptionCellIdentifier, forIndexPath: indexPath) as! TwoOptionTableViewCell
        } else {
            cell = tableView.dequeueReusableCellWithIdentifier(oneTextFieldCellIdentifier, forIndexPath: indexPath) as! OneTextFieldTableViewCell
        }



        cell.questionLabel.text = "What is the meaning of life?";

        return cell
    }

How do I access the elements in the subclass?

For example: TwoOptionTableViewCell has a segControl while the OneTextFieldTableViewCell has a answerTextField

You can use one of the two approaches:

1) The best way:

    if(indexPath.row == 1) {
        let cell = tableView.dequeueReusableCellWithIdentifier(twoOptionCellIdentifier, forIndexPath: indexPath) as! TwoOptionTableViewCell
        // the type of cell is TwoOptionTableViewCell. Configure it here.
        return cell
    } else {
        let cell = tableView.dequeueReusableCellWithIdentifier(oneTextFieldCellIdentifier, forIndexPath: indexPath) as! OneTextFieldTableViewCell
        // the type of cell is TwoOptionTableViewCell. Configure it here.
        return cell
    }

2) If you declare cell just once, as a superclass, then you have to downcast it like this.

var cell: FormItemTableViewCell

cell = ... // dequeue and assign the cell like you do in your code.

if let twoOptionCell = cell as? TwoOptionTableViewCell
{
     // configure twoOptionCell
}
else if let oneTextFieldCell = cell as? OneTextFieldTableViewCell
{
     // configure oneTextFieldCell
}

return cell

This is more verbose, once you add the code to dequeue the cell. So I personally prefer and recommend the first approach.

There are some decent answers in this question but most of them have one bad thing in common, they force unwrapped optionals, which you should avoid as much as you can (pretty much the only acceptable place to use them is in IBOutlets)

This is what I think is the best way to handle this:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    guard let cell = tableView.dequeueReusableCellWithIdentifier("Identifier", forIndexPath: indexPath) as? FormItemTableViewCell else {
        fatalError("Cell is not of kind FormItemTableViewCell")
    }

    switch cell {
    case let cell as TwoOptionTableViewCell where indexPath.row == 1:
        // Configure cell, which is an object of class TwoOptionTableViewCell, but only when we are in row 1
        break
    case let cell as TwoOptionTableViewCell:
        // Configure cell, which is an object of class TwoOptionTableViewCell, when the row is anything but 1
        break
    case let cell as OneTextFieldTableViewCell:
        // Configure cell, which is an object of class OneTextFieldTableViewCell
        break
    case _: print("The cell \(cell) didn't match any patterns: \(indexPath)")
    }

    cell.questionLabel.text = "What is the meaning of life?";

    return cell
}

Now let me walk you through the reasons I think it's the best way.

First of all, it doesn't force unwraps any optionals, everything is unwrapped nicely in the switch case.

It dequeues your cell from the table (something you should always do) and makes sure it's a subclass of FormItemTableViewCell , otherwise it throws a fatal error.

By using a switch case, it casts cell into the class you need, and at the same time it checks if it's the index path you want. So if you want to share some logic in different rows that share a class, you can compare indexPath.row to multiple values. If you don't use the where clause, it will use the same logic in all places where it finds a cell with that class.

Do note that you will need to add some logic to get the desired identifier depending on the row.

If I understand correctly, you want to keep main declaration of cell as FormItemTableViewCell to access common properties.

You can create a new variable and assign it the casted version. Do your stuff with this instance as this is a class object it will point to same reference.

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

    var cell = FormItemTableViewCell();
    // this can be replaced with below line as I don't see the purpose of creating an instance here while you use dequeue below.
    // var cell: FormItemTableViewCell!

    if(indexPath.row == 1){
        cell = tableView.dequeueReusableCellWithIdentifier(twoOptionCellIdentifier, forIndexPath: indexPath);
        let tempCell = cell as! TwoOptionTableViewCell;
        // access members of TwoOptionTableViewCell on tempCell
        tempCell.segControl.someProperty = 0;
    } else {
        cell = tableView.dequeueReusableCellWithIdentifier(oneTextFieldCellIdentifier, forIndexPath: indexPath);
        let tempCell = cell as! OneTextFieldTableViewCell;
        // access members of OneTextFieldTableViewCell on tempCell
        tempCell.answerTextField.text = "42";
    }



    cell.questionLabel.text = "What is the meaning of life?";

    return cell
}

You're going to have to conditionally cast them in that case. I like using Enums for Rows/Sections instead of == 1 (depending on how your TableView is setup), but basically you'll want to do the following:

if indexPath.row == 1 {
    let cell = tableView.dequeueReusableCellWithIdentifier(twoOptionCellIdentifier, forIndexPath: indexPath) as! TwoOptionTableViewCell
    // Note that we cast the cell to TwoOptionTableViewCell
    // access `segControl` here
    return cell
} else {  
  let cell = tableView.dequeueReusableCellWithIdentifier(oneTextFieldCellIdentifier, forIndexPath: indexPath) as! OneTextFieldTableViewCell
  // This cell we cast to OneTextFieldTableViewCell. 
  // access `answerTextField` here
  return cell
}

What you were doing was defining the cell as FormItemTableViewCell , so subsequent accesses would only know it in that form even though you explicitly cast it to a subclass during assignment.

As a side-note, you don't have to assign to the var as you did there, what you could do is let cell: FormItemTableViewCell . Then in the if-statements you could define new cells of the subclasses, operate on them, and then assign back to your original cell and then return that. This is useful if you're going to be performing the same operations on both cell types after the if statements (such as setting a background colour or something, regardless of which subclass you have).

Here is my favourite way of handling this situation:

enum CellTypes {
  case TwoOption, OneTextField
  init(row: Int) {
    if row == 1 {
      self = .TwoOption
    } else {
      self = .OneTextField
    }
  }

  var reuseIdentifier: String {
    switch self {
    case .TwoOption: return "twoOptionReuseIdentifier"
    case .OneTextField: return "oneTextFieldReuseIdentifier"
    }
  }
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
  let cell: FormItemTableViewCell

  let cellType = CellTypes(row: indexPath.row)
  switch cellType {
  case .TwoOption:
    let twoOptionCell = tableView.dequeueReusableCellWithIdentifier(cellType.reuseIdentifier, forIndexPath: indexPath) as! TwoOptionTableViewCell
    // do stuff with the `segControl`
    cell = twoOptionCell
  case .OneTextField:
    let textFieldCell = tableView.dequeueReusableCellWithIdentifier(cellType.reuseIdentifier, forIndexPath: indexPath) as! OneTextFieldTableViewCell
    // do stuff with the `answerTextField`
    cell = textFieldCell
  }

  // Here do something regardless of which CellType it is:
  cell.questionLabel.text = "What is the meaning of life?"

  return cell
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM