user832
user832

Reputation: 846

Table cell frame not updated after Image downloaded asynchronously

I have a UITableViewCell with UITableViewAutomaticDimension which consists of Image and title. After I get the data from API, I reload tableView, but only title in UILabel appears and not the image, as in below image After reload and before scrolling

After i scroll the tableView, the images appear and are adjusted according to there size. Like Below After Scrolling

I want this to happen before scrolling.

Things I have tried already

  1. Using a placeholder image, but it results to downloaded image to take placeholder image size and after scrolling the cell size is updated correctly.
  2. Using layout if needed or setNeedLayout
  3. Waiting for the file to download and then return cell after calculating the size, but this result in lagging the tableview while scrolling

My cellForRowAt Code

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    guard let cell = tableView.dequeueReusableCell(withIdentifier: Constants.custom.rawValue) as? CustomCell else {return UITableViewCell()}
    cell.update(model: arrImages[indexPath.row])
    return cell
}

My Cell Class Logic

Update Method

func update(model: ImageModel) {
    self.presentModel = model
}

UI Changes in cell

    var presentModel: ImageModel? {
    didSet{
        if let url = presentModel?.imgURL {
            imgView.kf.indicatorType = .activity
            imgView.kf.setImage(with: url)
        }
        lblTitle.text = presentModel?.title
    }
}

Addition info I am using KingFisher third party to download images.

Upvotes: 1

Views: 1219

Answers (2)

VIkas
VIkas

Reputation: 201

Do following

  • Add a stack view inside you table cell
  • Add add height constraint to the stack view
  • Create IBOutlet of this height constraint
  • load image with the URL using Kingfisher SDK with completion block.
  • Inside completion block calculate the height you want to set to your image view
  • Create UIImageView with frame using the calculated height and width.
  • Add imageView as arrangedsubView in your stack view
  • set the constant value of constrain to your calculated height
  • add these two lines after adding imageView to stack view
    stackView.setNeedsLayout()
    stackView.layoutIfNeeded()
    

sample code

let imageSize = image.size
let width = UIScreen.main.bounds.width - 20
let height = (width * imageSize.height) /  imageSize.width
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: width, height: height))
imageView.image = image
heightConstraint.constant = height
stackView.addArrangedSubview(imageView)
stackView.setNeedsLayout()
stackView.layoutIfNeeded()

Upvotes: 0

Natarajan
Natarajan

Reputation: 3271

You should reload your table view once image is loaded from the url.

You might have multiple cells with image on each cell. So you should call the tableView's reload method once your image is downloaded from the url asynchronously.

You said that images are appears properly when you scroll down the cells off and on screens, because the tableView's func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) will be called when cell is about to visible on screen.

So you should call tableView.reloadData() or tableView.reloadRows(at: [imageLoadedCellIndexPath], with: .automatic) method to trigger the tableview's delegate and data source methods.

As you are using Kingfisher extensions to download images from url, you can try using the below method to know the download status.

imageView.kf.setImage(with: url, completionHandler: { 
    (image, error, cacheType, imageUrl) in

})

instead of imageView.kf.setImage(with: url).

Example:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    guard let cell = tableView.dequeueReusableCell(withIdentifier: Constants.custom.rawValue) as? CustomCell else {return UITableViewCell()}

    cell.update(model: arrImages[indexPath.row])

    let imageModel = arrImages[indexPath.row]

    if let url = imageModel?.imgURL {

//Check if image is already downloaded
        ImageCache.default.retrieveImage(forKey: url.absoluteString, options: nil) {
            image, cacheType in

//Image is already downloaded
            if let image = image {

                //cell.imgView.image = image
                imgView.kf.setImage(with: url) //try this line instead of above one

            } else { //Download
                imgView.kf.indicatorType = .activity
                cell.imgView.kf.setImage(with: url, completionHandler: {
                    (image, error, cacheType, imageUrl) in

                    if image != nil{

                        tableView.reloadRows(at: [indexPath], with: .automatic)

                        //OR

                        tableView.reloadData()
                    }
                })
            }
        }

    }

    return cell
}

And Comment image download code in cell as like below,

var presentModel: ImageModel? {
    didSet{
//            if let url = presentModel?.imgURL {
//                imgView.kf.indicatorType = .activity
//                imgView.kf.setImage(with: url)
//            }
              lblTitle.text = presentModel?.title
        }
    }

Upvotes: 0

Related Questions