以编程方式制作的约束不起作用

Posted

技术标签:

【中文标题】以编程方式制作的约束不起作用【英文标题】:Programmatically made constraints are not working 【发布时间】:2019-11-01 23:52:41 【问题描述】:

当我想摆脱 xib 并以编程方式进行布局时,我发现使用完全相同的约束并不像我预期的那样起作用。

我想做这个 UITableViewCell 这是一个非常简单的单元格,其右侧有一个小图标以及一个活动指示器,因此我可以切换我想查看的那个。它们在视图内,左侧是标签

这些是我在大纲视图中的约束 而且效果很好。但是,当我删除 XIB 并自己编写所有代码时,就没有任何效果了

这是我的代码:

class StandardRow: UITableViewCell     
    private var initialWidth: CGFloat = 20


public var fetching: Bool = false 
    didSet 
        if (fetching) 
            activityIndicator?.startAnimating()
         else 
            activityIndicator?.stopAnimating()
        

        changeImageWidth()
    


public var rightImage: UIImage? = nil 
    didSet 
        rightImageView?.image = rightImage
        changeImageWidth()
    


private func changeImageWidth() 
    if (activityIndicator?.isAnimating) ?? false || rightImage != nil 
        imageWidth?.constant = initialWidth
     else 
        imageWidth?.constant = 0
    


override func prepareForReuse() 
    valueLabel?.text = ""
    imageView?.image = nil
    rightImage = nil
    fetching = false
    textLabel?.text = ""
    accessoryType = .none



//Views
private var imageContainer = UIView()
private var rightImageView = UIImageView()
private var activityIndicator: UIActivityIndicatorView? = UIActivityIndicatorView()
public var valueLabel: UILabel? = UILabel()
private var imageWidth: NSLayoutConstraint? = nil

override init(style: UITableViewCell.CellStyle = .default, reuseIdentifier: String? = nil) 
    super.init(style: style, reuseIdentifier: reuseIdentifier)
    buildView()


required init?(coder: NSCoder) 
    super.init(coder: coder)
    buildView()


func buildView() 
    contentView.addSubview(valueLabel!)
    imageContainer.addSubview(rightImageView)
    imageContainer.addSubview(activityIndicator!)
    contentView.addSubview(imageContainer)

    imageContainer.backgroundColor = .red


