Dynamic UIImageView Size Within UITableView

27,248

Solution 1

In the storyboard add trailing, leading, bottom and top constraints to your UIImageView. After you load your image all you need to do is add an aspect constraint to your UIImageView. Your images cell would look something like this:

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 setCustomImage(image : UIImage) {

        let aspect = image.size.width / image.size.height

        let constraint = NSLayoutConstraint(item: customImageView, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: customImageView, attribute: NSLayoutAttribute.height, multiplier: aspect, constant: 0.0)
        constraint.priority = 999

        aspectConstraint = constraint

        customImageView.image = image
    }
}

You can check working example here DynamicTableWithImages

Solution 2

enter image description here

While loading images into the tableView each image can be with diffrent aspect ratio. With using of SDWebImage we can get the image from url as -

 testImageView.sd_setImage(with: url, placeholderImage: nil, options: [], completed: { (downloadedImage, error, cache, url) in
            print(downloadedImage?.size.width)//prints width of image
            print(downloadedImage?.size.height)//prints height of image
        })

In UITableViewCell set the constraint for imageView as top, bottom, leading, trailing, fixed height constraint with 999 priority And change height-constraint-constant according to image.

 var cellFrame = cell.frame.size
 cellFrame.height =  cellFrame.height - 15
 cellFrame.width =  cellFrame.width - 15
 cell.feedImageView.sd_setImage(with: url, placeholderImage: nil, options: [], completed: { (theImage, error, cache, url) in
            cell.feedImageHeightConstraint.constant = self.getAspectRatioAccordingToiPhones(cellImageFrame: cellFrame,downloadedImage: theImage!)

        })

Calculate cell frame & find the respective height based on that.

  func getAspectRatioAccordingToiPhones(cellImageFrame:CGSize,downloadedImage: UIImage)->CGFloat {
        let widthOffset = downloadedImage.size.width - cellImageFrame.width
        let widthOffsetPercentage = (widthOffset*100)/downloadedImage.size.width
        let heightOffset = (widthOffsetPercentage * downloadedImage.size.height)/100
        let effectiveHeight = downloadedImage.size.height - heightOffset
        return(effectiveHeight)
    }

Example on Github

Solution 3

If you want the UIImageView to tell the UITableView how tall it needs to be, then you need to configure the table view to enable automatic row calculation. You do this by setting estimatedRowHeight to a fixed positive value and setting rowHeight to UIViewAutomaticDimension. That's part one.

Now you need to configure the UIImageView to request a height based on the width which the table view requires and on the aspect ratio of the image. This will be part two.

First, set the contentMode to .AspectFit. This will cause the view to resize the appearance of the image based on whatever dimensions it takes. However, this still doesn't cause it to request the needed height with auto layout. Instead, it will request the height of the intrinsicContentSize, which may be quite different from the resized image. So, second, add a constraint to the UIImageView that is of the form width = height * multiplier, where the multiplier is based on the aspect ratio of the image itself.

Now the table view will require the correct width, the contentMode mechanism will ensure the image is resized correctly without distortion, and the aspect ration constraint will ensure that the image view requires the correct height via auto layout.

This works. The downside of it is that you will need to update the aspect ratio constraint every time you assign a new image to the image view, which could be quite often if the image view is in a cell that's getting re-used.

An alternative approach is to add only a height constraint and update it in the layoutSubviews of a view containing the image view. This has better performance but worse code modularity.

Solution 4

Swift 4.0

You can use simple code to manage the height of row according to the height of imageView in tableView cell.

   func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        if let height = self.rowHeights[indexPath.row]{
            return height
        }else{
            return defaultHeight
        }
    }

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

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let identifier = "editImageCell"
        let customCell : EdiPostImageCell = (self.imagesTableView.dequeueReusableCell(withIdentifier: identifier)) as! EdiPostImageCell
        let aspectRatio = (imageArray[indexPath.row]).size.height/(imageArray[indexPath.row]).size.width
        customCell.editlImageView.image = imageArray[indexPath.row]
        let imageHeight = self.view.frame.width*aspectRatio
        self.imagesTableView.beginUpdates()
        self.rowHeights[indexPath.row] = imageHeight
        tableView.endUpdates()
    }

