Search code examples
iosswifttableviewalamofire

How to disable Tableview DequeueReusableCell?


I am trying to load my tableview without using dequeueReusableCell, but it just crashes my app, cant figure out what i am doing wrong?

let cell = Tableview.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! SökrutaCell       // THIS CODE WORKS FINE
        
let cell = SökrutaCell() // THIS CODE CRASHES - Unexpectedly found nil while unwrapping an optional

Would love if anyone could point me in the right direction so i can understand what is failing.

import UIKit
import Alamofire

class Sökruta: UIViewController {
    
    var sökresultat = [Object2]()
    @IBOutlet weak var Tableview: UITableView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        GetRequest(Url: "https://randomurl.se")

        Tableview.delegate = self
        Tableview.dataSource = self
        Tableview.backgroundColor = UIColor.white
        // Do any additional setup after loading the view.
    }
}

// MARK: - Delegate extension 1
extension Sökruta: UITableViewDelegate{
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("you tapped me!")
    }
}

// MARK: - Delegate extension 2
extension Sökruta: UITableViewDataSource{
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return sökresultat.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        //  let cell = Tableview.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! SökrutaCell       // THIS CODE WORKS FINE
        
        let cell = SökrutaCell()   // THIS CODE CRASHES - Unexpectedly found nil while unwrapping an optional
        
        cell.Söknamn.text = sökresultat[indexPath.row].displayname
        
        if let url = URL(string: dontmindthiscode) {
            
            DispatchQueue.global().async {
                do {
                    let data = try Data(contentsOf: url)
                    DispatchQueue.main.async {
                        cell.Sökbild.image = UIImage(data: data)
                    }
                } catch let err {
                    print("error: \(err.localizedDescription)")
                }
            }
        }
        else
        {
            DispatchQueue.main.async {
                cell.Sökbild.image = UIImage(systemName: "eye.slash")
            }
        }
        
        return cell
    }
}

// MARK: - Extension functions
extension Sökruta{
        
    // MARK: - GET REQUEST
    func GetRequest(Url: String)  {

        // MARK: - Login details
        let headers: HTTPHeaders = [
            .authorization(username: "username", password: "password"),
            .accept("application/json")]
        
        // MARK: - Api request
        AF.request(result, headers: headers).validate().responseJSON { response in
            
            // MARK: - Check for errors
            if let error = response.error
            {
                print (error)
                return}
            
            // MARK: - Print response
            if response.response != nil
            { }
            
            // MARK: - Print data
            if response.data != nil
            {
                let decoder = JSONDecoder()
                do
                {
                    let api = try decoder.decode(Main2.self, from: response.data!)
                
                    self.sökresultat = api.objects
                
                    self.Tableview.reloadData()
                }
               
                catch {
                    print(error.localizedDescription)
                    print("Error in JSON parsing")
                }
            }
        }
    }// MARK: - END
}

This is my cell.swift code:

import UIKit

class SökrutaCell: UITableViewCell {
    @IBOutlet weak var Sökbild: UIImageView!
    @IBOutlet weak var Söknamn: UILabel!

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

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

        // Configure the view for the selected state
    }
}

Solution

  • You have a class with @IBOutlet references to a prototype cell in your storyboard. You can't use that cell prototype (with its custom layout) and have the outlets hooked up for you unless you use the dequeueReusableCell(withIdentifier:for:).

    If your goal was to compare this against a non-reused cell, you could instantiate a UITableViewCell programmatically and use the built in textLabel and imageView properties. For example, you could:

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell(style: .default, reuseIdentifier: "...")
    
        cell.textLabel?.text = ...
        cell.imageView?.image = ...  // use a placeholder image here or else it won't show the image view at all!
    
        someMethodToFetchImageAsynchronously { image in
            // make sure this cell hasn't scrolled out of view!!!
            guard let cell = tableView.cellForRow(at: indexPath) else { return }
        
            // if still visible, update its image
            cell.imageView?.image = image
        }
        return cell
    }
    

    Or you could consider more complicated programmatic patterns (where you add controls and configure them manually, which probably would be a more fair comparison), too. But the above is a fairly minimal implementation.

    Needless to say, this pattern is not recommended, as you lose the storyboard cell prototype benefits and the performance/memory benefits of cell reuse. But if your goal was just to compare and contrast the memory, performance, and software design considerations, perhaps this helps you get your arms around it.


    While I've attempted to answer your question, before I worried about the inherent performance of dequeued cells, your image retrieval mechanism is a far greater performance concern. You are fetching images using Data(contentsOf:), a non-cancelable network request. So, if you have hundred rows, and you quickly scroll down to rows 90..<100, the image retrieval for those 10 rows will be backlogged being the network requests for the first 90 rows! Also, are your images appropriately sized for the small image view in the cell? That can also observably impact performance and the smoothness of the scrolling.

    There are a number of decent asynchronous image retrieval libraries out there. E.g. since you are already using Alamofire, I would suggest you consider AlamofireImage. This offers nice asynchronous image retrieval mechanisms, nice UIImageView extensions, the ability to cancel requests that are no longer needed, image caching, etc.

    But proper asynchronous image retrieval for a table view is a non-trivial problem. An image processing library like AlamofireImage, KingFisher, SDWebImage, etc., can simplify this process for you.