UITableViewCell 未按预期显示约束

Posted

技术标签:

【中文标题】UITableViewCell 未按预期显示约束【英文标题】:UITableViewCell not displaying constraints as intended 【发布时间】:2020-07-25 12:27:08 【问题描述】:

我正在尝试以编程方式创建一个 tableview 单元格,但在按预期布局时遇到了一些麻烦。我认为这取决于我调用事物的顺序,但我无法按预期显示内容(我尝试了 uiview 上的插图和其他约束,它似乎工作正常)。

我在 VC 中注册了 tableview 单元格,我还在 cellforrow 中将一个可重用的单元格出列,我在其中传递了活动对象来更新单元格 UI。单元格的高度由 heightforrow 回调设置为 300。

ActivityCell 代码

class ActivityCell: UITableViewCell 
    
    private var activityCard = UIView()
    private var verticalStack = UIStackView()
    private var nameStack = UIStackView()      // top horizontal stack
    private let profileImage = UIImageView()
    private let profileNameLbl = UILabel()
    private let dateLbl = UILabel()
    private let raceVerticalStack = UIStackView()  //middle
    private let topRaceStack = UIStackView()
    private let bottomRaceStack = UIStackView()
    private let raceNameStack = UIStackView()
    private let raceNameLbl = UILabel()
    private let distanceStack = UIStackView()
    private let distanceLbl = UILabel()
    private let distanceValueLbl = UILabel()
    private let timeStack = UIStackView()
    private let timeLbl = UILabel()
    private let timeValueLbl = UILabel()
    private let paceStack = UIStackView()
    private let paceLbl = UILabel()
    private let paceValueLbl = UILabel()
    private let positionStack = UIStackView()
    private let positionLbl = UILabel()
    private let positionValueLbl = UILabel()
    private let pointsStack = UIStackView()
    private let pointsLbl = UILabel()
    private let pointsValueLbl = UILabel()
    private let bottomStack = UIStackView()  //bottom
    private let clapStack = UIStackView()
    private let clapIcon = UIImageView()
    private let clapCountLbl = UILabel()
    private let clapCommentStack = UIStackView()
    private let commentStack = UIStackView()
    private let commentIcon = UIImageView()
    private let commentCountLbl = UILabel()
    private let clapButton = UIButton()     // interaction buttons
    private let commentButton = UIButton()
    
    
    private let gradientStart = UIColor(red: 24/255, green: 44/255, blue: 86/255, alpha: 1.0)
    private let gradientEnd = UIColor(red: 234/255, green: 82/255, blue: 119/255, alpha: 1.0)
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) 
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        layer.masksToBounds = true
        self.contentView.layer.masksToBounds = true
        activityCard.frame = self.frame.inset(by: UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10))
        verticalStack.frame = activityCard.frame
        
        setupHierarchy()
        setConstraints()
        configureView()
        
    
    
    
    required init?(coder: NSCoder) 
        fatalError("init(coder:) has not been implemented")
    
    
    private func configureView() 
        labelSetup()
        buttonSetup()
        stackViewSetup()
        imageSetup()
        stackViewSetup()
        setShadow()
    

    func updateCell(with activity: RaceActivity) 
        profileNameLbl.text = activity.runnerName
        profileImage.image = activity.profileImage
        raceNameLbl.text = activity.raceName
        distanceValueLbl.text = activity.distance
        timeValueLbl.text = activity.time
        paceValueLbl.text = activity.pace
        positionValueLbl.text = activity.position
        pointsValueLbl.text = activity.points
    
    
    private func labelSetup() 
        profileNameLbl.font = .Graphik()
        raceNameLbl.font = .Graphik(.medium, size: 22)
        dateLbl.font = .Graphik(.light, size: 17)
        distanceLbl.font = .Graphik()
        timeLbl.font = .Graphik()
        paceLbl.font = .Graphik()
        positionLbl.font = .Graphik()
        pointsLbl.font = .Graphik()
        clapCountLbl.font = .Graphik()
        commentCountLbl.font = .Graphik()
        paceLbl.font = .Graphik()
        distanceValueLbl.font = .Graphik(.medium)
        timeValueLbl.font = .Graphik(.medium)
        paceValueLbl.font = .Graphik(.medium)
        positionValueLbl.font = .Graphik(.medium)
   
        timeLbl.textAlignment = .left
        paceLbl.textAlignment = .center
        paceValueLbl.textAlignment = .center
        pointsLbl.textAlignment = .left
        pointsValueLbl.textAlignment = .left
        dateLbl.textAlignment = .right
        clapCountLbl.text = "33"
        commentCountLbl.text = "12"
        positionLbl.text = "Position"
        pointsLbl.text = "Points"
        distanceLbl.text = "Distance"
        timeLbl.text = "Time"
        paceLbl.text = "Pace"
        dateLbl.text = "Friday"
    
    
    private func stackViewSetup() 
        nameStack.distribution = .fillProportionally
        topRaceStack.distribution = .fillEqually
        bottomRaceStack.distribution = .fillEqually
        clapCommentStack.distribution = .fillEqually
        bottomStack.distribution = .fillEqually
        
        raceNameStack.axis = .horizontal
        nameStack.axis = .horizontal
        verticalStack.axis = .vertical
        raceVerticalStack.axis = .vertical
        bottomStack.axis = .horizontal
        topRaceStack.axis = .horizontal
        bottomRaceStack.axis = .horizontal
        distanceStack.axis = .vertical
        timeStack.axis = .vertical
        paceStack.axis = .vertical
        positionStack.axis = .vertical
        pointsStack.axis = .vertical
        
        nameStack.spacing = 10
        verticalStack.spacing = 5
        raceVerticalStack.spacing = 10
        bottomStack.spacing = 10
        
        nameStack.alignment = .center
        distanceStack.alignment = .center
        timeStack.alignment = .center
        paceStack.alignment = .center
        pointsStack.alignment = .center
        positionStack.alignment = .center
        clapCommentStack.alignment = .center
        bottomStack.alignment = .center
        
        raceVerticalStack.spacing = 5
        topRaceStack.spacing = 2
        clapCommentStack.spacing = 3
        clapStack.spacing = 4
        commentStack.spacing = 4
    
    
    private func buttonSetup() 
        clapButton.setTitle("Clap", for: .normal)
        clapButton.setTitleColor(.darkGray, for: .normal)
        clapButton.titleLabel?.font = .Graphik(.regular, size: 14)
        clapButton.titleEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
        clapButton.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 30)
        clapButton.setImage(UIImage(named: "clap"), for: .normal)
        commentButton.setTitle("Comment", for: .normal)
        commentButton.setTitleColor(.darkGray, for: .normal)
        commentButton.titleLabel?.font = .Graphik(.regular, size: 14)
        commentButton.titleEdgeInsets = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 0)
        commentButton.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 10)
        commentButton.setImage(UIImage(named: "comment"), for: .normal)
    
    
    private func imageSetup() 
        profileImage.layer.cornerRadius = 15
        profileImage.backgroundColor = .lightGray
        clapIcon.image = UIImage(named: "clapGrey")
        clapIcon.contentMode = .scaleAspectFit
        commentIcon.image = UIImage(named: "commentGrey")
        commentIcon.contentMode = .scaleAspectFit
    
 
    
    private func setShadow() 

        let gradient = CAGradientLayer()
        gradient.frame =  CGRect(origin: CGPoint(x: 10, y: 5), size: frame.size)
        gradient.colors = [gradientStart.cgColor, gradientEnd.cgColor]

        let border = CAShapeLayer()
        border.lineWidth = 2
        border.path = UIBezierPath(roundedRect: frame.inset(by: UIEdgeInsets(top: 10, left: 2, bottom: 10, right: 22)), cornerRadius: 12).cgPath
        border.strokeColor = UIColor.black.cgColor
        border.fillColor = UIColor.clear.cgColor
        gradient.mask = border
        removeExistingGradient(from: self)
        self.layer.addSublayer(gradient)
        
    
    
    private func setConstraints() 
        
        let constraints = [
            nameStack.heightAnchor.constraint(equalToConstant: 50),
            raceNameStack.heightAnchor.constraint(equalToConstant: 30),
            topRaceStack.heightAnchor.constraint(equalToConstant: 60),
            bottomRaceStack.heightAnchor.constraint(equalToConstant: 60),
            profileImage.heightAnchor.constraint(equalToConstant: 30),
            profileImage.widthAnchor.constraint(equalToConstant: 30),
            commentButton.widthAnchor.constraint(greaterThanOrEqualToConstant: 90),
            clapButton.widthAnchor.constraint(greaterThanOrEqualToConstant: 90),
            clapIcon.widthAnchor.constraint(equalToConstant: 15),
            commentIcon.widthAnchor.constraint(equalToConstant: 15),
        ]
        NSLayoutConstraint.activate(constraints)
        setupMargins()
    
    
    private func setupMargins() 
        nameStack.layoutMargins = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 25)
        raceNameStack.layoutMargins = UIEdgeInsets(top:0, left: 20, bottom: 0, right: 0)
        raceVerticalStack.layoutMargins = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 35)
        bottomStack.layoutMargins = UIEdgeInsets(top: 0, left: 20, bottom: 10, right: 30)
        nameStack.isLayoutMarginsRelativeArrangement = true
        topRaceStack.isLayoutMarginsRelativeArrangement = true
        bottomRaceStack.isLayoutMarginsRelativeArrangement = true
        raceNameStack.isLayoutMarginsRelativeArrangement = true
        bottomStack.isLayoutMarginsRelativeArrangement = true
        addLines()
    
    
    private func addLines() 
        guard bottomStack.frame.height > 0 else  return 
        let horizontalStart = CGPoint(x: bottomStack.frame.minX, y: bottomStack.frame.minY)
        let bottomLine = UIView(frame: CGRect(x: horizontalStart.x + 20, y: horizontalStart.y + 12, width: bottomStack.frame.width - 40, height: 1.0))
        bottomLine.tag = 99
        bottomLine.layer.borderWidth = 0.8
        bottomLine.layer.borderColor = UIColor.lightGray.cgColor

        let clapStackLine = UIView(frame: CGRect(x: clapCommentStack.frame.maxX, y: horizontalStart.y + 22, width: 1.0, height: clapCommentStack.frame.maxY - clapCommentStack.frame.minY))
        clapStackLine.layer.borderWidth = 0.8
        clapStackLine.layer.borderColor = UIColor.lightGray.cgColor
        let commentLine = UIView(frame: CGRect(x: clapButton.frame.maxX, y: horizontalStart.y + 22, width: 1.0, height: clapCommentStack.frame.maxY - clapCommentStack.frame.minY))
        commentLine.layer.borderWidth = 0.8
        commentLine.layer.borderColor = UIColor.lightGray.cgColor
        let topLine = UIView(frame: CGRect(x: horizontalStart.x + 20, y: nameStack.frame.maxY + 5, width: bottomStack.frame.width - 40, height: 1.0))
        topLine.layer.borderWidth = 0.8
        topLine.layer.borderColor = UIColor.lightGray.cgColor
        self.activityCard.addSubview(bottomLine)
        self.activityCard.addSubview(clapStackLine)
        self.activityCard.addSubview(commentLine)
        self.activityCard.addSubview(topLine)
    
    
    
    private func setupHierarchy() 
        nameStack.addArrangedSubview(profileImage)
        nameStack.addArrangedSubview(profileNameLbl)
        nameStack.addArrangedSubview(dateLbl)
        
        raceVerticalStack.addArrangedSubview(raceNameStack)
        raceVerticalStack.addArrangedSubview(topRaceStack)
        raceVerticalStack.addArrangedSubview(bottomRaceStack)
        
        raceNameStack.addArrangedSubview(raceNameLbl)
        
        topRaceStack.addArrangedSubview(distanceStack)
        topRaceStack.addArrangedSubview(timeStack)
        topRaceStack.addArrangedSubview(paceStack)
        
        bottomRaceStack.addArrangedSubview(positionStack)
        bottomRaceStack.addArrangedSubview(pointsStack)
        bottomRaceStack.addArrangedSubview(UIView())
        
        distanceStack.addArrangedSubview(distanceLbl)
        distanceStack.addArrangedSubview(distanceValueLbl)
        timeStack.addArrangedSubview(timeLbl)
        timeStack.addArrangedSubview(timeValueLbl)
        paceStack.addArrangedSubview(paceLbl)
        paceStack.addArrangedSubview(paceValueLbl)
        positionStack.addArrangedSubview(positionLbl)
        positionStack.addArrangedSubview(positionValueLbl)
        pointsStack.addArrangedSubview(pointsLbl)
        pointsStack.addArrangedSubview(pointsValueLbl)
        
        clapStack.addArrangedSubview(clapIcon)
        clapStack.addArrangedSubview(clapCountLbl)
        
        commentStack.addArrangedSubview(commentIcon)
        commentStack.addArrangedSubview(commentCountLbl)
        
        clapCommentStack.addArrangedSubview(clapStack)
        clapCommentStack.addArrangedSubview(commentStack)
        
        mainStackHierarchy()
    
    
    private func mainStackHierarchy() 
        bottomStack.addArrangedSubview(clapCommentStack)
        bottomStack.addArrangedSubview(clapButton)
        bottomStack.addArrangedSubview(commentButton)
        
        verticalStack.addArrangedSubview(nameStack)
        verticalStack.addArrangedSubview(raceNameStack)
        verticalStack.addArrangedSubview(raceVerticalStack)
        verticalStack.addArrangedSubview(bottomStack)
        activityCard.addSubview(verticalStack)
        self.contentView.addSubview(activityCard)
    


