4

I have a table view and table view cells with an image view on them. I want them to have a fixed width but with a dynamic height (depending on the image coming in from the server).

I am using SDWebImage to download and set the image, but the table view cells are turning out very weird.

I didn't forget to:

postTableView.estimatedRowHeight = UITableViewAutomaticDimension
postTableView.rowHeight = UITableViewAutomaticDimension

cellForRow method:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "postCell", for: indexPath) as! TableViewCell
    let post = postArray[indexPath.row]
    cell.setupCell(with: post)
    return cell
}

Table view cell class:

class TableViewCell: UITableViewCell {

    @IBOutlet weak var postTitle: UILabel!
    @IBOutlet weak var postSource: UILabel!
    @IBOutlet weak var postChart: UIImageView!

    internal var aspectConstraint: NSLayoutConstraint? {
        didSet {
            if oldValue != nil {
                postChart.removeConstraint(oldValue!)
            }
            if aspectConstraint != nil {
                postChart.addConstraint(aspectConstraint!)
            }
        }
    }

    override func awakeFromNib() {
        super.awakeFromNib()
        selectionStyle = UITableViewCellSelectionStyle.none
    }

    override func prepareForReuse() {
        super.prepareForReuse()
        postChart.image = nil
        aspectConstraint = nil
    }

    func setupCell(with post: Post) {
        postTitle.text = post.title
        postSource.text = post.source
        let tempImageView = UIImageView()
        tempImageView.sd_setImage(with: URL(string: post.chartURL!), placeholderImage: UIImage(named: "placeholder.png")) { (image, error, cache, url) in
            if let image = image {
                self.setCustomImage(image: image)
            }
        }
    }

    func setCustomImage(image: UIImage) {
        let aspect = image.size.width / image.size.height
        let constraint = NSLayoutConstraint(item: postChart, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: postChart, attribute: NSLayoutAttribute.height, multiplier: aspect, constant: 0.0)
        constraint.priority = UILayoutPriority(rawValue: 999)
        aspectConstraint = constraint
        postChart.image = image
        setNeedsLayout()
    }
}
Cesare
  • 9,139
  • 16
  • 78
  • 130

1 Answers1

6

You just need to tell the whole tableView to refresh its view. Use this code in the view controller holding the tableView:

func refreshTableView() {
    self.tableView.beginUpdates()
    self.tableView.setNeedsDisplay()
    self.tableView.endUpdates()
}

Using a delegate pattern tell the viewController to refresh the tableView, so something like:

func setCustomImage(image: UIImage) {
    let aspect = image.size.width / image.size.height
    let constraint = NSLayoutConstraint(item: postChart, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: postChart, attribute: NSLayoutAttribute.height, multiplier: aspect, constant: 0.0)
    constraint.priority = UILayoutPriority(rawValue: 999)
    aspectConstraint = constraint
    postChart.image = image

    // call this to refresh table
    delegate.refreshTableView()
}

Post class:

class Post {
    var title : String?
    var source : String?
    var chartURL : String?
    var postChart: UIImage?
    var category : String?
    var rank : CGFloat?
    var imageSize: CGSize?

    init(data: NSDictionary) {
        title = data["title"] as? String
        source = data["source"]as? String
        chartURL = data["chartURL"] as? String
        postChart = data["postChart"] as? UIImage
        category = data["category"] as? String
        rank = data["rank"] as? CGFloat
    }
}
Cesare
  • 9,139
  • 16
  • 78
  • 130
Milan Nosáľ
  • 19,169
  • 4
  • 55
  • 90
  • thanks, this works great but it lags so much (especially when the user scrolls to go to the top). Any fix? – Cesare Jan 22 '18 at 16:54
  • @Cesare did it lag before? if yes, use time profiler to detect which code is time inefficient – Milan Nosáľ Jan 22 '18 at 16:55
  • @Cesare if not, then I suggest you add an `imageSize` property to the `post` model, and once an image is downloaded, store it there. and modify the constraint handling so that if you already loaded the image (and therefore you have `imageSize`), do not set up the constraint in the callback, but directly in `setupCell`, and if that is the case, then in the callback set just the image, and not the constraint (also, in that case do not explicitly refresh the table) – Milan Nosáľ Jan 22 '18 at 16:59
  • it didn't lag before unfortunately. – Cesare Jan 22 '18 at 17:00
  • so the `imageSize` is a `CGSize`? also, not sure how you would store an `imageSize` property since the posts array are controlled by the view controller. – Cesare Jan 22 '18 at 17:03
  • @Cesare the point is that if you already downloaded the image, you can "cache" its size so that you can setup cell's layout directly in `cellForRowAt` instead of postponing it till the callback is called.. just be careful to handle both cases properly – Milan Nosáľ Jan 22 '18 at 17:06
  • thanks so much. how do I cache the image size? I'm guessing I have to this work in the view controller. and how do I check if I already downloaded the image? – Cesare Jan 22 '18 at 17:08
  • @Cesare is `Post` struct or class? – Milan Nosáľ Jan 22 '18 at 17:09
  • it's a class, I've updated my question. I think I got it except for checking whether I have the image already or now – Cesare Jan 22 '18 at 17:11
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/163667/discussion-between-milan-nosa-and-cesare). – Milan Nosáľ Jan 22 '18 at 17:12
  • @Cesare I thought something wasn't right, but needed to go for a football match, so only now I can add it - check the discussion – Milan Nosáľ Jan 22 '18 at 19:53
  • @MilanNosáľ hey sir,may I know the `delegate.refreshTableView()` the `delegate` variable is from where?? – ken Jan 24 '18 at 01:19
  • @ken the viewController with the `tableView` implements that delegate, and sets it to each cell in `cellForRowAt`.. each cell should have the reference (`weak`) to the delegate so that it can notify it to refresh the table. I am planning to write a more complete example during this weekend, cause it seems that this is a commontask – Milan Nosáľ Jan 24 '18 at 06:51
  • @MilanNosáľ looking forward bro – ken Jan 24 '18 at 06:58