Search code examples
iosswiftuitableviewfirebasesdwebimage

UITableView cell correct after scrolling


I created a Swift UITableView of posts, each post including some text and a chart image. The image is loaded asynchronously using SDWebImage and Firebase. Images have different heights, but a fixed width.

Here is a short video showing the display issue : https://youtu.be/QzQFT2z0GjA

Some cells are not displayed correctly the first time, but look perfect after some scrolling. I read about using layoutIfNeeded and setNeedsLayout as suggested in this post or iOS 11 UITableViewAutomaticDimension but it does not seem to work in my case.

Here is my code :

var postArray : [Post] = [Post]()

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.

    postTableView.delegate = self
    postTableView.dataSource = self
    aLaUneWidthConstraint.constant = view.frame.size.width/2

    etatFranceWidthConstraint.constant = view.frame.size.width/2
    postTableView.register(UINib(nibName:"TableViewCell", bundle: nil), forCellReuseIdentifier: "postCell")



    activityIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.gray
    activityIndicator.startAnimating()

    retrievePosts()

}

override func viewWillAppear(_ animated: Bool) {
    self.navigationController?.isNavigationBarHidden = true
}

func numberOfSections(in tableView: UITableView) -> Int {
    return 1
}

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

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

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

    tableView.separatorStyle = UITableViewCellSeparatorStyle.none
    cell.selectionStyle = UITableViewCellSelectionStyle.none

    cell.postTitle.text = postArray[indexPath.row].title
    cell.postSource.text = postArray[indexPath.row].source
    cell.postChart.sd_setImage(with: URL(string: postArray[indexPath.row].chartURL!), placeholderImage: UIImage(named: "placeholder.png")) { (image, error, cache, url) in
        cell.chartHeightConstraint.constant = ((cell.postChart.image?.size.height)!/2)
        cell.setNeedsLayout()
        cell.layoutIfNeeded()
    }

    return cell

}

func retrievePosts() {

    let postDB = Database.database().reference().child("Posts")

    postDB.observe(.childAdded, with: { (snapshot) in

        let snapshotValue = snapshot.value as! NSDictionary

        let title = snapshotValue["title"] as! String
        let source = snapshotValue["source"] as! String
        let chartURL = snapshotValue["chartURL"] as! String
        let category = snapshotValue["category"] as! String

        let post = Post(data: snapshotValue)
        post.title = title
        post.source = source
        post.chartURL = chartURL
        post.category = category

        self.postArray.append(post)

        self.activityIndicator.stopAnimating()
        self.activityView.isHidden = true
        self.activityView.frame.size.height = 0
        self.postTableView.reloadData()

    })

}

Any idea? Thanks in advance.


Solution

  • The solution consists in adding chartWidth and chartHeight params in the Post object and adding their values for each post in the Firebase database, and then setting some constraints to precalculate the cell height before the image is downloaded.

    In TableViewCell.swift add :

    func setChartSize(size: CGSize) {
        postChart.removeConstraint(postChartRatioConstraint)
    
        postChartRatioConstraint = NSLayoutConstraint(
            item: postChart,
            attribute: .height,
            relatedBy: .equal,
            toItem: postChart,
            attribute: .width,
            multiplier: (size.height / size.width),
            constant: 0)
    
        postChart.addConstraint(postChartRatioConstraint)
    }
    

    In ViewController use the setChartSize function :

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    
        let cell = tableView.dequeueReusableCell(withIdentifier: "postCell", for: indexPath) as! TableViewCell
    
        tableView.separatorStyle = UITableViewCellSeparatorStyle.none
        cell.selectionStyle = UITableViewCellSelectionStyle.none
    
        let post = postArray[indexPath.row]
    
        cell.postTitle.text = post.title
        cell.postSource.text = post.source
    
        cell.postChart.sd_setShowActivityIndicatorView(true)
        cell.postChart.sd_setIndicatorStyle(.white)
    
        cell.setChartSize(size: CGSize(width: post.chartWidth!, height: post.chartHeight!))
        cell.postChart.sd_setImage(
            with: URL(string: post.chartURL!),
            placeholderImage: UIImage(named: "placeholder.png"),
            options: [.continueInBackground],
            completed: nil)
    
        return cell
    }
    

    Any other option like resizing the cell after the chart is downloaded generated scrolling jumps.