Stackview показывает скрытые ограничения, не позволяющие изменять размер самостоятельно

#ios #uitableview #autolayout #uistackview

#iOS #uitableview #автоматическое описание #uistackview

Вопрос:

Я создал пользовательский интерфейс для ячейки в классе ячеек ниже:

 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

}
 

Ниже приведена реализация TableView для 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)))amp;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
}
 

Как вы можете видеть, разделитель и значок могут быть скрыты или показаны и соответствующим образом изменяют высоту ячейки. Проблема в том, что саморазмер не работает при первоначальном удалении из очереди, но после прокрутки он исправляется сам.

введите описание изображения здесь

Вот ошибка времени выполнения, вызванная из автозапуска: 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)>
 

Что я сделал:

Я добавил несколько ограничений и уменьшил приоритет, поскольку они выдавали некоторые ошибки, когда были скрыты, но имели фиксированную высоту / ширину. Но ошибки по-прежнему возникают, и самонастройка по-прежнему не работает должным образом.

Ответ №1:

Вы должны быть в состоянии избавиться от ошибок / предупреждений вашего макета одним изменением:

 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
}()
 

Совет: при работе с UIStackView забудьте о .fillProportionally настройке распространения. Существуют очень специфические макеты, где это уместно, но вы вряд ли столкнетесь с ними. И, как вы видите, это может вызвать проблемы при неправильном использовании.

В качестве дополнительных примечаний:

 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
}()
 

и:

 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
}()