首页 > 解决方案 > Swift:与现代 CollectionView 中的自定义 UICollectionViewListCell 的约束冲突

问题描述

我正在使用带有 iOS14 的“现代” UICollectionView 来实现列表配置。我的实现是成功的,即单元格以动态高度大小呈现,但具有冲突的约束。

2020-12-24 19:23:50.889822+0800 circulars[18490:2636547] [LayoutConstraints] 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:0x60000102b250 V:|-(16)-[UIStackView:0x7f9b6ad4d120]   (active, names: '|':circulars.CircularCollectionCell:0x7f9b6ad4c5f0 )>",
    "<NSLayoutConstraint:0x60000102b4d0 UIStackView:0x7f9b6ad4d120.bottom == circulars.CircularCollectionCell:0x7f9b6ad4c5f0.bottom - 16   (active)>",
    "<NSLayoutConstraint:0x60000102b520 'UISV-canvas-connection' UIStackView:0x7f9b6ad4d120.top == UILabel:0x7f9b6ad49320.top   (active)>",
    "<NSLayoutConstraint:0x60000102b570 'UISV-canvas-connection' V:[UILabel:0x7f9b6ad4ceb0]-(0)-|   (active, names: '|':UIStackView:0x7f9b6ad4d120 )>",
    "<NSLayoutConstraint:0x60000102b5c0 'UISV-spacing' V:[UILabel:0x7f9b6ad49320]-(8)-[UILabel:0x7f9b6ad4c930]   (active)>",
    "<NSLayoutConstraint:0x60000102b610 'UISV-spacing' V:[UILabel:0x7f9b6ad4c930]-(8)-[UILabel:0x7f9b6ad4ceb0]   (active)>",
    "<NSLayoutConstraint:0x60000101dd60 'UIView-Encapsulated-Layout-Height' circulars.CircularCollectionCell:0x7f9b6ad4c5f0.height == 44   (active)>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x60000102b610 'UISV-spacing' V:[UILabel:0x7f9b6ad4c930]-(8)-[UILabel:0x7f9b6ad4ceb0]   (active)>

Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKitCore/UIView.h> may also be helpful.

代码:

class TestController: UIViewController {
    
    lazy var collectionView: UICollectionView = {
        let config = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
        let layout = UICollectionViewCompositionalLayout.list(using: config)
        let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
        cv.translatesAutoresizingMaskIntoConstraints = false
        cv.backgroundColor = .systemBackground
        cv.delegate = self
        return cv
    }()
    
    var dataSource: UICollectionViewDiffableDataSource<Section, Circular>?
    
    override func viewDidLoad() {
        super.viewDidLoad()
      
        setupViews()
        setupData()
    }
    
    func setupViews() {
        view.backgroundColor = .systemBackground
        navigationItem.title = "CV"
        
        view.addSubview(collectionView)
        collectionView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
        collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
        setupCell()
    }
    
    func setupCell() {
        //No constraint conflict for this cell section
        let registration = UICollectionView.CellRegistration<UICollectionViewListCell, Circular> { (cell, indexPath, circular) in
            var content = cell.defaultContentConfiguration()
            content.text = circular.title
            cell.contentConfiguration = content
        }
        
        //Constraint conflict for this cell/section.
        let cirRegistration = UICollectionView.CellRegistration<CircularCollectionCell, Circular> { (cell, indexPath, circular) in
            cell.circular = circular
        }
        
        dataSource = UICollectionViewDiffableDataSource<Section, Circular>(collectionView: collectionView) { (collectionView, indexPath, circular) -> UICollectionViewCell? in
            
            if indexPath.section == 0 {
                return collectionView.dequeueConfiguredReusableCell(using: registration, for: indexPath, item: circular)

            } else {
                return collectionView.dequeueConfiguredReusableCell(using: cirRegistration, for: indexPath, item: circular)
            }
            
        }
    }
    
    func setupData() {
        let circulars = [
            //Setup circulars
        ]
        
        populate(circulars: circulars)
    }
    
    func populate(circulars: [Circular]) {
        var snapshot = NSDiffableDataSourceSnapshot<Section, Circular>()
        snapshot.appendSections([.main, .second])
        snapshot.appendItems(circulars, toSection: .main)
        snapshot.appendItems(Array(circulars.prefix(2)), toSection: .second)
        dataSource?.apply(snapshot)
    }    
}

extension TestController {
    enum Section {
        case main, second
    }
}

//At custom CircularCollectionCell
class CircularCollectionCell: UICollectionViewListCell {
        
    var companyLabel: UILabel = {
        let l = UILabel()
        l.translatesAutoresizingMaskIntoConstraints = false
        l.numberOfLines = 0
        l.font = UIFont.systemFont(ofSize: 15, weight: .heavy)
        return l
    }()
    
    var titleLabel: UILabel = {
        let l = UILabel()
        l.translatesAutoresizingMaskIntoConstraints = false
        l.numberOfLines = 0
        l.font = UIFont.systemFont(ofSize: 17, weight: .medium)
        return l
    }()
    
    var dateLabel: UILabel = {
        let l = UILabel()
        l.translatesAutoresizingMaskIntoConstraints = false
        l.numberOfLines = 0
        l.font = UIFont.systemFont(ofSize: 14, weight: .light)
        l.textColor = .systemGray
        return l
    }()
    
    var circular: Circular? {
        didSet {
            if let circular = circular {
               //Setup UI
               dateLabel.text = circular.date
            }
        }
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupViews()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func setupViews() {
        
        let stackView = UIStackView(arrangedSubviews: [
            companyLabel,
            titleLabel,
            dateLabel
        ])
        stackView.translatesAutoresizingMaskIntoConstraints = false
        stackView.axis = .vertical
        stackView.spacing = 8
        
        addSubview(stackView)
        stackView.topAnchor.constraint(equalTo: topAnchor, constant: 16).isActive = true
        stackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16).isActive = true
        stackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16).isActive = true
        stackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -16).isActive = true
    }

}

标签: swift

解决方案


避免冲突约束问题的一种方法是给出一个约束,它负责高度,即topAnchor更高的优先级:

let topAnchorConstraint = stackView.topAnchor.constraint(equalTo: topAnchor, constant: 16)
topAnchorConstraint.priority = .defaultHigh // Also used sometimes: UILayoutPriority(999)
topAnchorConstraint.isActive = true

因为这个约束现在具有更高的优先级,UIView-Encapsulated-Layout-Height被否决并且冲突是可以解决的。


推荐阅读