当前输出截图:


预期输出的屏幕截图:


任何帮助表示赞赏:D

【问题讨论】:

【参考方案1】:

您遇到了问题,因为您使用了很多显式帧而不是利用自动布局。

首先,如果您将 init() 函数更改为此并运行您的应用程序:

override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) 
    super.init(style: style, reuseIdentifier: reuseIdentifier)
    layer.masksToBounds = true
    self.contentView.layer.masksToBounds = true
    
    // no need to set frames
    //activityCard.frame = self.frame.inset(by: UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10))
    //verticalStack.frame = activityCard.frame
    
    setupHierarchy()
    setConstraints()
    configureView()

    activityCard.translatesAutoresizingMaskIntoConstraints = false
    verticalStack.translatesAutoresizingMaskIntoConstraints = false
    
    NSLayoutConstraint.activate([
        
        activityCard.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10.0),
        activityCard.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10.0),
        activityCard.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -10.0),
        activityCard.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -10.0),

        verticalStack.topAnchor.constraint(equalTo: activityCard.topAnchor, constant: 6.0),
        verticalStack.leadingAnchor.constraint(equalTo: activityCard.leadingAnchor, constant: 6.0),
        verticalStack.trailingAnchor.constraint(equalTo: activityCard.trailingAnchor, constant: -6.0),
        verticalStack.bottomAnchor.constraint(equalTo: activityCard.bottomAnchor, constant: -6.0),

    ])
    

