1

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

7
  • 2
    It's much simpler to use anchor constraints in code. Commented Nov 2, 2019 at 0:08
  • 1
    Don't add constraints in layoutSubviews. You'll end up adding duplicate constraints over and over every time the view is laid out. Do it in buildView instead. Commented Nov 2, 2019 at 0:47
  • 1
    I think you were on the right track with setting translatesAutoresizingMaskIntoConstraints to false, but you should only set that to false for the subviews that you've created, not the cell itself nor the cell's contentView. Try setting it to false just for imageContainer, rightImageView, activityIndicator, and valueLabel. If this fixes your issue (I'm not 100% sure it will), then I'll add this as an answer. Commented Nov 2, 2019 at 0:49
  • 1
    few comments: (1)you need to set constraints when the cell is created, not in layoutSubviews (2) You're setting top, bottom and centreY constraints to the container - if you are anchoring top & bottom you don't need to do the centre (3) use anchors as they're far easier to read and debug (4) if you're setting all priorities to the same, then there's no point setting them (5) you don't seem to have set any size or leading constraint for the label. I'll have a crack at rewriting them as anchors below and try to address these. Commented Nov 2, 2019 at 1:32
  • 1
    @CheshireChild Still need example code with anchors etc or you OK now? Commented Nov 2, 2019 at 8:46

3 Answers 3

1

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
Sign up to request clarification or add additional context in comments.

Comments

1

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

Comments

0

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
    }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.