0

This is a part of my storyboard:

Image

this is my running app:

Image

This is my part of codes:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        if indexPath.section == 0 {
            if indexPath.row == 0 {
                 return super.tableView(tableView, cellForRowAt: indexPath)
            } else {
                tableView.register(SubTextFieldCell.self, forCellReuseIdentifier: "SubTextFieldCell")
                let cell = tableView.dequeueReusableCell(withIdentifier: "SubTextFieldCell", for: indexPath) as! SubTextFieldCell

//                cell.deleteButton.isEnabled = true
//                cell.subTextfield.text = "OK"

                print("indexPath.row: \(indexPath.row)")

                return cell
            }
...

I have already connected the button and the textfield in various places and I can guarantee that this part is not wrong, but when I click the Add button in the first row, I only get a cell without any content.

If I use code like this cell.deleteButton..., Xcode will report an error:

Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value

Then I tried to use the viewWithTag method to see if show the content, but I still get the same error as before.

This is the first time I have encountered this kind of error. I have no error with similar code and methods in my other programs.

Vinodh
  • 5,262
  • 4
  • 38
  • 68
K.B.
  • 291
  • 2
  • 13

1 Answers1

0

When you configure custom cells inside a storyboard file, you don't need to call register(_:forCellReuseIdentifier:) because the storyboard should have done that for you.

The reason deleteButton is nil is because by re-registering the cell class as you did, you overwrote what the storyboard registered for you. All cells created by dequeueing with that reuse identifier will have no connection to the storyboard and simply be empty.

Assuming all the @IBOutlets and reuse identifiers and things are set up (which you said you did), then simply dequeue the cell with the reuse identifier set up in storyboard.

Dequeue Cell Example:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    if indexPath.section == 0 {
        if indexPath.row == 0 {
            return super.tableView(tableView, cellForRowAt: indexPath)
        } else {
            // Registering again is unnecessary, because the storyboard should have already done that.
//            tableView.register(SubTextFieldCell.self, forCellReuseIdentifier: "SubTextFieldCell")
            let cell = tableView.dequeueReusableCell(withIdentifier: "SubTextFieldCell") as! SubTextFieldCell

            cell.deleteButton.isEnabled = true
            cell.subTextfield.text = "OK"

            return cell
        }
    } else {
        ...
    }
}

Note:

Even in cases where you do need to register a class with a table view, you should only have to do this once. (For example, during viewDidLoad)

Even in those times, you should not call it every time you dequeue a cell. You're just making your app work harder.

Connecting views to cells in Storyboard

Set a subclass to the table view Subclass set to Table View in Storyboard

Set a subclass to the first prototype cell Subclass set to prototype cell

Set a reuse identifier to the prototype cell enter image description here

Make sure subview (UIButton, etc.) is connected to property with @IBOutlet (subclass code shown below) IB Outlet set to button, as seen by cell IB Outlet set to button, as seen by button

Example UITableViewController subclass:

class MyTableViewController: UITableViewController {

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 2
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        if indexPath.row == 0 {
            let cell = tableView.dequeueReusableCell(withIdentifier: "MyFirstCell", for: indexPath) as! MyFirstTableViewCell

            // Configure cell if needed
            cell.myButton.setTitle("New Button Text", for: .normal)
            cell.myButton.tintColor = .green

            return cell
        } else {
            let cell = tableView.dequeueReusableCell(withIdentifier: "MySecondCell", for: indexPath) as! MySecondTableViewCell

            // Configure cell if needed
            cell.myTextField.backgroundColor = .red

            return cell
        }
    }

}

Example UITableViewCell subclass:

class MyFirstTableViewCell: UITableViewCell {

    @IBOutlet weak var myButton: UIButton!

}

Result:

Simulator screenshot showing changes to cell subviews

ABeard89
  • 911
  • 9
  • 17
  • I did this, but get an error: *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'unable to dequeue a cell with identifier SubTextFieldCell - must register a nib or a class for the identifier or connect a prototype cell in a storyboard', you can see my another question: https://stackoverflow.com/questions/50445191/why-uitableviewcell-needs-to-be-registered – K.B. May 23 '18 at 01:11
  • That's telling you that you did not set the reuse identifier properly. I've attached a snapshot to show you where to set this. – ABeard89 May 23 '18 at 01:23
  • For every type of cell you create in a storyboard, set a different `Identifier` as shown in my screenshot. Then use that same string to dequeue cells in code. – ABeard89 May 23 '18 at 01:25
  • Thank you but I had set Identifier in storyboard and I checked many times, is there any other possibility of error? – K.B. May 23 '18 at 01:30
  • Did you connect the cell's button and text field to `@IBOutlet`s in a cell subclass and set that subclass to the cell in storyboard? (Looking at your other question, you said you're using tags.) – ABeard89 May 23 '18 at 01:41
  • All connected and set, I have not used viewWithTag anymore. – K.B. May 23 '18 at 01:49
  • The table view cell disagrees with you. Please make sure you have everything set up as detailed by my latest screenshots. – ABeard89 May 23 '18 at 02:05
  • At each step, I'm sure there are no mistakes, and similar code can run flawlessly in my Demo, but it's not in this program. – K.B. May 24 '18 at 01:08
  • I found a small difference, I do not know if it will affect, my cell is not directly on the tableView, but in the first section of the tableView. – K.B. May 24 '18 at 01:12
  • Are you still getting the error `unable to dequeue a cell with identifier SubTextFieldCell`? – ABeard89 May 24 '18 at 01:14
  • My example cells are in the first section, too. I just assume I only have one section. You have more sections, which is fine. – ABeard89 May 24 '18 at 01:17
  • Yes, it is still that error. I have no other solution now. – K.B. May 24 '18 at 01:29
  • Is your table view controller's `storyboard` property nil? – ABeard89 May 24 '18 at 01:32
  • I found the problem. In order to use multiple sections, I used the tableview's Content property to set it as a Static Cell. The same code can be run under Dynamic Prototypes, but the Static Cell cannot run. Do you have a way to make code run under a Static Cell? – K.B. May 26 '18 at 02:36
  • Glad you found the problem! I've never worked with static cells, so I didn't even think about that. The biggest difference between static and dynamic cells is that you control the cell counts via `UITableViewDataSource` and dequeue cells via `UITableViewDelegate`. The downside to this is that there is no semi-dynamic way to do this. Either it's fully static, or it's fully dynamic. – ABeard89 May 28 '18 at 01:30
  • But for the static cell cases, you shouldn't need to do much more than dequeue the cell and return it. The Storyboard should take care of most of its configuration. – ABeard89 May 28 '18 at 01:31