Search code examples
iosswiftxcodeuitableviewcell

weird tableview behavior when cell is selected


My tableview has a weird behavior when a cell is selected but this behavior is not seen always. When a cell has been selected some cells which are below the selected cell moves. These two gifs will show you the behavior in the two cases when it is shown and when it doesn't appear. this is the tableview without weird behavior and this is the tableview with the weird behavior and this is my code :

  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell : UserTableViewCell = (tableView.dequeueReusableCell(withIdentifier: "userCell") as? UserTableViewCell)!
    if(self.firebaseUsers.count != 0){
    cell.influentBackgroudView.layer.cornerRadius=10
        let url = URL.init(string:  String().loadCorrectUrl(url:firebaseUsers[indexPath.row].image))
        cell.influentImageView.contentMode = .scaleAspectFit
        cell.influentImageView!.sd_setImage(with: url!, placeholderImage: UIImage.init(named: "placeholder"),options: [.continueInBackground,.progressiveDownload], completed: { (image, error, cacheType, imageURL) in
            if (error != nil) {
                cell.influentImageView!.image = UIImage(named: "placeholder")
            } else {
                cell.influentImageView.contentMode = .scaleAspectFill
                cell.influentImageView.image = image
            }

        })
        cell.influentImageView.layer.cornerRadius=10
        cell.influentImageView.layer.masksToBounds = true
        cell.influentNameLabel.text=" " + firebaseUsers[indexPath.row].name + " "
        cell.influentNameLabel.adjustsFontSizeToFitWidth = true
        cell.influentNameLabel.textAlignment = .center
        if(selectedCellIndex==indexPath ){
        cell.influentBackgroudView.isHidden=false
            }
        else{
            cell.influentBackgroudView.isHidden=true
            }
    cell.selectionStyle = .none
    }

    return cell 
} 
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let previousIndex=selectedCellIndex
        selectedCellIndex=indexPath
        if(previousIndex==selectedCellIndex){
            let nextVC = storyboard?.instantiateViewController(withIdentifier: "VisitedProfileViewController") as! VisitedProfileViewController
            nextVC.passedUser = firebaseUsers[selectedCellIndex!.row]
            navigationController?.pushViewController(nextVC, animated: true)
        }
        if(previousIndex==nil){
            tableView.reloadRows(at: [indexPath], with:.none)
        }
        else{
            if(previousIndex != indexPath){
                tableView.reloadRows(at: [indexPath,previousIndex!], with: .none)
            }
            else {
                tableView.reloadRows(at: [indexPath], with: .none)

                }
        }
}

thank you guys for your help!


Solution

  • As identified from the comments the issue you are facing is produced by calling reloadRows when you press a cell with conjunction of incorrect estimated row heights. So either reloading needs to be removed or estimated height corrected. The first solution is already covered in an answer provided by A. Amini.

    Since many of such anomalies are related to estimated row height it still makes sense to improve it.

    For simple static row heights you can either implement a delegate method for instance

    func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
        return indexPath.row == 0 ? 44.0 : 200.0
    }
    

    where the values are exactly the same as in your heightForRowAt method. Or if all rows have same hight you can remove this delegate method but set the heights directly in some initialization method like:

    override func viewDidLoad() {
        super.viewDidLoad()
    
        tableView.rowHeight = 200
        tableView.estimatedRowHeight = tableView.rowHeight
    }
    

    When more complicated cells are introduced and automatic cell height is used we usually use a height cache of cells. It means we need to save height of a cell when it disappears so we may use it later. All heights are saved in a dictionary of type [IndexPath: CGFloat]. A very simple implementation should look like this:

    private var cellHeightCache: [IndexPath: CGFloat] = [IndexPath: CGFloat]()
    
    func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        cellHeightCache[indexPath] = cell.bounds.height
    }
    
    func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
        return cellHeightCache[indexPath] ?? UITableView.automaticDimension
    }
    

    In some cases extra work is needed like clearing the cache when table view is reloaded.

    The reason why this is happening is because table view will not reload cells around the cells you currently see but rather check the content offset and compute which cells you were supposed to be seeing. So any call to reload may make the table view jump or animate cells due to wrong estimated row height.