override func layoutSubviews() 
    super.layoutSubviews()

    //IMAGE CONTAINER CONSTRAINTS
    imageWidth = NSLayoutConstraint(item: imageContainer, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: initialWidth)
    imageWidth?.priority = UILayoutPriority(rawValue: 999)
    imageWidth?.isActive = true
    let bottomImageContainerConstraint = NSLayoutConstraint(item: imageContainer, attribute: .bottom, relatedBy: .equal, toItem: contentView, attribute: .bottom, multiplier: 1, constant: 0)
    bottomImageContainerConstraint.isActive = true
    bottomImageContainerConstraint.priority = UILayoutPriority(rawValue: 999)

    let topImageContainerConstraint = NSLayoutConstraint(item: imageContainer, attribute: .top, relatedBy: .equal, toItem: contentView, attribute: .top, multiplier: 1, constant: 0)
    topImageContainerConstraint.isActive = true
    topImageContainerConstraint.priority = UILayoutPriority(rawValue: 999)

    let trailingImageContainerConstraint = NSLayoutConstraint(item: imageContainer, attribute: .trailing, relatedBy: .equal, toItem: contentView, attribute: .trailing, multiplier: 1, constant: 5)
    trailingImageContainerConstraint.priority = UILayoutPriority(rawValue: 999)
    trailingImageContainerConstraint.isActive = true

    let centerYImageContainerConstraint = NSLayoutConstraint(item: imageContainer, attribute: .centerY, relatedBy: .equal, toItem: contentView, attribute: .centerY, multiplier: 1, constant: 0)
    centerYImageContainerConstraint.isActive = true
    centerYImageContainerConstraint.priority = UILayoutPriority(rawValue: 999)
    //VALUE LABEL CONSTRAINTS
    let trailingValueLabelConstraint = NSLayoutConstraint(item: valueLabel!, attribute: .trailing, relatedBy: .equal, toItem: imageContainer, attribute: .leading, multiplier: 1, constant: 5)
    trailingValueLabelConstraint.isActive = true
    trailingValueLabelConstraint.priority = UILayoutPriority(rawValue: 999)

    let centerYValueLabelConstraint = NSLayoutConstraint(item: valueLabel!, attribute: .centerY, relatedBy: .equal, toItem: contentView, attribute: .centerY, multiplier: 1, constant: 0)
    centerYValueLabelConstraint.isActive = true
    centerYValueLabelConstraint.priority = UILayoutPriority(rawValue: 999)
    //ACTIVITY INDICATOR CONSTRAINGS
    NSLayoutConstraint(item: activityIndicator!, attribute: .trailing, relatedBy: .equal, toItem: imageContainer, attribute: .trailing, multiplier: 1, constant: 0).isActive = true
    NSLayoutConstraint(item: activityIndicator!, attribute: .leading, relatedBy: .equal, toItem: imageContainer, attribute: .leading, multiplier: 1, constant: 11).isActive = false
    NSLayoutConstraint(item: activityIndicator!, attribute: .bottom, relatedBy: .equal, toItem: imageContainer, attribute: .bottom, multiplier: 1, constant: 11).isActive = false
    NSLayoutConstraint(item: activityIndicator!, attribute: .top, relatedBy: .equal, toItem: imageContainer, attribute: .top, multiplier: 1, constant: 0).isActive = true
    NSLayoutConstraint(item: activityIndicator!, attribute: .centerY, relatedBy: .equal, toItem: imageContainer, attribute: .centerY, multiplier: 1, constant: 0).isActive = true
    //RIGHT IMAGE VIEW CONSTRAINTS
    NSLayoutConstraint(item: rightImageView, attribute: .trailing, relatedBy: .equal, toItem: activityIndicator!, attribute: .trailing, multiplier: 1, constant: 0).isActive = true
    NSLayoutConstraint(item: rightImageView, attribute: .leading, relatedBy: .equal, toItem: rightImageView, attribute: .leading, multiplier: 1, constant: 0).isActive = true
    NSLayoutConstraint(item: rightImageView, attribute: .bottom, relatedBy: .equal, toItem: activityIndicator!, attribute: .bottom, multiplier: 1, constant: 0).isActive = true
    NSLayoutConstraint(item: rightImageView, attribute: .top, relatedBy: .equal, toItem: activityIndicator!, attribute: .top, multiplier: 1, constant: 0).isActive = true
    NSLayoutConstraint(item: rightImageView, attribute: .centerY, relatedBy: .equal, toItem: activityIndicator!, attribute: .centerY, multiplier: 1, constant: 0).isActive = true
    //changeImageWidth()

所以我对它的来源有一些想法,首先将“translatesAutoresizingMaskIntoConstraints”默认设置为 true,但是当我在超级视图中将其设置为 false 时,我的单元格不再显示,并且在contentView,Xcode 告诉我,由于未定义的行为,我不应该这样做

我也在使用 Reveal 来调试我的 UI,然后我发现了那些特殊的值:

这不是我想要的,Reveal 报告说这些约束正在将视图的自动调整大小掩码转换为自动布局,因此它将证实先前的理论。我确实将某些约束的优先级设置为 999,否则它们会被破坏。

我实际上处于死胡同,我认为我遗漏了一些东西,但我无法确定是什么,因为我对非界面构建器约束没有足够的经验

【问题讨论】:

