CheshireChild
CheshireChild

Reputation: 148

Programmatically made constraints are not working

As I want to move away from xib and make my layout programmatically, I found that using the same exact constraints doesn't work as I would expect.

I want to make this UITableViewCell TableCell I want to make It's a quite simple cell with a small icon to its right as well as an Activity Indicator so I can toggle which one I want to see. They are inside a View and to their left is a label

Those are my constraints in the outline view Outline view And it works perfectly. However when I'm removing the XIB and doing all of the code myself, nothing works anymore

So here's my code:

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()
}}

So I have a few ideas to where it can come from, firstly being "translatesAutoresizingMaskIntoConstraints" set to true by default, but when I'm setting it to false in the superview then my cell doesn't show anymore and in the contentView, Xcode tells me I shouldn't do that because of an undefined behaviour

I'm also using Reveal to debug my UI and then I found those peculiar values:

Reveal values Which is not what I want, Reveal is reporting that those constraints are translating the autoresizing mask of the view to autolayout so it would confirm the previous theory. I did set the priority to 999 to some of the constraints because otherwise they would be broken.

I'm actually at a dead end and I think I'm missing something but I can't pinpoint what as I don't have enough experience with non-interface builder constraints

Upvotes: 1

Views: 1148

Answers (3)

Arturo Reyes
Arturo Reyes

Reputation: 309

I recommend using this framework for building constraint based layouts programmatically, it makes the process straightforward and faster. Take the setup for the contentView of this cell for example:

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
    }

Upvotes: 0

isHidden
isHidden

Reputation: 860

You can add the same method to your UIView extension

 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])
}

Upvotes: 1

youssef mostafa
youssef mostafa

Reputation: 306

Try Anchors, it's much easier.

Example

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

Upvotes: 1

Related Questions