你会发现你离你想要的更近了。

接下来,改变:

private var activityCard = UIView()

到:

private var activityCard = ActivityCardView() 

并使用这个类:

class ActivityCardView: UIView 
    private let gradientStart = UIColor(red: 24/255, green: 44/255, blue: 86/255, alpha: 1.0)
    private let gradientEnd = UIColor(red: 234/255, green: 82/255, blue: 119/255, alpha: 1.0)

    private let gradLayer = CAGradientLayer()

    override init(frame: CGRect) 
        super.init(frame: frame)
        layer.addSublayer(gradLayer)
        gradLayer.colors = [gradientStart.cgColor, gradientEnd.cgColor]
    
    required init?(coder: NSCoder) 
        fatalError("init(coder:) has not been implemented")
    
    override func layoutSubviews() 
        super.layoutSubviews()

        gradLayer.frame = bounds
        
        let border = CAShapeLayer()
        border.lineWidth = 2
        border.path = UIBezierPath(roundedRect: bounds.inset(by: UIEdgeInsets(top: 10, left: 2, bottom: 10, right: 2)), cornerRadius: 12).cgPath
        border.strokeColor = UIColor.black.cgColor
        border.fillColor = UIColor.clear.cgColor
        gradLayer.mask = border
    

然后,从单元类中删除您的 private func setShadow()。这个ActivityCardView 类会自动处理渐变边框。