Solution 5

if you are using sdwebimage library then you can manage images after downloading and cache images with the same height and width that are showing on

                guard let myImageWidth = downloadedImage?.size.width else { return }
                guard let myImageHeight = downloadedImage?.size.height else { return }
                
                let myViewWidth = cell.imageViewPost.frame.size.width
                let ratio = myViewWidth / myImageWidth
                let scaledHeight = myImageHeight * ratio
                
                self.tableView.beginUpdates()
            
                let transformer = SDImageResizingTransformer(size: CGSize(width: myViewWidth, height: scaledHeight), scaleMode: .fill)
                cell.imageViewPost.sd_setImage(with: url, placeholderImage: nil, context: [.imageTransformer: transformer])
                cell.imageViewPost.heightAnchor.constraint(equalToConstant: scaledHeight).isActive = true
                self.tableView.endUpdates()
Share:
27,248
fabdarice
Author by

fabdarice

Web Developer in Ruby on Rails. Mobile Developer with Swift.

Updated on November 01, 2021

Comments

  • fabdarice
    fabdarice over 2 years

    I want to implement a TableView with a Custom TableViewCell showing an image.

    To make this simple, I simply put a UIImageView inside a tableviewcell using autolayout (illustrated below). enter image description here

    What I want is to display the image inside a UIImageView however those images dimensions can be anything and are inconsistent (portrait, landscape, square) ...

    Therefore, I'm looking to display an image with a fixed width (the width of the device) and a dynamic height that respect the ratio of the images. I want something like this:
    enter image description here

    I unfortunately didn't manage to reach that result.

    Here's how I implemented it - (I'm using Haneke to load the image from an URL - image stored in Amazon S3):

    class TestCellVC: UITableViewCell {
    
        @IBOutlet weak var selfieImageView: UIImageView!
        @IBOutlet weak var heightConstraint: NSLayoutConstraint!
    
        func loadItem(#selfie: Selfie) {        
            let selfieImageURL:NSURL = NSURL(string: selfie.show_selfie_pic())!
    
            self.selfieImageView.hnk_setImageFromURL(selfieImageURL, placeholder: nil, format: HNKFormat<UIImage>(name: "original"), failure: {error -> Void in println(error)
                }, success: { (image) -> Void in
                    // Screen Width
                    var screen_width = UIScreen.mainScreen().bounds.width
    
                    // Ratio Width / Height
                    var ratio =  image.size.height / image.size.width
    
                    // Calculated Height for the picture
                    let newHeight = screen_width * ratio
    
                    // METHOD 1
                    self.heightConstraint.constant = newHeight
    
                    // METHOD 2
                    //self.selfieImageView.bounds = CGRectMake(0,0,screen_width,newHeight)
    
                    self.selfieImageView.image = image
                }
            )
        }
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            // Register the xib for the Custom TableViewCell
            var nib = UINib(nibName: "TestCell", bundle: nil)
            self.tableView.registerNib(nib, forCellReuseIdentifier: "TestCell")
    
            // Set the height of a cell dynamically
            self.tableView.rowHeight = UITableViewAutomaticDimension
            self.tableView.estimatedRowHeight = 500.0
    
            // Remove separator line from UITableView
            self.tableView.separatorStyle = UITableViewCellSeparatorStyle.None
    
            loadData()
        }
    
        override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
            var cell = tableView.dequeueReusableCellWithIdentifier("TestCell") as TestCellVC
    
    
            cell.loadItem(selfie: self.selfies_array[indexPath.row])
            // Remove the inset for cell separator
            cell.layoutMargins = UIEdgeInsetsZero
            cell.separatorInset = UIEdgeInsetsZero
    
            // Update Cell Constraints
            cell.setNeedsUpdateConstraints()
            cell.updateConstraintsIfNeeded()
            cell.sizeToFit()
    
            return cell
        }
    }
    

    My calculation of the dynamic Height is correct (I've printed it). I've tried both method (describe in the code) but none of them worked:

    1. Set the Height Autolayout constraint of the UIImageView
    2. Modify the frame of the UIImageView

    See Results of Method 1 and 2 here: image Image 2