1

I've made UI for cell in cell class below:

final class OptionTVCell: UITableViewCell {

fileprivate static let id = String(describing: OptionTVCell.self)

private var defaultTintColor: UIColor {
    let color = AppConfiguration.sharedAppConfiguration.appTextColor
    return Utility.hexStringToUIColor(hex: color ?? "ffffff")
}

// MARK: - Subviews
lazy private(set) var optionImageView: UIImageView = {
    let imageView = UIImageView()
    imageView.translatesAutoresizingMaskIntoConstraints = false
    imageView.heightAnchor.constraint(equalToConstant: 24).isActive = true
    imageView.setContentHuggingPriority(.defaultHigh, for: .horizontal)
    imageView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
    let widthConstraint = imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor)
    widthConstraint.priority = UILayoutPriority(998)
    widthConstraint.isActive = true
    imageView.contentMode = .scaleAspectFit
    imageView.tintColor = defaultTintColor
    return imageView
}()

lazy private(set) var optionNameLabel: UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    label.setContentHuggingPriority(.defaultLow, for: .horizontal)
    label.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
    label.textColor = defaultTintColor
    label.numberOfLines = 0
    let fontSize: CGFloat = Constants.shared.IPHONE ? 14 : 20
    label.font = UIFont(name: Utility.getFontName(), size: fontSize)
    return label
}()

lazy private(set) var separatorLineView: UIView = {
    let separator = UIView()
    separator.translatesAutoresizingMaskIntoConstraints = false
    let heightConstraint = separator.heightAnchor.constraint(equalToConstant: 1)
    heightConstraint.priority = UILayoutPriority(999)
    heightConstraint.isActive = true
    separator.backgroundColor = defaultTintColor.withAlphaComponent(0.5)
    return separator
}()

lazy private(set) var separatorContainerStackView: UIStackView = {
    let verticalStackView = UIStackView()
    verticalStackView.translatesAutoresizingMaskIntoConstraints = false
    verticalStackView.axis = .vertical
    verticalStackView.spacing = 10
    verticalStackView.distribution = .fillProportionally
    verticalStackView.alignment = .fill
    return verticalStackView
}()

lazy private(set) var iconContainerStackView: UIStackView = {
    let horizontalStackView = UIStackView()
    horizontalStackView.translatesAutoresizingMaskIntoConstraints = false
    horizontalStackView.axis = .horizontal
    horizontalStackView.spacing = 16
    horizontalStackView.distribution = .fill
    horizontalStackView.alignment = .center
    return horizontalStackView
}()

// MARK: - Init
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
    super.init(style: style, reuseIdentifier: reuseIdentifier)
    setupView()
}

required init?(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

// MARK: - Life Cycle
override func setHighlighted(_ highlighted: Bool, animated: Bool) {
    super.setHighlighted(highlighted, animated: animated)
    let color = highlighted
        ? Utility.hexStringToUIColor(hex: AppConfiguration.sharedAppConfiguration.primaryHoverColor ?? "ffffff")
        : defaultTintColor
    optionImageView.tintColor = color
    optionNameLabel.textColor = color
}

// MARK: - Setup
private func setupView() {
    selectionStyle = .none
    backgroundColor = .clear
    contentView.backgroundColor = .clear
    setupIconStackView()
    setupSeparatorStackView()
}

private func setupSeparatorStackView() {
    contentView.addSubview(separatorContainerStackView)
    NSLayoutConstraint.activate([
        separatorContainerStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
        separatorContainerStackView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 8),
        separatorContainerStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -8),
        separatorContainerStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16)
    ])
    separatorContainerStackView.addArrangedSubview(iconContainerStackView)
    separatorContainerStackView.addArrangedSubview(separatorLineView)
}