对于您的“分隔符”行,您最好将它们添加为verticalStack 的排列子视图,而不是尝试使用绝对坐标定位它们:

private func mainStackHierarchy() 
    bottomStack.addArrangedSubview(clapCommentStack)
    bottomStack.addArrangedSubview(clapButton)
    bottomStack.addArrangedSubview(commentButton)
    
    verticalStack.addArrangedSubview(nameStack)
    
    var hLineView = UIView()
    hLineView.heightAnchor.constraint(equalToConstant: 1.0).isActive = true
    hLineView.backgroundColor = .lightGray
    verticalStack.addArrangedSubview(hLineView)
    
    verticalStack.addArrangedSubview(raceNameStack)
    verticalStack.addArrangedSubview(raceVerticalStack)

    hLineView = UIView()
    hLineView.heightAnchor.constraint(equalToConstant: 1.0).isActive = true
    hLineView.backgroundColor = .lightGray
    verticalStack.addArrangedSubview(hLineView)
    
    verticalStack.addArrangedSubview(bottomStack)
    activityCard.addSubview(verticalStack)
    self.contentView.addSubview(activityCard)

此时,您的输出应如下所示:

比赛名称上的下划线可能最好使用带有下划线样式的属性文本来处理。

我假设您会想要对间距进行一些调整——但这不应该有任何问题。

【讨论】:

太棒了!非常感谢,我不敢相信我忽略了为活动卡创建一个新课程。我想我在接近它时过多地考虑故事板设计,而不是利用以编程方式创建 UI 元素的优势

以上是关于UITableViewCell 未按预期显示约束的主要内容,如果未能解决你的问题,请参考以下文章

程序约束未按预期工作

UITableViewCell 高度内的 UITableView 未按预期工作

当UITableViewCell被点击时,UILabel未按预期更新的行数

未按预期应用约束

iOS Storyboard 约束未按预期运行

IOS 8 高度约束动画未按预期工作