Multiline UIButton and autolayout

I have created a view controller that looks like this:

enter image description here

  • UIButton selection color
  • How to get back the UIButton border in iOS 7?
  • Is there anyway to add same scroll menubar at the navigation bar?
  • Xcode 8 - Some buttons border removed
  • How can I save and load the alpha values of a UIButton in an app?
  • ABTableViewCell - Adding a UIButton
  • I want the two top buttons to always have 20 points between themselves and the left/right edges of the whole view. They should always have the same width too. I have created the constraints for all of this and it works exactly how I want it to. The problem is the vertical constraints. The buttons should always be 20 points beneath the top edge. They should have the same height. However, autolayout doesn’t respect that the left label needs two lines to fit all its text, so the result looks like this:

    enter image description here

    I want it to look like in the first picture. I can’t add constant height constraints to the buttons because when the app runs on iPad, only one line is needed and it would be wasteful to have extra space then.

    In viewDidLoad I tried this:

    - (void)viewDidLoad
    {
        [super viewDidLoad];
        self.leftButton.titleLabel.preferredMaxLayoutWidth = (self.view.frame.size.width - 20.0 * 3) / 2.0;
        self.rightButton.titleLabel.preferredMaxLayoutWidth = (self.view.frame.size.width - 20.0 * 3) / 2.0;
    }
    

    But that did not change anyhting at all.

    The question: How do I make autolayout respect that the left button needs two lines?

    6 Solutions Collect From Internet About “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 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 func intrinsicContentSize() -> CGSize {
           return titleLabel?.intrinsicContentSize() ?? CGSizeZero
       }
    
       override func layoutSubviews() {
           super.layoutSubviews()
           titleLabel?.preferredMaxLayoutWidth = titleLabel?.frame.size.width ?? 0
           super.layoutSubviews()
       }
    
    }
    

    Have you tried using this:

    self.leftButton.titleLabel.textAlignment = NSTextAlignmentCenter;
    self.leftButton.titleLabel.lineBreakMode = NSLineBreakByWordWrapping | NSLineBreakByTruncatingTail;
    self.leftButton.titleLabel.numberOfLines = 0;
    

    UPDATED Swift/Swift 2.0 version again based on @Jan’s answer

    @IBDesignable
    class MultiLineButton:UIButton {
    
      //MARK: -
      //MARK: Setup
      func setup () {
        self.titleLabel?.numberOfLines = 0
    
        //The next two lines are essential in making sure autolayout sizes us correctly
        self.setContentHuggingPriority(UILayoutPriorityDefaultLow+1, forAxis: .Vertical) 
        self.setContentHuggingPriority(UILayoutPriorityDefaultLow+1, forAxis: .Horizontal)
      }
    
      //MARK:-
      //MARK: Method overrides
      required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
      }
    
      override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
      }
    
      override func intrinsicContentSize() -> CGSize {
        return self.titleLabel!.intrinsicContentSize()
      }
    
      override func layoutSubviews() {
        super.layoutSubviews()
        titleLabel?.preferredMaxLayoutWidth = self.titleLabel!.frame.size.width
      }
    }
    

    tweaks for Swift 3.1

    intrisicContentSize is a property instead of a function

    override var intrinsicContentSize: CGSize {
        return self.titleLabel!.intrinsicContentSize
    }
    

    Complete class in Swift 3 – based on @Jan, @Quantaliinuxite and @matt bezark:

    @IBDesignable
    class MultiLineButton:UIButton {
    
        //MARK: -
        //MARK: Setup
        func setup () {
            self.titleLabel?.numberOfLines = 0
    
            //The next two lines are essential in making sure autolayout sizes us correctly
            self.setContentHuggingPriority(UILayoutPriorityDefaultLow+1, for: .vertical)
            self.setContentHuggingPriority(UILayoutPriorityDefaultLow+1, for: .horizontal)
        }
    
        //MARK:-
        //MARK: Method overrides
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            setup()
        }
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            setup()
        }
    
        override var intrinsicContentSize: CGSize {
            return self.titleLabel!.intrinsicContentSize
        }
    
        override func layoutSubviews() {
            super.layoutSubviews()
            titleLabel?.preferredMaxLayoutWidth = self.titleLabel!.frame.size.width
        }
    }