private func setupIconStackView() {
    iconContainerStackView.addArrangedSubview(optionImageView)
    iconContainerStackView.addArrangedSubview(optionNameLabel)
    let labelWidth = optionNameLabel.widthAnchor.constraint(greaterThanOrEqualTo: iconContainerStackView.widthAnchor, constant: -40)
    labelWidth.isActive = true
}

// MARK: - Configure

}

Below is the tableView implementation for cell:

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return UITableView.automaticDimension
}

func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
    return CGFloat(cellHeight)
}


func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    guard
        let cell = tableView.dequeueReusableCell(withIdentifier: OptionTVCell.id) as? OptionTVCell,
        indexPath.row <= optionsArray.count-1,
        let navItem = optionsArray[indexPath.row] as? OptionItem
    else {
        return UITableViewCell()
    }
    cell.separatorLineView.isHidden = navItem.hasSeparator == true ? false : true
    if let imageName = navItem.pageIcon, !imageName.isEmpty {
        if let optionImage = UIImage(named: imageName)?.withRenderingMode(.alwaysTemplate) {
            cell.optionImageView.image = optionImage
            cell.optionImageView.isHidden = false
        } else {
            let listImageStringURL = imageName.appending("?w=\(Utility.sharedUtility.getImageSizeAsPerScreenResolution(size: cell.optionImageView.frame.size.width)))&h=\(Utility.sharedUtility.getImageSizeAsPerScreenResolution(size: cell.optionImageView.frame.size.height))")

            if let imageURL = URL(string: listImageStringURL) {
                cell.optionImageView.af.setImage(
                    withURL: imageURL,
                    placeholderImage: nil,
                    filter: nil,
                    imageTransition: .crossDissolve(0.2)
                )
            }
        }
                    
    } else {
        cell.optionImageView.isHidden = true
    }
    cell.optionNameLabel.text = navItem.title?.uppercased()
    return cell
}

As you can see the separator view and icon can be hidden or shown and modifies cell height accordingly. The issue is the self sizing does not work on initial dequeue but after scrolling it fixes itself.

enter image description here

Here is the runtime error thrown from autolayout: 1.

 Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want. 
Try this: 
    (1) look at each constraint and try to figure out which you don't expect; 
    (2) find the code that added the unwanted constraint or constraints and fix it. 
(
    "<NSLayoutConstraint:0x600002525810 'fittingSizeHTarget' UIStackView:0x7fafd1e8c330.width == 0   (active)>",
    "<NSLayoutConstraint:0x600002524f50 'UISV-canvas-connection' UIStackView:0x7fafd1e8c330.leading == UIImageView:0x7fafd1e8c4c0.leading   (active)>",
    "<NSLayoutConstraint:0x600002524fa0 'UISV-canvas-connection' H:[UILabel:0x7fafd1e8aa30]-(0)-|   (active, names: '|':UIStackView:0x7fafd1e8c330 )>",
    "<NSLayoutConstraint:0x600002524ff0 'UISV-spacing' H:[UIImageView:0x7fafd1e8c4c0]-(16)-[UILabel:0x7fafd1e8aa30]   (active)>"
)
Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x600002524ff0 'UISV-spacing' H:[UIImageView:0x7fafd1e8c4c0]-(16)-[UILabel:0x7fafd1e8aa30]   (active)>
Unable to simultaneously satisfy constraints.
    Probably at least one of the constraints in the following list is one you don't want. 
    Try this: 
        (1) look at each constraint and try to figure out which you don't expect; 
        (2) find the code that added the unwanted constraint or constraints and fix it. 
