Search code examples
swiftuitableviewuiimageviewautolayout

Dynamic UITableView with images


There are similar questions, but non of the answers worked for me. In a dynamic table I want to display images that have different heigh. Each cell has a UIImageView with contentMode = .scaleAspectFit so the image nicely takes the width of the table and takes the height as much as needed.

Cell has 4 constraints: enter image description here

Table view controller:

class TableTableViewController: UITableViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.estimatedRowHeight = 100
        tableView.rowHeight = UITableViewAutomaticDimension
    }

    override func numberOfSections(in tableView: UITableView) -> Int {

        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

        return 2
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: ImageTableViewCell.self), for: indexPath) as! ImageTableViewCell

        let image = indexPath.row == 0 ? UIImage(named: "1.jpg")! : UIImage(named: "2.jpg")!
        cell.customImageView.image = image

        return cell
    }
}

Result:

enter image description here

As you can see that the height of the cell is incorrect (red background of the image view on top and bottom of the image view). I believe this happens because intrinsicContentSize of the image view is equal to the image size and thats why the height of the cell is calculated incorrectly (content mode is not taken into account). I tried calculating height of the image and adding height constraint for the image view:

cell.heightConstraint.constant = cell.frame.width * image.size.height /  image.size.width

but it breaks cell's content view constraints.

The project can be downloaded here>>


Solution

  • In your ImageTableViewCell.swift

    import UIKit
    
    class ImageTableViewCell: UITableViewCell {
    
        @IBOutlet weak var customImageView: UIImageView!
    
        internal var aspectConstraint : NSLayoutConstraint? {
            didSet {
                if oldValue != nil {
                    customImageView.removeConstraint(oldValue!)
                }
                if aspectConstraint != nil {
                    customImageView.addConstraint(aspectConstraint!)
                }
            }
        }
    
        override func prepareForReuse() {
            super.prepareForReuse()
            aspectConstraint = nil
        }
    
        func setPostedImage(image : UIImage) {
    
            let aspect = image.size.width / image.size.height
    
            aspectConstraint = NSLayoutConstraint(item: customImageView, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: customImageView, attribute: NSLayoutAttribute.height, multiplier: aspect, constant: 0.0)
    
            customImageView.image = image
        }
    }
    

    And into your TableTableViewController.swift your cellForRowAt method will be:

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "ImageTableViewCell", for: indexPath) as! ImageTableViewCell
    
        let image = imageArr[indexPath.row]
        cell.setPostedImage(image: image!)
        return cell
    }
    

    And declare your imageArr this way:

    let imageArr = [UIImage(named: "1.jpg"), UIImage(named: "2.jpg")]
    

    And your compete code will be:

    import UIKit
    
    class TableTableViewController: UITableViewController {
    
        let imageArr = [UIImage(named: "1.jpg"), UIImage(named: "2.jpg")]
        override func viewDidLoad() {
            super.viewDidLoad()
    
            tableView.estimatedRowHeight = 100
            tableView.rowHeight = UITableViewAutomaticDimension
        }
    
        override func numberOfSections(in tableView: UITableView) -> Int {
    
            return 1
        }
    
        override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    
            return 2
        }
    
        override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: "ImageTableViewCell", for: indexPath) as! ImageTableViewCell
    
            let image = imageArr[indexPath.row]
            cell.setPostedImage(image: image!)
            return cell
        }
    }
    

    And THIS will be your result.

    EDIT:

    To fix constraint issue set aspectConstraint priority to 999 and aspectConstraint will be:

    internal var aspectConstraint : NSLayoutConstraint? {
        didSet {
            if oldValue != nil {
                customImageView.removeConstraint(oldValue!)
            }
            if aspectConstraint != nil {
                aspectConstraint?.priority = 999  //add this
                customImageView.addConstraint(aspectConstraint!)
            }
        }
    }