Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiline UIButton and autolayout

I had the same problem where I wanted my button to grow along with its title. I had to sublcass the UIButton and its intrinsicContentSize so that it returns the intrinsic size of the label.

- (CGSize)intrinsicContentSize
{
    return self.titleLabel.intrinsicContentSize;
}

Since the UILabel is multiline, its intrinsicContentSize is unknown and you have to set its preferredMaxLayoutWidth See objc.io article about that

- (void)layoutSubviews
{
    [super layoutSubviews];
    self.titleLabel.preferredMaxLayoutWidth = self.titleLabel.frame.size.width;
    [super layoutSubviews];
}

The rest of the layout should work. If you set your both button having equal heights, the other one will grow to. The complete button looks like this

@implementation TAButton

- (instancetype)initWithCoder:(NSCoder *)coder
{
    self = [super initWithCoder:coder];
    if (self) {
        self.titleLabel.numberOfLines = 0;
        self.titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
    }
    return self;
}

- (CGSize)intrinsicContentSize
{
    return self.titleLabel.intrinsicContentSize;
}

- (void)layoutSubviews
{
    [super layoutSubviews];
    self.titleLabel.preferredMaxLayoutWidth = self.titleLabel.frame.size.width;
    [super layoutSubviews];
}

@end

Swift 4.1.2 Version based on @Jan answer.

import UIKit

class MultiLineButton: UIButton {

    // MARK: - Init

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

        self.commonInit()
    }

    private func commonInit() {
        self.titleLabel?.numberOfLines = 0
        self.titleLabel?.lineBreakMode = .byWordWrapping
    }

    // MARK: - Overrides

    override var intrinsicContentSize: CGSize {
        get {
             return titleLabel?.intrinsicContentSize ?? CGSize.zero
        }
    }

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

}

This respects content edge insets and worked for me:

class MultilineButton: UIButton {

    func setup() {
        self.titleLabel?.numberOfLines = 0
        self.setContentHuggingPriority(UILayoutPriorityDefaultLow + 1, for: .vertical)
        self.setContentHuggingPriority(UILayoutPriorityDefaultLow + 1, for: .horizontal)
    }

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

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

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

A simple solution working for me: make the multiline button to respect its title height in Swift 4.2 by adding a constraint for the button's height based on its title label's height:

let height = NSLayoutConstraint(item: multilineButton,
                                attribute: .height,
                                relatedBy: .equal,
                                toItem: multilineButton.titleLabel,
                                attribute: .height,
                                multiplier: 1,
                                constant: 0)
multilineButton.addConstraint(height)

add the missing constraints:

if let label = button.titleLabel {

    button.addConstraint(NSLayoutConstraint(item: label, attribute: .top, relatedBy: .equal, toItem: button, attribute: .top, multiplier: 1.0, constant: 0.0))
    button.addConstraint(NSLayoutConstraint(item: label, attribute: .bottom, relatedBy: .equal, toItem: button, attribute: .bottom, multiplier: 1.0, constant: 0.0))
}