Home > Back-end >  How to update the height of a cell given the intrinsic size of an UIImageView inside using constrain
How to update the height of a cell given the intrinsic size of an UIImageView inside using constrain

Time:11-07

After long hours I arrived to this conclusion: It's impossible to do it.

Now, I don't want to believe it. Here is what I want to do: Use constraints in order to make layout automatic, such that if the image of an UIImageView is replaced, the layout is automatically adjusted.

    // This works, the cell height is adjusted to the height of this image
    cell.myImageView.image = UIImage(named:"myDefaultImage") 

    // This doesn't work.
    // The result is that the size of both the image and the cell are still the same size and only the image changes
    // The result I expect is that the cell layout is recalculated and the size changes alongside the image itself
    // I repeat, right now the image content changes, it displays the new image but no re layout is happening. Intrinsic content size of the image changes, but it is not propagated to the UI.
// Putting reload cell code here will make the cell reload indefinitely, I don't want to add more state to keep track of that
    DispatchQueue.main.asyncAfter(deadline: .now()   1) {
      cell.myImage.image = UIImage(named:"downloadedFromTheInternetImage")
    }

This means:

  • I don't want to use heightForRowAtIndexPath
  • I don't want any manual intervention, for example using UIImageView.intrinsicContentSize
  • I don't want to reload the cell, as this does more than just applying the necessary layout math (I only need layout recalculation)
  • I don't want to subclass layoutSubviews, because again, this means doing it manually

The reason is, that autolayout is supposed to be automatic. Also because this works in other situations, such as UILabel, where, an increase in text will make the layout change.

If you still insist in just suggesting doing it manually, please don't, I already know that manually it can be done. In fact anything can be done with math... I don't even need the table view.

If this cannot be done, then oh well, but I feel like this should be possible. I leave the complete code below...

I'm willing to make use of: (I've tried every combination of these with no result)

  • setNeedsLayout
  • layoutIfNeeded
  • other layout related methods that are part of layout only

Complete code:

import UIKit

// The view controller with a simple table
class RootViewController2: UIViewController {
    var once = false
    let table = UITableView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = UIColor.brown
        
        table.dataSource = self
        table.backgroundColor = .green
        
        table.estimatedRowHeight = 122.0
        table.rowHeight = UITableView.automaticDimension
        
        table.register(MyCell2.self, forCellReuseIdentifier: "reusedCell")
        self.view.addSubview(table)
    }
    
    
    override func viewDidLayoutSubviews() {
        table.frame = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.view.bounds.height)
    }
}

// The table data configuration, just 100 cells of the same image, they change into a new image after 1 second of being displayed
extension RootViewController2: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 100
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = table.dequeueReusableCell(withIdentifier: "reusedCell", for: indexPath) as! MyCell2
        cell.imageView2.image = UIImage(named: "Group") // This image is short
        
        // Imagine this is a newtwork request that returns later
        DispatchQueue.main.asyncAfter(deadline: .now()   1) {
            cell.imageView2.image = UIImage(named: "keyboard") // This image is large, the cell should expand
        }
        
        return cell

    }
    
}

class MyCell2: UITableViewCell {
    
    let imageView2 = UIImageView()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        self.contentView.addSubview(imageView2)

        // Constraints such that the intrinsic size of the image is used
        // This works correctly, the problem is, if the image is changed, the layout is not
        imageView2.translatesAutoresizingMaskIntoConstraints = false
        imageView2.topAnchor.constraint(equalTo: self.contentView.topAnchor).isActive = true
        imageView2.leftAnchor.constraint(equalTo: self.contentView.leftAnchor).isActive = true
        let bottom = imageView2.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor, constant: 10)
        bottom.priority = UILayoutPriority(900)
        bottom.isActive = true
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

CodePudding user response:

I'm sure you're aware this is not the way to update content in cells:

    // Imagine this is a newtwork request that returns later
    DispatchQueue.main.asyncAfter(deadline: .now()   1) {
        cell.imageView2.image = UIImage(named: "Keyboard") // This image is large, the cell should expand
    }

But, it does demonstrate the issue.

So, the problem is that the cell layout IS changing, but that doesn't change the tableView layout.

Anytime your cell content changes - such as setting an image like your example code, changing the .text of a multi-line label, etc - you must inform the table view:

    // Imagine this is a newtwork request that returns later
    DispatchQueue.main.asyncAfter(deadline: .now()   1) {
        cell.imageView2.image = UIImage(named: "Keyboard") // This image is large, the cell should expand
        
        // add this line
        tableView.performBatchUpdates(nil, completion: nil)
    }
    
  • Related