(
    "<NSLayoutConstraint:0x600002500410 V:|-(8)-[UIStackView:0x7fafd1f47c90]   (active, names: '|':UITableViewCellContentView:0x7fafd1f0cf90 )>",
    "<NSLayoutConstraint:0x600002500820 UIStackView:0x7fafd1f47c90.bottom == UITableViewCellContentView:0x7fafd1f0cf90.bottom - 8   (active)>",
    "<NSLayoutConstraint:0x600002500550 UIImageView:0x7fafd1f569f0.height == 24   (active)>",
    "<NSLayoutConstraint:0x600002502120 'UISV-canvas-connection' V:[_UILayoutSpacer:0x60000399e2b0'UISV-alignment-spanner']-(0)-|   (active, names: '|':UIStackView:0x7fafd1f5a1e0 )>",
    "<NSLayoutConstraint:0x600002500370 'UISV-canvas-connection' UIStackView:0x7fafd1f5a1e0.centerY == UIImageView:0x7fafd1f569f0.centerY   (active)>",
    "<NSLayoutConstraint:0x600002500000 'UISV-canvas-connection' UIStackView:0x7fafd1f47c90.top == UIStackView:0x7fafd1f5a1e0.top   (active)>",
    "<NSLayoutConstraint:0x6000025d2260 'UISV-canvas-connection' V:[UIView:0x7fafd1f48db0]-(0)-|   (active, names: '|':UIStackView:0x7fafd1f47c90 )>",
    "<NSLayoutConstraint:0x6000025b2490 'UISV-spacing' V:[UIStackView:0x7fafd1f5a1e0]-(10)-[UIView:0x7fafd1f48db0]   (active)>",
    "<NSLayoutConstraint:0x600002502210 'UISV-spanning-boundary' _UILayoutSpacer:0x60000399e2b0'UISV-alignment-spanner'.bottom >= UIImageView:0x7fafd1f569f0.bottom   (active)>",
    "<NSLayoutConstraint:0x6000025f2cb0 'UIView-Encapsulated-Layout-Height' UITableViewCellContentView:0x7fafd1f0cf90.height == 43.5   (active)>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x600002500550 UIImageView:0x7fafd1f569f0.height == 24   (active)>

What I did:

I have added a few constraints and reduced priority as they were giving some errors when made hidden but they had fixed height/width. But errors are still thrown and self sizing still not working properly.

Amber K
  • 700
  • 1
  • 5
  • 20

1 Answers1

0

You should be able to get rid of your layout errors / warnings with a single change:

lazy private(set) var separatorContainerStackView: UIStackView = {
    let verticalStackView = UIStackView()
    verticalStackView.translatesAutoresizingMaskIntoConstraints = false
    verticalStackView.axis = .vertical
    verticalStackView.spacing = 10
    
    // use .fill NOT .fillProportionally
    verticalStackView.distribution = .fill // .fillProportionally
    
    verticalStackView.alignment = .fill
    return verticalStackView
}()

A tip: when working with UIStackView, forget about the .fillProportionally distribution setting. There are very specific layouts where that is appropriate, but you are unlikely to encounter them. And, as you see, it can cause problems when used incorrectly.

As side notes:

lazy private(set) var optionImageView: UIImageView = {
    let imageView = UIImageView()
    imageView.translatesAutoresizingMaskIntoConstraints = false
    imageView.heightAnchor.constraint(equalToConstant: 24).isActive = true

    // you can use this
    imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor).isActive = true
    
    // none of this is needed
    //imageView.setContentHuggingPriority(.defaultHigh, for: .horizontal)
    //imageView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
    //let widthConstraint = imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor)
    //widthConstraint.priority = UILayoutPriority(998)
    //widthConstraint.isActive = true
    
    imageView.contentMode = .scaleAspectFit
    imageView.tintColor = defaultTintColor
    return imageView
}()

and:

lazy private(set) var optionNameLabel: UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    
    // these two are not needed
    //label.setContentHuggingPriority(.defaultLow, for: .horizontal)
    //label.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
    
    label.textColor = defaultTintColor
    label.numberOfLines = 0
    let fontSize: CGFloat = Constants.shared.IPHONE ? 14 : 20
    label.font = UIFont(name: Utility.getFontName(), size: fontSize)
    return label
}()
DonMag
  • 69,424
  • 5
  • 50
  • 86