Search code examples
iosswiftuitableviewuiimageviewaspect-ratio

Set height for UIImageView in UITableViewCell using height / width values


I need to render an image within a UITableViewCell.

The API returns the original height and width of the image, so I believe I should be able to calculate the ratio. However I am unsure how to lay this out using autolayout.

final class ContentArticleImage: UITableViewCell {

  var ratio: CGFloat? { // 1000 / 600 (width / height)
    didSet {
      guard let ratio = ratio else { return }
      contentImageView.heightAnchor.constraint(equalTo: widthAnchor, multiplier: 1 / ratio).isActive = true
    }
  }

  private lazy var contentImageView = configure(UIImageView(frame: .zero), using: {
    $0.translatesAutoresizingMaskIntoConstraints = false
    $0.backgroundColor = .darkGray
    $0.contentMode = .scaleAspectFit
  })

  override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
    super.init(style: style, reuseIdentifier: reuseIdentifier)
    configureUI()
  }

  required init?(coder: NSCoder) {
    return nil
  }
}
private extension ContentArticleImage {
  func configureUI() {

    addSubview(contentImageView)
    NSLayoutConstraint.activate([
      contentImageView.topAnchor.constraint(equalTo: topAnchor, constant: 12),
      contentImageView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 12),
      contentImageView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -12),
      contentImageView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -12)
    ])

  }
}

I tried something like the above, but I can an autolayout error

(
    "<NSLayoutConstraint:0x600002885b30 V:|-(12)-[UIImageView:0x7ffe3551e170]   (active, names: '|':OneHubApp.ContentArticleImage:0x7ffe3551ddc0'ContentArticleImage' )>",
    "<NSLayoutConstraint:0x600002885c70 UIImageView:0x7ffe3551e170.bottom == OneHubApp.ContentArticleImage:0x7ffe3551ddc0'ContentArticleImage'.bottom - 12   (active)>",
    "<NSLayoutConstraint:0x600002885ea0 UIImageView:0x7ffe3551e170.height == 0.548298*OneHubApp.ContentArticleImage:0x7ffe3551ddc0'ContentArticleImage'.width   (active)>",
    "<NSLayoutConstraint:0x6000028860d0 'UIView-Encapsulated-Layout-Height' OneHubApp.ContentArticleImage:0x7ffe3551ddc0'ContentArticleImage'.height == 251   (active)>",
    "<NSLayoutConstraint:0x600002886080 'UIView-Encapsulated-Layout-Width' OneHubApp.ContentArticleImage:0x7ffe3551ddc0'ContentArticleImage'.width == 414   (active)>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x600002885c70 UIImageView:0x7ffe3551e170.bottom == OneHubApp.ContentArticleImage:0x7ffe3551ddc0'ContentArticleImage'.bottom - 12   (active)>

Using the original height / width how should I calculate the height of an image in my cell?

EDIT My cell is rendered using the following method:

  override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "CONTENT_CELL", for: indexPath) as! ContentArticleImage
    cell.ratio = asset.width / asset.height
    cell.selectionStyle = .none
    return cell
  }

Solution

  • What you need to do is deactivate your height constraint each time the cell is reused. Also, you need to set the height constraint equal to a multiple of the contentImageView width constraint, not the cell's width constraint. So your code should look something like this:

    final class ContentArticleImage: UITableViewCell {
    
        var heightConstraint: NSLayoutConstraint?
    
        var ratio: CGFloat? { // 1000 / 600 (width / height)
            didSet {
                guard let ratio = ratio else { return }
                heightConstraint?.isActive = false
                heightConstraint = contentImageView.heightAnchor.constraint(equalTo: contentImageView.widthAnchor, multiplier: 1 / ratio)
                heightConstraint?.isActive = true
                heightConstraint?.priority = .defaultHigh
            }
        }
    
        lazy var contentImageView: UIImageView = {
            let imageView = UIImageView()
            imageView.translatesAutoresizingMaskIntoConstraints = false
            imageView.backgroundColor = .darkGray
            imageView.contentMode = .scaleAspectFit
            return imageView
        }()
    
        override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
            super.init(style: style, reuseIdentifier: reuseIdentifier)
            configureUI()
        }
    
        required init?(coder: NSCoder) {
            super.init(coder: coder)
            configureUI()
        }
    }
    

    Note that I'm setting the priority to .defaultHight just to silence a layout warning in the console.