Jhonny
Jhonny

Reputation: 597

Unexpectedly found nil while unwrapping an Optional value swift

I'm getting an error message "Unexpectedly found nil while unwrapping an Optional" from Swift, with the below class. The error occurs on the line:

(cell.contentView.viewWithTag(1) as UILabel).text = object["firstName"] as? String

I have a custom cell class with 2 UILabels tagged 1 and 2, the outlets are set

    import UIKit
    import Foundation

    class dictionaryTableViewController: UIViewController,       UITableViewDelegate, UITableViewDataSource{

    var objects = NSMutableArray()
    var dataArray = [["firstName":"Debasis","lastName":"Das","email":"[email protected]"],["firstName":"John","lastName":"Doe","email":"[email protected]"],["firstName":"Jane","lastName":"Doe","email":"[email protected]"],["firstName":"Mary","lastName":"Jane","email":"[email protected]"]]

    @IBOutlet
    var tableView: UITableView!

    var items: [String] = ["We", "Heart", "Swift"]

    override func viewDidLoad() {
        super.viewDidLoad()
        self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "MyCell")
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }


    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dataArray.count;//self.items.count;
    }

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        //var cell:UITableViewCell = self.tableView.dequeueReusableCellWithIdentifier("cell") as UITableViewCell
        //cell.textLabel?.text = self.items[indexPath.row]
        //return cell
        let cell = tableView.dequeueReusableCellWithIdentifier("MyCell", forIndexPath: indexPath) as UITableViewCell
        let object = dataArray[indexPath.row] as NSDictionary
        (cell.contentView.viewWithTag(1) as UILabel).text = object["firstName"] as? String
        (cell.contentView.viewWithTag(2) as UILabel).text = object["lastName"] as? String
        return cell
    }

    func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {

        println("You selected cell #\(indexPath.row)!")

    }

}

Upvotes: 8

Views: 17462

Answers (3)

Rob
Rob

Reputation: 437432

If you want to use a custom cell layout, I would suggest

  1. Subclass UITableViewCell:

    //  CustomTableViewCell.swift
    
    import UIKit
    
    class CustomTableViewCell: UITableViewCell {
    
        @IBOutlet weak var firstNameLabel: UILabel!
        @IBOutlet weak var lastNameLabel: UILabel!
    
    }
    
  2. Create table view in storyboard. Add cell prototype to tableview in storyboard. Design that cell prototype (adding whatever labels you want).

  3. Specify the storyboard identifier for the cell prototype to be whatever you want (MyCell in your example).

    enter image description here

  4. Specify the base class for the cell prototype to be, in this example, CustomTableViewCell:

    enter image description here

  5. Hook up the IBOutlet references between your cell prototype and your UITableViewCell subclass.

    enter image description here

    Notice the solid bullets to the left of the IBOutlet references. That indicates that the outlet was hooked up correctly.

  6. Implement cellForRowAtIndexPath in your data source, e.g. in Swift 3:

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath) as! CustomTableViewCell
    
        let person = dataArray[indexPath.row]
    
        cell.firstNameLabel.text = person["firstName"]
        cell.lastNameLabel.text = person["lastName"]
    
        return cell
    }
    

    Or in Swift 2:

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("MyCell", forIndexPath: indexPath) as! CustomTableViewCell
    
        let person = dataArray[indexPath.row]
    
        cell.firstNameLabel.text = person["firstName"]
        cell.lastNameLabel.text = person["lastName"]
    
        return cell
    }
    
  7. Note, if using cell prototypes, you do not want to call registerClass in viewDidLoad. The storyboard will automatically register your custom class with the reused identifier for you (because of what you have done in steps 3 and 4).

Upvotes: 23

Rafael Fernandes
Rafael Fernandes

Reputation: 100

All I had to do was:

cell.subviews[0].subviews[0].viewWithTag(//your tag//)

Upvotes: 0

Ben
Ben

Reputation: 3485

EDIT2: This solution is maybe better for your purpose. With this you create your own class for your own UITableViewCell and use that with reusing pattern. Comments are in the code:

    class dictionaryTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource{

    // The data
    var dataArray = [["firstName":"Debasis","lastName":"Das","email":"[email protected]"],["firstName":"John","lastName":"Doe","email":"[email protected]"],["firstName":"Jane","lastName":"Doe","email":"[email protected]"],["firstName":"Mary","lastName":"Jane","email":"[email protected]"]]

    // The outlet to your tableview in the ViewController in storyboard
    @IBOutlet var tableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()

        // Create a nib for reusing
        let nib = UINib(nibName: "MyCell", bundle: nil)
        // Register the nib for reusing within the tableview with your reuseidentifier
        tableView.registerNib(nib, forCellReuseIdentifier: "MyCell")

        // Set delegate and datasource to self (you can do that in interface builder as well
        tableView.delegate = self
        tableView.dataSource = self
    }

    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dataArray.count; // return the number of datasets
    }

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

        // create a reusable cell with the reuse identifier you registered in viewDidLoad and cast it to MyCell
        let cell = tableView.dequeueReusableCellWithIdentifier("MyCell") as MyCell
        let object = dataArray[indexPath.row] as NSDictionary

        // set the strings matching your array
        cell.label1.text = object["firstName"] as? String
        cell.label2.text = object["lastName"] as? String

        // return the cell
        return cell

    }

    // some example for the delegate
    func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        println("You selected cell #\(indexPath.row)!")
    }


}


// class for you own UITableViewCell
// has own xib file
class MyCell: UITableViewCell {

    // two outlets representing two labels in the xib file
    @IBOutlet private weak var label1: UILabel!
    @IBOutlet private weak var label2: UILabel!
}

For this to work you have to create a xib-file with the right name (in this case MyCell). It should something similar to this:

enter image description here

It is very important to set the class of the custom view and the outlets.

------------------------------------- OTHER SOLUTION --------------------------------------

EDIT: As I see it you don't initialize your own class ´MyCell´ but Apple's class ´UITableViewCell´ with the reuseIdentifier ´MyCell´. So that is the solution for using UITableViewCell:

The line (cell.contentView.viewWithTag(1) as UILabel) is not only a cast to a specific class it is also unwrapping the Optional (UIView). You do that in other cases with '!'. So the problem is: you are creating an instance of UITableViewCell, a class Apple created. You want to get subviews inside of that view with specific tags. How do you know apple gave them these tags? What you really want is creating an instance of UITableViewCell with a special style and set the values of the textLabels like this:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        // Create an instance of UITableViewCell. Style is .Subtitle but could be others as well
        var cell = tableView.dequeueReusableCellWithIdentifier("MyCell") as? UITableViewCell
    let object = dataArray[indexPath.row] as NSDictionary

    // set the strings matching your array
    cell?.textLabel?.text = object["firstName"] as? String
    cell?.detailTextLabel?.text = object["lastName"] as? String

    // return the cell
    return cell!
    }

Upvotes: 5

Related Questions