在制作具有自动行高的基本表格视图时,我遇到了一个奇怪的问题 运行

I have Run into a weird problem while making a basic tableview with automatic rowheight

我的手机看起来像这样:

class listofJobsTVC: UITableViewCell {
    
    let jobName = UILabel()
    let jobNumber = UILabel()
    let jobDescription = UILabel()
    let jobStartDate = UILabel()
    let jobEndDate = UILabel()
    let jobTaskCount = UILabel()
    
    let expandBtn = UIButton()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        for view in contentView.subviews {
            view.removeFromSuperview()
        }
        SetupCell()
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
        }
        
        func SetupCell() {
            let jobNameLeft = UILabel()
            let jobNumberLeft = UILabel()
            let jobDescriptionLeft = UILabel()
            let jobStartDateLeft = UILabel()
            let jobEndDateLeft = UILabel()
            let jobTaskCountLeft = UILabel()
            
            let container = ShadowRoundedView()
            
            addSubview(container)
            
            addConstraintsWithFormat("H:|-15-[v0]-15-|", views: container)
            addConstraintsWithFormat("V:|-15-[v0]-15-|", views: container)
            
            container.addSubview(jobName)
            container.addSubview(jobNumber)
            container.addSubview(jobDescription)
            container.addSubview(jobStartDate)
            container.addSubview(jobEndDate)
            container.addSubview(jobTaskCount)
            container.addSubview(jobNameLeft)
            container.addSubview(jobNumberLeft)
            container.addSubview(jobDescriptionLeft)
            container.addSubview(jobStartDateLeft)
            container.addSubview(jobEndDateLeft)
            container.addSubview(jobTaskCountLeft)
            container.addSubview(expandBtn)
            
            let leftWidth : CGFloat = 150
            let rightBtnWidth : CGFloat = 30
            
            container.addConstraintsWithFormat("V:|[v0]|", views: expandBtn)
            container.addConstraintsWithFormat("V:|-10-[v0]-8-[v1]-8-[v2]-8-[v3]-8-[v4]-8-[v5]-10-|", views: jobName, jobNumber, jobDescription, jobStartDate, jobEndDate, jobTaskCount)
            container.addConstraintsWithFormat("H:|-10-[v0(\(leftWidth))]-8-[v1]-10-[v2(\(rightBtnWidth))]|", views: jobNameLeft, jobName, expandBtn)
            container.addConstraintsWithFormat("H:|-10-[v0(\(leftWidth))]-8-[v1]-10-[v2(\(rightBtnWidth))]|", views: jobNumberLeft, jobNumber, expandBtn)
            container.addConstraintsWithFormat("H:|-10-[v0(\(leftWidth))]-8-[v1]-10-[v2(\(rightBtnWidth))]|", views: jobDescriptionLeft, jobDescription, expandBtn)
            container.addConstraintsWithFormat("H:|-10-[v0(\(leftWidth))]-8-[v1]-10-[v2(\(rightBtnWidth))]|", views: jobStartDateLeft, jobStartDate, expandBtn)
            container.addConstraintsWithFormat("H:|-10-[v0(\(leftWidth))]-8-[v1]-10-[v2(\(rightBtnWidth))]|", views: jobEndDateLeft, jobEndDate, expandBtn)
            container.addConstraintsWithFormat("H:|-10-[v0(\(leftWidth))]-8-[v1]-10-[v2(\(rightBtnWidth))]|", views: jobTaskCountLeft, jobTaskCount, expandBtn)
            jobNameLeft.topAnchor.constraint(equalTo: jobName.topAnchor).isActive = true
            jobNumberLeft.topAnchor.constraint(equalTo: jobNumber.topAnchor).isActive = true
            jobDescriptionLeft.topAnchor.constraint(equalTo: jobDescription.topAnchor).isActive = true
            jobStartDateLeft.topAnchor.constraint(equalTo: jobStartDate.topAnchor).isActive = true
            jobEndDateLeft.topAnchor.constraint(equalTo: jobEndDate.topAnchor).isActive = true
            jobTaskCountLeft.topAnchor.constraint(equalTo: jobTaskCount.topAnchor).isActive = true
            
            setUpLabel(AppColors.appPrimaryTealColor, jobName)
            setUpLabel(AppColors.appPrimaryTealColor, jobNumber)
            setUpLabel(AppColors.appPrimaryTealColor, jobDescription)
            setUpLabel(AppColors.appPrimaryTealColor, jobStartDate)
            setUpLabel(AppColors.appPrimaryTealColor, jobEndDate)
            setUpLabel(AppColors.appPrimaryTealColor, jobTaskCount)
            setUpLabel(.black, jobNameLeft)
            setUpLabel(.black, jobNumberLeft)
            setUpLabel(.black, jobDescriptionLeft)
            setUpLabel(.black, jobStartDateLeft)
            setUpLabel(.black, jobEndDateLeft)
            setUpLabel(.black, jobTaskCountLeft)
            jobNameLeft.text = "JOB NAME"
            jobNumberLeft.text = "JOB NUMBER"
            jobDescriptionLeft.text = "JOB DESCRIPTION"
            jobStartDateLeft.text = "JOB START DATE"
            jobEndDateLeft.text = "JOB END DATE"
            jobTaskCountLeft.text = "JOB TASK COUNT"
            expandBtn.roundCorners([.topRight,.bottomRight], radius: 15)
            expandBtn.backgroundColor = AppColors.appPrimaryTealColor
            expandBtn.setImage(#imageLiteral(resourceName: "arrow-down-sign-to-navigate").maskWithColor(color: .white), for: .normal)
            expandBtn.imageView?.contentMode = .scaleAspectFit
            expandBtn.imageEdgeInsets = UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 5)
            container.cornerRadious = 15
        }
        
        func setUpLabel(_ color : UIColor, _ label : UILabel){
            label.font = UIFont.systemFont(ofSize: 16, weight: .semibold)
            label.numberOfLines = 0
            label.textColor = color
        }
        
    }

