Swift实现JTACDayCell布局时约束激活失败问题排查
我尝试用Swift代码实现指定UI布局(对应Xib视图层级结构),使用JTACDayCell(JTAppleCalendar库的UICollectionViewCell子类)编写了如下代码:
class DateTableCell: JTACDayCell { static let reuseID = "dateCell" lazy var parentView: UIView = { let view = UIView() view.translatesAutoresizingMaskIntoConstraints = false return view }() lazy var selectedView: UIView = { let view = UIView() view.translatesAutoresizingMaskIntoConstraints = false return view }() lazy var dateLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false return label }() lazy var eventView: UIView = { let view = UIView() view.translatesAutoresizingMaskIntoConstraints = false return view }() lazy var separatorView: UIView = { let view = UIView() view.translatesAutoresizingMaskIntoConstraints = false view.backgroundColor = .lightGray return view }() override init(frame: CGRect) { super.init(frame: frame) setUpAutoLayout() parentView.addSubview(selectedView) parentView.addSubview(dateLabel) parentView.addSubview(eventView) addSubview(parentView) addSubview(separatorView) } required init?(coder: NSCoder) { fatalError("init?(coder: NSCoder) has not been implemented") } private func setUpAutoLayout() { NSLayoutConstraint.activate([ parentView.leftAnchor.constraint(equalTo: leftAnchor), parentView.rightAnchor.constraint(equalTo: rightAnchor), parentView.topAnchor.constraint(equalTo: topAnchor), parentView.bottomAnchor.constraint(equalTo: separatorView.topAnchor), selectedView.centerXAnchor.constraint(equalTo: parentView.centerXAnchor), selectedView.centerYAnchor.constraint(equalTo: parentView.centerYAnchor, constant: -2.5), selectedView.widthAnchor.constraint(equalToConstant: 35), selectedView.heightAnchor.constraint(equalToConstant: 35), dateLabel.centerXAnchor.constraint(equalTo: parentView.centerXAnchor), dateLabel.centerYAnchor.constraint(equalTo: parentView.centerYAnchor, constant: -2.5), eventView.centerXAnchor.constraint(equalTo: parentView.centerXAnchor), eventView.widthAnchor.constraint(equalToConstant: 6), eventView.heightAnchor.constraint(equalToConstant: 6), eventView.bottomAnchor.constraint(equalTo: parentView.topAnchor, constant: 5), separatorView.leftAnchor.constraint(equalTo: leftAnchor), separatorView.rightAnchor.constraint(equalTo: rightAnchor), separatorView.heightAnchor.constraint(equalToConstant: 0.5), separatorView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 1) ]) } }
运行后抛出如下异常:
*** 应用因未捕获异常'NSGenericException'终止,原因:无法激活锚点<NSLayoutXAxisAnchor:0x6000034619c0 "UIView:0x7fb1a8a20ce0.left">和<NSLayoutXAxisAnchor:0x600003460800 "AppName.DateTableCell:0x7fb1a8a28470.left">的约束,因为它们没有共同祖先。约束或其锚点是否引用了不同视图层级中的元素?这是非法的。
问题根源
你在init(frame:)方法中先调用了setUpAutoLayout()激活约束,再添加子视图到视图层级。此时parentView、separatorView等视图还未成为cell的子视图,约束关联的两个锚点属于不同的视图树,没有共同祖先,因此触发布局异常。
修正代码
调整执行顺序,先完成所有子视图的层级添加,再激活约束:
class DateTableCell: JTACDayCell { static let reuseID = "dateCell" lazy var parentView: UIView = { let view = UIView() view.translatesAutoresizingMaskIntoConstraints = false return view }() lazy var selectedView: UIView = { let view = UIView() view.translatesAutoresizingMaskIntoConstraints = false return view }() lazy var dateLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false return label }() lazy var eventView: UIView = { let view = UIView() view.translatesAutoresizingMaskIntoConstraints = false return view }() lazy var separatorView: UIView = { let view = UIView() view.translatesAutoresizingMaskIntoConstraints = false view.backgroundColor = .lightGray return view }() override init(frame: CGRect) { super.init(frame: frame) // 先添加子视图到正确层级 parentView.addSubview(selectedView) parentView.addSubview(dateLabel) parentView.addSubview(eventView) addSubview(parentView) addSubview(separatorView) // 再激活约束 setUpAutoLayout() } required init?(coder: NSCoder) { fatalError("init?(coder: NSCoder) has not been implemented") } private func setUpAutoLayout() { NSLayoutConstraint.activate([ parentView.leftAnchor.constraint(equalTo: leftAnchor), parentView.rightAnchor.constraint(equalTo: rightAnchor), parentView.topAnchor.constraint(equalTo: topAnchor), parentView.bottomAnchor.constraint(equalTo: separatorView.topAnchor), selectedView.centerXAnchor.constraint(equalTo: parentView.centerXAnchor), selectedView.centerYAnchor.constraint(equalTo: parentView.centerYAnchor, constant: -2.5), selectedView.widthAnchor.constraint(equalToConstant: 35), selectedView.heightAnchor.constraint(equalToConstant: 35), dateLabel.centerXAnchor.constraint(equalTo: parentView.centerXAnchor), dateLabel.centerYAnchor.constraint(equalTo: parentView.centerYAnchor, constant: -2.5), eventView.centerXAnchor.constraint(equalTo: parentView.centerXAnchor), eventView.widthAnchor.constraint(equalToConstant: 6), eventView.heightAnchor.constraint(equalToConstant: 6), eventView.bottomAnchor.constraint(equalTo: parentView.topAnchor, constant: 5), separatorView.leftAnchor.constraint(equalTo: leftAnchor), separatorView.rightAnchor.constraint(equalTo: rightAnchor), separatorView.heightAnchor.constraint(equalToConstant: 0.5), separatorView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 1) ]) } }
额外提示:eventView的约束bottomAnchor.constraint(equalTo: parentView.topAnchor, constant: 5)会让它显示在cell外部,如果这不是预期效果,建议调整为基于parentView内部的布局(比如eventView.topAnchor.constraint(equalTo: dateLabel.bottomAnchor, constant: 4))。
内容的提问来源于stack exchange,提问作者Bhanuteja

