lithium
lithium

Reputation: 1302

UIButton heights and Auto Layout

In one of my ViewControllers I'm using the UIView with two buttons at the bottom. Obviously, I want to fit both of the buttons of the screen so I'm using this:

leftButton.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -16).isActive = true
rightButton.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -16).isActive = true
//16 points between the buttons
leftButton.rightAnchor.constraint(equalTo: rightButton.leftAnchor, constant: -16).isActive = true 
//right alignment for the buttons
rightButton.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -16).isActive = true 
//left padding for the left button - 16 points
leftButton.leftAnchor.constraint(greaterThanOrEqualTo: self.leftAnchor, constant: 16).isActive = true 

//rightButton can't be less wide than 1/3 of its superview
rightButton.widthAnchor.constraint(greaterThanOrEqualToConstant: widthOfTheView / 3).isActive = true

It works but what if I have a really long title for the left button? My left button became bigger in height, and it's ok. But my right button is still not very high, and it looks slightly ugly.

right button is too small

Ok, but what if I'm telling to Auto Layout that I want the right button to have the same height as the left.

rightButton.heightAnchor.constraint(equalTo: leftButton.heightAnchor, constant: 0).isActive = true

I thought it'd be a solution, but I'm getting this instead:

left button too small

Technically, their heights are equal but it's not the outcome I wanted.

I thought that, maybe, problem is in UIEdgeInsets within the button but it's not the case (I killed this line, result is the same).

I think I can't choose maximum height between two buttons and use it as a constant for constraint because their respective frames are zero at this stage.

I tried to use another UIView as container for these two buttons but result is the same.

I can solve this issue by turning-off Auto Layout of course and calculating button sizes and frames but I don't want to do that for now.

So, what am I doing wrong here?

Upvotes: 2

Views: 457

Answers (3)

Abhinav Khanduja
Abhinav Khanduja

Reputation: 116

This answer is the continuation of @DonMag's answer, if you want a handy extension to resize UIButton according to the text inside it then use the below code :-

extension UIButton {
func wrapContentHeight(constantValue : CGFloat) -> CGFloat {
    self.titleLabel?.text = self.title(for: .normal)

    self.titleLabel?.numberOfLines = 0
    self.titleLabel?.frame.size.width = self.frame.width
    self.titleLabel?.lineBreakMode = .byWordWrapping
    self.superview?.layoutIfNeeded()

    let height = self.titleEdgeInsets.top + self.titleEdgeInsets.bottom + (self.titleLabel?.frame.height ?? 45)
    if height < constantValue {return constantValue}
    return height
}

and you can apply the above extention to button's height constraint like below:-

btnHeight.constant = selectionBtn.wrapContentHeight(constantValue: 45)

Upvotes: 2

DonMag
DonMag

Reputation: 77423

This can be done very easily with a UIStackView, setting

.axis = .horizontal
.alignment = .fill
.distribution = .fillEqually
.spacing = 12

enter image description here

Automatically adjusts on size change:

enter image description here

Here is the code:

class MultilineRoundedButton: UIButton {

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        commonInit()
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }

    func commonInit() -> Void {
        self.titleLabel?.numberOfLines = 0
        self.titleLabel?.textAlignment = .center
        self.setContentHuggingPriority(UILayoutPriority.defaultLow + 1, for: .vertical)
        self.setContentHuggingPriority(UILayoutPriority.defaultLow + 1, for: .horizontal)
        self.layer.cornerRadius = 8
    }

    override var intrinsicContentSize: CGSize {
        let size = self.titleLabel!.intrinsicContentSize
        return CGSize(width: size.width + contentEdgeInsets.left + contentEdgeInsets.right, height: size.height + contentEdgeInsets.top + contentEdgeInsets.bottom)
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        titleLabel?.preferredMaxLayoutWidth = self.titleLabel!.frame.size.width
    }
}

class TwoButtonsView: UIView {

    let theStackView: UIStackView = {
        let v = UIStackView()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.axis = .horizontal
        v.alignment = .fill
        v.distribution = .fillEqually
        v.spacing = 16
        return v
    }()

    let leftButton: MultilineRoundedButton = {
        let v = MultilineRoundedButton()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.setTitle("Really (really!) long first button title", for: .normal)
        v.backgroundColor = UIColor(red: 184.0 / 255.0, green: 233.0 / 255.0, blue: 133.0 / 255.0, alpha: 1.0)
        return v
    }()

    let rightButton: MultilineRoundedButton = {
        let v = MultilineRoundedButton()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.setTitle("Second title", for: .normal)
        v.backgroundColor = UIColor(red:  74.0 / 255.0, green: 143.0 / 255.0, blue: 226.0 / 255.0, alpha: 1.0)
        return v
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        commonInit()
    }

    func commonInit() -> Void {

        leftButton.titleLabel?.font = UIFont.systemFont(ofSize: 24.0, weight: .bold)
        rightButton.titleLabel?.font = leftButton.titleLabel?.font

        addSubview(theStackView)
        theStackView.addArrangedSubview(leftButton)
        theStackView.addArrangedSubview(rightButton)

        NSLayoutConstraint.activate([
            theStackView.topAnchor.constraint(equalTo: topAnchor),
            theStackView.bottomAnchor.constraint(equalTo: bottomAnchor),
            theStackView.leadingAnchor.constraint(equalTo: leadingAnchor),
            theStackView.trailingAnchor.constraint(equalTo: trailingAnchor),
            ])

    }

}

class TwoButtonViewController: UIViewController {

    let buttonsView: TwoButtonsView = {
        let v = TwoButtonsView()
        v.translatesAutoresizingMaskIntoConstraints = false
        return v
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(buttonsView)

        NSLayoutConstraint.activate([
            buttonsView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20.0),
            buttonsView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20.0),
            buttonsView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20.0),
            ])
    }

}

Upvotes: 2

Abu Ul Hassan
Abu Ul Hassan

Reputation: 1396

change below line

leftButton.rightAnchor.constraint(equalTo: rightButton.leftAnchor, constant: -16).isActive = true

To

leftButton.rightAnchor.constraint(greaterThanOrEqualTo: rightButton.leftAnchor, constant: -16).isActive = true

you can set

rightButton.heightAnchor.constraint(equalTo: leftButton.heightAnchor).isActive = true

Currently leftButton's right anchor is trying to Fullfill -16 constant and due to that left button is getting stretched using greaterOrEqual will increase distancr among buttons but sizes will be good to go.

Upvotes: -1

Related Questions