Search code examples
swiftnslayoutconstraint

Why is my second button behaving different when using auto layout constraints?


I can not figure out why my button is behaving different when using auto layout constraints programmatically.

When setting the view that holds my cancelBtn every thing is working fine:

 let cancelBtnView = TriangularView(frame: CGRect(x: 0, y: 0, width: 300, height: 150))

 let cancelBtn = UIButton()
 cancelBtnView.addSubview(cancelBtn)
 cancelBtn.titleLabel!.font = UIFont.fontAwesome(ofSize: 35, style: .regular)
 cancelBtn.setTitle(String.fontAwesomeIcon(name: .times), for: .normal)
 cancelBtn.frame = CGRect(x: cancelBtnView.bounds.width / 16, y: cancelBtnView.bounds.height / 2 ,width: 55, height: 55)

I get the following layout:

enter image description here

When setting up the view for my doneBtn I get a different output:

let doneBtnView = TriangularView()

 doneBtnView.translatesAutoresizingMaskIntoConstraints = false
 doneBtnView.widthAnchor.constraint(equalToConstant: 300).isActive = true
 doneBtnView.heightAnchor.constraint(equalToConstant: 150).isActive = true
 doneBtnView.trailingAnchor.constraint(equalTo: actionButtonView.trailingAnchor).isActive = true
 doneBtnView.bottomAnchor.constraint(equalTo: actionButtonView.bottomAnchor).isActive = true

 let doneBtn = UIButton()
 doneBtnView.addSubview(doneBtn)
 doneBtn.titleLabel!.font = UIFont.fontAwesome(ofSize: 35, style: .regular)
 doneBtn.setTitle(String.fontAwesomeIcon(name: .check), for: .normal)

  doneBtn.translatesAutoresizingMaskIntoConstraints = false
  doneBtn.widthAnchor.constraint(equalToConstant: 55).isActive = true
  doneBtn.heightAnchor.constraint(equalToConstant: 55).isActive = true
  doneBtn.leadingAnchor.constraint(equalTo: doneBtnView.leadingAnchor, constant:    doneBtnView.bounds.width / 16).isActive = true
 doneBtn.bottomAnchor.constraint(equalTo: doneBtnView.bottomAnchor, constant: doneBtnView.bounds.height / 2).isActive = true

Setting the constraints for my doneBtn programmatically I get the following:

enter image description here

The constraints for the doneBtnView are set programmatically because I want to pin it to the bottom right of its superview.


Solution

  • Your problem is that you are looking at the bounds of the other view when setting your constraint for your doneBtn, but those bounds will not have been set yet because layout has not happened. In general, it is a bad idea to combine frame/bounds and constraints.

    This can be done, but not with anchors. Try the following NSLayoutConstraints:

    NSLayoutConstraint(item: doneBtn, attribute: .leading, relatedBy: .equal,
        toItem: doneBtnView, attribute: .trailing, multiplier: 1/16, constant: 0).isActive = true
        
    NSLayoutConstraint(item: doneBtn, attribute: .top, relatedBy: .equal,
        toItem: doneBtnView, attribute: .bottom, multiplier: 1/2, constant: 0).isActive = true
    

    and then set the width and height using anchors:

    doneBtn.widthAnchor.constraint(equalToConstant: 55).isActive = true
    doneBtn.heightAnchor.constraint(equalToConstant: 55).isActive = true
    

    Note: Because we're using the .trailing and .bottom constraints to represent width and height, it might be necessary to put doneBtnView into a container view of the same size because the values will be in the coordinate system of the parent view. By making the parent view the exact same size, width will be equal to the trailing constraint, and height will be equal to the bottom constraint.