在我的视图控制器中,我正在使我的 table 具有这样的自动维度:

mainTable.backgroundColor = .clear
        mainTable.separatorStyle = .none
        mainTable.rowHeight = UITableView.automaticDimension
        mainTable.dataSource = self
        mainTable.delegate = self

这里是行方法的单元格:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = listofJobsTVC()
        cell.jobName.text = ": 1"
        cell.jobNumber.text = ": 1"
        cell.jobDescription.text = ": 1"
        cell.jobStartDate.text = ": 1"
        cell.jobEndDate.text = ": 1"
        cell.jobTaskCount.text = ": 1"
        return cell
    }

单元格的设计非常完美,但我创建的每个单元格都会打破这种奇怪的约束,这真的很烦人

2021-02-21 14:56:30.961185+0530 INDORE IPDS[30128:647175] SQLiteDB opened!
2021-02-21 14:56:34.325851+0530 INDORE IPDS[30128:647175] [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:0x600001363890 V:|-(15)-[INDORE_IPDS.ShadowRoundedView:0x7f806ec2a140]   (active, names: '|':INDORE_IPDS.listofJobsTVC:0x7f806ec27880 )>",
    "<NSLayoutConstraint:0x600001363840 V:[INDORE_IPDS.ShadowRoundedView:0x7f806ec2a140]-(15)-|   (active, names: '|':INDORE_IPDS.listofJobsTVC:0x7f806ec27880 )>",
    "<NSLayoutConstraint:0x600001363700 V:|-(10)-[UILabel:0x7f806ec10e60]   (active, names: '|':INDORE_IPDS.ShadowRoundedView:0x7f806ec2a140 )>",
    "<NSLayoutConstraint:0x6000013636b0 V:[UILabel:0x7f806ec10e60]-(8)-[UILabel:0x7f806ec27c50]   (active)>",
    "<NSLayoutConstraint:0x600001363660 V:[UILabel:0x7f806ec27c50]-(8)-[UILabel:0x7f806ec27ed0]   (active)>",
    "<NSLayoutConstraint:0x600001363610 V:[UILabel:0x7f806ec27ed0]-(8)-[UILabel:0x7f806ec28150]   (active)>",
    "<NSLayoutConstraint:0x6000013635c0 V:[UILabel:0x7f806ec28150]-(8)-[UILabel:0x7f806ec283d0]   (active)>",
    "<NSLayoutConstraint:0x600001363570 V:[UILabel:0x7f806ec283d0]-(8)-[UILabel:0x7f806ec28650]   (active)>",
    "<NSLayoutConstraint:0x600001363520 V:[UILabel:0x7f806ec28650]-(1000)-|   (active, names: '|':INDORE_IPDS.ShadowRoundedView:0x7f806ec2a140 )>",
    "<NSLayoutConstraint:0x6000013793b0 'UIView-Encapsulated-Layout-Height' INDORE_IPDS.listofJobsTVC:0x7f806ec27880.height == 44   (active)>"
)