在代码中使用锚约束要简单得多。 不要在layoutSubviews 中添加约束。每次布局视图时,您最终都会一遍又一遍地添加重复的约束。请改用buildView 我认为您将translatesAutoresizingMaskIntoConstraints 设置为false 是正确的,但您应该只将创建的子视图设置为false,不是单元格本身,也不是单元格的contentView。尝试将 imageContainerrightImageViewactivityIndicatorvalueLabel 设置为 false just。如果这可以解决您的问题(我不能 100% 确定它会),那么我会将其添加为答案。 few cmets:(1)您需要在创建单元格时设置约束,而不是在 layoutSubviews 中(2)您正在为容器设置顶部、底部和 centerY 约束 - 如果您锚定顶部&底部你不需要做中心(3)使用锚,因为它们更容易阅读和调试(4)如果你将所有优先级设置为相同,那么设置它们没有意义(5)你似乎没有为标签设置任何大小或前导约束。我将在下面将它们重写为锚点并尝试解决这些问题。 @CheshireChild 仍然需要带有锚点等的示例代码,或者您现在可以了吗? 【参考方案1】:

试试 Anchors,它更容易。

示例

var redView = UIView()
redView.backgroundColor = .red
anyView.addsubView(redView)
redView.translatesAutoresizingMaskIntoConstraints = false
redView.centerXAnchor.constraint(equalTo: self.parentView.centerXAnchor).isActive = true
redView.centerYAnchor.constraint(equalTo: self.parentView.centerYAnchor).isActive = true
redView.heightAnchor.constraint(equalToConstant: 100).isActive = true
redView.widthAnchor.constraint(equalToConstant: 100).isActive = true

【讨论】:

【参考方案2】:

您可以将相同的方法添加到您的 UIView 扩展中

 func constrainToEdges(_ subview: UIView, top: CGFloat = 0, bottom: CGFloat = 0, leading: CGFloat = 0, trailing: CGFloat = 0) 

    subview.translatesAutoresizingMaskIntoConstraints = false

    let topContraint = NSLayoutConstraint(
        item: subview,
        attribute: .top,
        relatedBy: .equal,
        toItem: self,
        attribute: .top,
        multiplier: 1.0,
        constant: top)

    let bottomConstraint = NSLayoutConstraint(
        item: subview,
        attribute: .bottom,
        relatedBy: .equal,
        toItem: self,
        attribute: .bottom,
        multiplier: 1.0,
        constant: bottom)

    let leadingContraint = NSLayoutConstraint(
        item: subview,
        attribute: .leading,
        relatedBy: .equal,
        toItem: self,
        attribute: .leading,
        multiplier: 1.0,
        constant: leading)

    let trailingContraint = NSLayoutConstraint(
        item: subview,
        attribute: .trailing,
        relatedBy: .equal,
        toItem: self,
        attribute: .trailing,
        multiplier: 1.0,
        constant: trailing)

    addConstraints([
        topContraint,
        bottomConstraint,
        leadingContraint,
        trailingContraint])

【讨论】:

【参考方案3】:

我建议使用此framework 以编程方式构建基于约束的布局,它使过程简单快捷。以该单元格的 contentView 设置为例:

contentView.addSubview(descriptionLabel)
    contentView.addSubview(amountLabel)
    contentView.addSubview(dateLabel)
    contentView.addSubview(bottomRightLabel)

    constrain(descriptionLabel, amountLabel, dateLabel, bottomRightLabel)  desc, amount, date, bottomRight in

        desc.top              == desc.superview!.top + 16
        desc.left             == desc.superview!.left + 16
        desc.right            <= amount.left + 12
        desc.bottom           == date.top - 12

        amount.centerY        == desc.centerY
        amount.right          == amount.superview!.right - 12

        date.left             == date.superview!.left + 16
        date.right            <= bottomRight.left - 12
        date.bottom           == date.superview!.bottom - 16

        bottomRight.centerY   == date.centerY
        bottomRight.right     == bottomRight.superview!.right - 12
    

【讨论】:

以上是关于以编程方式制作的约束不起作用的主要内容,如果未能解决你的问题,请参考以下文章

以编程方式添加的约束不起作用

在 Swift 中以编程方式添加约束不起作用

编程约束在 UICollectionViewCell Swift 中不起作用

以编程方式创建布局,使用堆栈视图和约束不起作用

以编程方式自动布局不起作用

为啥这些约束不起作用?