iosswiftuitableviewforced-unwrapping

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


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)
}

}


Solution

  • 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