Home > OS >  Dynamic UIImageView inside UITableViewCell based on aspect ratio of image coming from URL
Dynamic UIImageView inside UITableViewCell based on aspect ratio of image coming from URL

Time:11-02

Im implementing a UITableViewCell for social media post which includes username, userImage, text, media like image post posted by the user and like, comment buttons etc. Here the media will be optional and if there is any image posted, I will unhide the UIView contains imageView and adjust the UIImageView height based on aspect ratio of the image coming from the API response.

Here is my code for the UITableViewCell class:

class PostsTableViewCell: UITableViewCell {

    @IBOutlet weak var ivProfilePic: UIImageView!
    @IBOutlet weak var ivPost: UIImageView!
    
    @IBOutlet weak var lblName: UILabel!
    @IBOutlet weak var lblPostContent: UILabel!
    
    @IBOutlet weak var viewPost: UIView!
    
    @IBOutlet weak var heighIvPost: NSLayoutConstraint!
    
    var postImage: UIImage? {
        didSet {
            if let image = postImage {
                configureCellWhenPostImageIsAvailable(image: image)
            }
            viewPost.isHidden = postImage == nil
            layoutIfNeeded()
        }
    }
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
 
        viewPost.isHidden = true
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }
    
    override func prepareForReuse() {
        super.prepareForReuse()
 
        viewPost.isHidden = true

        heighIvPost.constant = 162 // default height of UIImageView
        ivPost.image = nil
    }
    
    // To calculate aspect ratio & set heightIvPost constraint value
    func configureCellWhenPostImageIsAvailable(image: UIImage) {
        let hRatio = image.size.height / image.size.width
        let newImageHeight = hRatio * viewPost.bounds.width
        heighIvPost.constant = newImageHeight
        ivPost.image = image
        ivPost.layoutIfNeeded()
    }
    
}

This is my cellForRowAt function in the main UIViewController:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "PostsTableViewCell", for: indexPath) as! PostsTableViewCell
    
    let data = posts[indexPath.row]
    
    if let userImage = data.memberProfilePic {
        cell.ivProfilePic.kf.setImage(with: userImage)
    }
    
    cell.lblName.text = data.memberName
    
    if let postText = data.postContent {
        cell.lblPostContent.isHidden = false
        cell.lblPostContent.text = postText
    }
    
    if let postImage = data.postImage { // data.postImage contains URL for image and if not nil then unhide viewPost and set Image
        cell.viewPost.isHidden = false
        cell.ivPost.kf.setImage(with: postImage)
        if let image = cell.ivPost.image {
            cell.configureCellWhenPostImageIsAvailable(image: image)
            cell.layoutIfNeeded()
        }
    }
    
    return cell
}

Here is my data model just in case:

class PostEntity: NSObject {

    var postContent: String?
    var postImage: URL?
    var memberName: String?
    var memberProfilePic: URL?
    
    override init() {
        
    }

    init(jsonData: JSON){
        postContent = jsonData["postContent"].stringValue
        postImage = jsonData["postImages"].url
        memberName = jsonData["member_name"].stringValue
        memberProfilePic = jsonData["member_profile"].url
    }
}

When I run this code, my requirement is if there is any image in post ie.. data.postImage != nil, it should display image with correct aspect ratio however what I get is:

  1. When UITableView is loaded, the cells that are loaded show images with correct aspect ratio.
  2. When I scroll down, the UIImageView will not show images in correct aspect ratio but default one.
  3. When I scroll back up, I think because of prepareForReuse, it again displays images in correct aspect ratio.

Only problem I face is when o scroll down and new cells are created, it won't show correct aspect ratio if data.postImage != nil.

Here is the video link for further clarification: https://youtube.com/shorts/vcRb4u_KAVM?feature=share

In the video above you can see, at start all images have perfect aspect ratio but when I scroll down and reach robot and car image, they are of default size i.e. 162, but when I scroll down and scroll back up to them, they get resized to desired results.

I want to remove that behaviour and have correct aspect ratio based on image size.

CodePudding user response:

The problem is...

When a table view calls cellForRowAt, the row height gets set -- usually, be constraints on the content of the cell. If you change the height after the cell has been displayed, it is up to you to inform the table view that it needs to re-calculate the height of the row.

So, you can add a closure to your cell class like this:

class PostsTableViewCell: UITableViewCell {

    // closure
    var layoutChange: ((PostsTableViewCell) -> ())?
    
    // the rest of your cell code...
    
    func configureCellWhenPostImageIsAvailable(image: UIImage) {
        let hRatio = image.size.height / image.size.width
        let newImageHeight = hRatio * viewPost.bounds.width
        heighIvPost.constant = newImageHeight
        ivPost.image = image
        // not needed
        //ivPost.layoutIfNeeded()
        
        // use the closure to inform the table view the row height has changed
        self.layoutChange?(self)
    }
}

then, in your controller's cellForRowAt:

    cell.layoutChange = { [weak self] theCell in
        guard let self = self,
              let cellIndexPath = tableView.indexPath(for: theCell)
        else { return }

        // you probably want to update something in your data
        //  maybe:
        var data = self.posts[cellIndexPath.row]
        data.profilePicDownloaded = true
        self.posts[cellIndexPath.row] = data

        // tell the table view to re-cacluclate the row heights
        self.tableView.performBatchUpdates(nil, completion: nil)
    }
    
    return cell
  • Related