How could UILabel always be nil -- Unexpectedly found nil while implicitly unwrapping an Optional value

117 views Asked by At

As many people encountered, I tried to build tableView. I found many similar questions but it seems answers are not helping. I would be very grateful if anyone could help me. The problem I encountered:

Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value

This is a description Xcode gives me

Here's what I did:

(1) I connected Labels in the storyboard to the class it related to, which should be right as it's not hollow.

(2) I used tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath), and I tried to print cell I got, all cells aren't nil and belongs to CollegeTableViewCell, which is correct.

(3) I changed the identifier of tableViewCell to Cell which matches, and I changed it's class to CollegeTableViewCell too.

My program crashed directly when it executes following code. I only works when I make labels optional. So the problem is what did I do wrong so that labels in cell are always nil?

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! CollegeTableViewCell
    let college = colleges[indexPath.row]

    cell.collegeName.text = college.name // <-CRASH
    cell.collegeGeo.text = college.city + ", " + college.state
    return cell
}

Following is my CollegeTableViewCell class:

class CollegeTableViewCell: UITableViewCell {


@IBOutlet weak var collegeName: UILabel!
@IBOutlet weak var collegeGeo: UILabel!

override func awakeFromNib() {
    super.awakeFromNib()
    // Initialization code
}

}

EDIT: more codes related to this problem.

class CollegeChooseViewController: UIViewController {

@IBOutlet weak var searchBar: UISearchBar!
@IBOutlet weak var tableView: UITableView!

var colleges = [CollegeInfo]()
let searchController = UISearchController(searchResultsController: nil)
let collegeApiUrl = "https://api.collegeai.com/v1/api/autocomplete/colleges?api_key=b47484dd6e228ea2cc5e1bf6ca&query="

override func viewDidLoad() {
    super.viewDidLoad()
    tableView.delegate = self
    tableView.dataSource = self
    tableView.register(CollegeTableViewCell.self, forCellReuseIdentifier: "Cell")
    getColleges(contentInSearch: "MIT")
}

func getColleges(contentInSearch: String) {
    guard let url = URL(string: (collegeApiUrl + contentInSearch)) else { return }
    URLSession.shared.fetchData(for: url) {(result: Result<Initial, Error>) in
        switch result {
        case .success(let initial):
            self.colleges = initial.collegeList
            DispatchQueue.main.async {
                self.tableView.reloadData()
            }
        case .failure(let error):
            print("failed fetching college list from API: \(error)")
        }
    }
}

}

extension URLSession {
func fetchData<T: Decodable>(for url: URL, completion: @escaping (Result<T, Error>) -> Void) {
self.dataTask(with: url) { (data, response, error) in
  if let error = error {
    completion(.failure(error))
  }
  if let data = data {
    do {
      let object = try JSONDecoder().decode(T.self, from: data)
        completion(.success(object))
    } catch let decoderError {
      completion(.failure(decoderError))
    }
  }
}.resume()

} }

extension CollegeChooseViewController: UITableViewDataSource, UITableViewDelegate {

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! CollegeTableViewCell
    let college = colleges[indexPath.row]

    cell.collegeName.text = college.name // <-CRASH
    cell.collegeGeo.text = college.city + ", " + college.state
    return cell
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    print(colleges.count)
    return colleges.count
}

}

class CollegeTableViewCell: UITableViewCell {


@IBOutlet weak var collegeName: UILabel!
@IBOutlet weak var collegeGeo: UILabel!

override func awakeFromNib() {
    super.awakeFromNib()
    // Initialization code
}

override func setSelected(_ selected: Bool, animated: Bool) {
    super.setSelected(true, animated: true)
}

}

2

There are 2 answers

5
Fabio On BEST ANSWER

This is a sample of your tableview programmatically way... If I don't know where your data comes from, I used a simulation of your arrays... conform your controller to UITableViewDelegate and Datasource:

class YourController: UIViewController, UITableViewDelegate, UITableViewDataSource 

Now set tableView and constraints

var name = ["Mike", "Jhon", "Carl", "Steve", "Elon", "Bill", "Bruce"] // simulation of your array
var city = ["Milano", "New Yor", "Paris", "Los Angeles", "Madrid", "Amsterdam", "Tokyo"] // simulation of your array
var state = ["Italia", "USA", "France", "USA", "Spain", "Holland", "Japan"] // simulation of your array

let tableView = UITableView()

override func viewDidLoad() {
    super.viewDidLoad()

    view.backgroundColor = .darkBlue
    
    tableView.backgroundColor = .white
    tableView.register(CollegeTableViewCell.self, forCellReuseIdentifier: "cellId") // register cell
    tableView.delegate = self
    tableView.dataSource = self
    tableView.translatesAutoresizingMaskIntoConstraints = false
    tableView.separatorColor = .lightGray
    
    view.addSubview(tableView)
    tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
    tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
    tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
    tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
    
    // this is my extension to configure navigation bar, you can configure it as you want
    configureNavigationBar(largeTitleColor: .red, backgoundColor: .black, tintColor: .red, title: "Sample", preferredLargeTitle: true)
}

After that set your tableView Delegate and DataSource:

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return UITableView.automaticDimension
}

// Mark: - set number of rows with your array.count
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return name.count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    
    let name = name[indexPath.row]
    let city = city[indexPath.row]
    let state = state[indexPath.row]
    
    let cell = tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath) as! CollegeTableViewCell
    cell.collegeName.text = name
    cell.collegeGeo.text = "\(city), \(state)"
    
    return cell
}