Will attempt to recover by breaking constraint 
<NSLayoutConstraint:0x600001363570 V:[UILabel:0x7f806ec283d0]-(8)-[UILabel:0x7f806ec28650]   (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.

我已经用这种方法制作细胞一千次了,但从未见过像这样破坏约束的情况。我猜 44 是 Tableview 单元格的默认高度。也不要告诉我输入估计的行高..完成了..没有改变任何我之前创建的所有单元格都可以正常工作而没有估计的行高..我不知道该怎么做..我没有看到任何错误单元格中的约束...

参考这里的 addConstraintsWithFormat 方法:

func addConstraintsWithFormat(_ format: String, views: UIView...) {
        var viewsDictionary = [String: UIView]()
        for (index, view) in views.enumerated() {
            let key = "v\(index)"
            view.translatesAutoresizingMaskIntoConstraints = false
            viewsDictionary[key] = view
        }
        addConstraints(NSLayoutConstraint.constraints(withVisualFormat: format, options: [], metrics: nil, views: viewsDictionary))
    }

这是一个常见问题...

据我了解,自动布局 开始 其过程假设行(单元格)的默认高度为 44。当它开始布置单元格的 UI 元素,它可以抛出警告,因为它尚未 完成 布局,并且 在那一点 存在冲突。

这就是为什么您会收到警告/错误消息,但单元格按预期布局的原因。

解决此问题的一种方法是为底部约束提供低于要求的优先级

您可以通过更改代码中的一行来完成此操作:

container.addConstraintsWithFormat("V:|-10-[v0]-8-[v1]-8-[v2]-8-[v3]-8-[v4]-8-[v5]-10-|", views: jobName, jobNumber, jobDescription, jobStartDate, jobEndDate, jobTaskCount)

至:

container.addConstraintsWithFormat("V:|-10-[v0]-8-[v1]-8-[v2]-8-[v3]-8-[v4]-8-[v5]-10@750-|", views: jobName, jobNumber, jobDescription, jobStartDate, jobEndDate, jobTaskCount)

注意添加“[v5]-10 @750 -|”对于最后一个垂直约束。

这允许自动布局“暂时”打破约束,而不用警告消息填充调试控制台。


附带说明一下,我的印象是 VFL 已被 Apple 删除。至少,我已经好几年没有看到任何新的东西了。虽然它可以很好地用于简单的布局,但它有很多缺陷,而且在我看来,当出现问题时,它并不那么清晰或易于调试。

此外,您完成此操作的方式最终将宽度和尾随约束应用到您的 expandBtn 6 次。虽然它在此特定实例中没有不利影响,但很容易 意外地 添加多个约束,最终导致冲突。

这是我个人认为更合乎逻辑且更易于管理和调试的另一种方法:

    let leftWidth : CGFloat = 150
    let rightBtnWidth : CGFloat = 30
    
    expandBtn.translatesAutoresizingMaskIntoConstraints = false
    NSLayoutConstraint.activate([
        // constrain expand button
        // Top / Trailing / Bottom to container
        expandBtn.topAnchor.constraint(equalTo: container.topAnchor, constant: 0.0),
        expandBtn.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: 0.0),
        expandBtn.bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: 0.0),
        // width = rightBtnWidth (constant value)
        expandBtn.widthAnchor.constraint(equalToConstant: rightBtnWidth),
    ])
    
    // arrays of left and right labels
    let leftLabels: [UILabel] = [
        jobNameLeft, jobNumberLeft, jobDescriptionLeft, jobStartDateLeft, jobEndDateLeft, jobTaskCountLeft,
    ]
    let rightLabels: [UILabel] = [
        jobName, jobNumber, jobDescription, jobStartDate, jobEndDate, jobTaskCount,
    ]
    
    // this will be tracked inside the for loop
    var prevLabel: UILabel!
    
    for (leftLabel, rightLabel) in zip(leftLabels, rightLabels) {
        
        // of course
        leftLabel.translatesAutoresizingMaskIntoConstraints = false
        rightLabel.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            // all left labels have Leading: 10.0 and Width: leftWidth
            leftLabel.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 10.0),
            leftLabel.widthAnchor.constraint(equalToConstant: leftWidth),
            // all right labels have Leading: 8.0 from left label Trailing
            rightLabel.leadingAnchor.constraint(equalTo: leftLabel.trailingAnchor, constant: 8.0),
            // all right labels have Trailing: -10.0 from expand button Leading
            rightLabel.trailingAnchor.constraint(equalTo: expandBtn.leadingAnchor, constant: -10.0),
            // all right labels have Top equal to respective left label Top
            rightLabel.topAnchor.constraint(equalTo: leftLabel.topAnchor, constant: 0.0),
        ])
        
        if nil == prevLabel {
            // if it's the First left label
            //  constrain Top to container Top + 10.0
            leftLabel.topAnchor.constraint(equalTo: container.topAnchor, constant: 10.0).isActive = true
        } else {
            // not the first label, so
            //  constrain Top to previous label Bottom + 8.0
            leftLabel.topAnchor.constraint(equalTo: prevLabel.bottomAnchor, constant: 8.0).isActive = true
        }
        
        if leftLabel == leftLabels.last {
            // if it's the Last left label
            //  constrain Bottom to container Bottom - 10.0
            let c = leftLabel.bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: -10.0)
            // also give it a less-than-required Priority, so auto-layout won't complain
            c.priority = .defaultHigh
            c.isActive = true
        }
        
        prevLabel = leftLabel
    }

    // this is no longer needed
    /*
    container.addConstraintsWithFormat("V:|[v0]|", views: expandBtn)
    container.addConstraintsWithFormat("V:|-10-[v0]-8-[v1]-8-[v2]-8-[v3]-8-[v4]-8-[v5]-10@750-|", views: jobName, jobNumber, jobDescription, jobStartDate, jobEndDate, jobTaskCount)
    container.addConstraintsWithFormat("H:|-10-[v0(\(leftWidth))]-8-[v1]-10-[v2(\(rightBtnWidth))]|", views: jobNameLeft, jobName, expandBtn)
    container.addConstraintsWithFormat("H:|-10-[v0(\(leftWidth))]-8-[v1]-10-[v2(\(rightBtnWidth))]|", views: jobNumberLeft, jobNumber, expandBtn)
    container.addConstraintsWithFormat("H:|-10-[v0(\(leftWidth))]-8-[v1]-10-[v2(\(rightBtnWidth))]|", views: jobDescriptionLeft, jobDescription, expandBtn)
    container.addConstraintsWithFormat("H:|-10-[v0(\(leftWidth))]-8-[v1]-10-[v2(\(rightBtnWidth))]|", views: jobStartDateLeft, jobStartDate, expandBtn)
    container.addConstraintsWithFormat("H:|-10-[v0(\(leftWidth))]-8-[v1]-10-[v2(\(rightBtnWidth))]|", views: jobEndDateLeft, jobEndDate, expandBtn)
    container.addConstraintsWithFormat("H:|-10-[v0(\(leftWidth))]-8-[v1]-10-[v2(\(rightBtnWidth))]|", views: jobTaskCountLeft, jobTaskCount, expandBtn)
    
    jobNameLeft.topAnchor.constraint(equalTo: jobName.topAnchor).isActive = true
    jobNumberLeft.topAnchor.constraint(equalTo: jobNumber.topAnchor).isActive = true
    jobDescriptionLeft.topAnchor.constraint(equalTo: jobDescription.topAnchor).isActive = true
    jobStartDateLeft.topAnchor.constraint(equalTo: jobStartDate.topAnchor).isActive = true
    jobEndDateLeft.topAnchor.constraint(equalTo: jobEndDate.topAnchor).isActive = true
    jobTaskCountLeft.topAnchor.constraint(equalTo: jobTaskCount.topAnchor).isActive = true
    */