This is how your cell look like:

class CollegeTableViewCell: UITableViewCell {

let collegeName: UILabel = {
    let label = UILabel()
    label.textColor = .white
    label.font = .systemFont(ofSize: 16, weight: .semibold)
    label.backgroundColor = .clear
    label.translatesAutoresizingMaskIntoConstraints = false
    
    return label
}()

let collegeGeo: UILabel = {
    let label = UILabel()
    label.textColor = .white
    label.font = .systemFont(ofSize: 14, weight: .semibold)
    label.backgroundColor = .clear
    label.translatesAutoresizingMaskIntoConstraints = false
    
    return label
}()

override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
    super.init(style: style, reuseIdentifier: reuseIdentifier)
    
    contentView.backgroundColor = .ultraDark
    
    let stackView = UIStackView(arrangedSubviews: [collegeName, collegeGeo]) // use stack view for automatic table view dimension
    stackView.axis = .vertical
    stackView.distribution = .fillEqually
    stackView.spacing = 2
    stackView.translatesAutoresizingMaskIntoConstraints = false
    
    contentView.addSubview(stackView)
    stackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 20).isActive = true
    stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20).isActive = true
    stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20).isActive = true
    stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20).isActive = true
}

required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
 }
}

this is the result:

enter image description here

EDIT based on new information full code and Json decoder:

struct CollegeInfo: Decodable {
let collegeList: [MyDataResults]
}

struct MyDataResults: Decodable {
let id: String
let name: String
let city: String
let state: String
}

class tableController: UIViewController, UITableViewDelegate, UITableViewDataSource {

var myData = [MyDataResults]() // simulation of your array
let urlString = "https://api.collegeai.com/v1/api/autocomplete/colleges?api_key=b47484dd6e228ea2cc5e1bf6ca&query="

let tableView = UITableView()

override func viewDidLoad() {
    super.viewDidLoad()

    view.backgroundColor = .darkBlue
    
    tableView.backgroundColor = .white
    tableView.register(CollegeTableViewCell.self, forCellReuseIdentifier: "cellId") // register cell
    tableView.delegate = self
    tableView.dataSource = self
    tableView.translatesAutoresizingMaskIntoConstraints = false
    tableView.separatorColor = .lightGray
    
    view.addSubview(tableView)
    tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
    tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
    tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
    tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
    
    // this is my extension to configure navigation bar, you can configure it as you want
    configureNavigationBar(largeTitleColor: .red, backgoundColor: .black, tintColor: .red, title: "Sample", preferredLargeTitle: true)
    
    fetchJson { [weak self] (res) in
        
        switch res {
        case .success(let dataResults):
            dataResults.forEach { (dataresult) in
                self?.myData.removeAll()
                
                DispatchQueue.main.asyncAfter(deadline: .now() + 0) {
                    self?.myData = dataresult.collegeList
                    self?.tableView.reloadData()
                }
            }
        case .failure(let err):
            print("Failed to fetch json", err)
        }
    }
}

fileprivate func fetchJson(completion: @escaping (Result<[CollegeInfo], Error >) -> ()) {
    
    guard let url = URL(string: urlString) else { return }
    URLSession.shared.dataTask(with: url) { data, resp, err in
        
        if let err = err {
            completion(.failure(err))
            return
        }
        
        do {
            guard let data = data else { return }
            let results = try JSONDecoder().decode(CollegeInfo.self, from: data)
            
            //succesful
            completion(.success([results]))
            
        } catch let jsonErr {
            completion(.failure(jsonErr))
        }
        
    }.resume()
}

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return UITableView.automaticDimension
}

// Mark: - set number of rows with your array.count
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return myData.count
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    
    let myResults = myData[indexPath.row]
    
    let cell = tableView.dequeueReusableCell(withIdentifier: "cellId", for: indexPath) as! CollegeTableViewCell
    cell.collegeName.text = myResults.name
    cell.collegeGeo.text = "\(myResults.city), \(myResults.state)"
    
    return cell
}

The cell:

class CollegeTableViewCell: UITableViewCell {

let collegeName: UILabel = {
    let label = UILabel()
    label.textColor = .white
    label.font = .systemFont(ofSize: 16, weight: .semibold)
    label.backgroundColor = .clear
    label.translatesAutoresizingMaskIntoConstraints = false
    
    return label
}()

let collegeGeo: UILabel = {
    let label = UILabel()
    label.textColor = .white
    label.font = .systemFont(ofSize: 14, weight: .semibold)
    label.backgroundColor = .clear
    label.translatesAutoresizingMaskIntoConstraints = false
    
    return label
}()

override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
    super.init(style: style, reuseIdentifier: reuseIdentifier)
    
    contentView.backgroundColor = .ultraDark
    
    let stackView = UIStackView(arrangedSubviews: [collegeName, collegeGeo]) // use stack view for automatic table view dimension
    stackView.axis = .vertical
    stackView.distribution = .fillEqually
    stackView.spacing = 2
    stackView.translatesAutoresizingMaskIntoConstraints = false
    
    contentView.addSubview(stackView)
    stackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 20).isActive = true
    stackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20).isActive = true
    stackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20).isActive = true
    stackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20).isActive = true
}

required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
 }
}

The result:

enter image description here

1
Shabnam Siddiqui On

you're using the wrong bundle name for dequeueReusableCell.

instead of cell use CollegeTableViewCell

it should be :

let cell = tableView.dequeueReusableCell(withIdentifier: "CollegeTableViewCell", for: indexPath) as! CollegeTableViewCell