148

In interface builder, holding Command + = will resize a button to fit its text. I was wondering if this was possible to do programmatically before the button was added to the view.

UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button.titleLabel setFont:[UIFont fontWithName:@"Arial-BoldMT" size:12]];
[button addTarget:self action:@selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];
// I need to know the width needed to accomodate NSStringVariable
[button setTitle:NSStringVariable forState:UIControlStateNormal]; 
// So that I can set the width property of the button before addSubview
[button setFrame:CGRectMake(10, 0, width, fixedHeight)];
[subNavigation addSubview:button];
buildsucceeded
  • 4,203
  • 4
  • 34
  • 72
Oh Danny Boy
  • 4,857
  • 8
  • 56
  • 88
  • 1
    **Note for this very old question:** nowadays (2017), just set the constraint as "greater than or equal" and it does it all. (Notice answer below by Tad.) – Fattie Dec 27 '16 at 17:13
  • @Fattie A "*greater than or equal*" constraint is **not enough** for compatibility with AutoLayout. Nowadays (2017+) one should either wrap the button inside something that resizes properly (Bartłomiej Semańczyk answer), or one should subclass the UIButton, as in https://stackoverflow.com/a/50575588/1033581 – Cœur May 29 '18 at 02:48
  • hi @Cœur - there are many subtleties to autolayout. It's possible you're thinking of a slightly different situation? However, fortunately, you are totally wrong - **just try it** !! Just put a UIButton on a view controller. obviously set the horizontal and vertical center. And then add a >= 100 width constraint. it works perfectly in the latest iOS. – Fattie May 29 '18 at 10:45
  • 1
    (For anyone new to autolayout googling here ........... just TBC you don't have to add anything at all, and UIButton (also UILabel, etc) will resize to width automatically with text changes. Just constraint properly the H/V position.) – Fattie May 29 '18 at 10:46
  • @Fattie [demonstration](https://github.com/Coeur/autolayoutButton). In particular, at runtime, it behaves like that: https://github.com/Coeur/autolayoutButton/blob/master/buttonSelfSizing/Screen%20Shot%20at%20runtime.png – Cœur May 29 '18 at 11:50
  • hi @Cœur - I'm sorry, we may have a language barrier. UILabel is **completely, 100%, self-sizing. this is a basic of autolayout**. I'll put in some images showing. It has undefined behavior when more than one line of text (the natural behavior currently seems to be it just clips if the solver reaches the maximum possible width). If you are using UIButton to make very long paragraphs of text clickable, I'm sure you agree that's a poor idea but sure you'd have to rebuild the class. – Fattie May 29 '18 at 12:33

17 Answers17

131

In UIKit, there are additions to the NSString class to get from a given NSString object the size it'll take up when rendered in a certain font.

Docs was here. Now it's here under Deprecated.

In short, if you go:

CGSize stringsize = [myString sizeWithFont:[UIFont systemFontOfSize:14]]; 
//or whatever font you're using
[button setFrame:CGRectMake(10,0,stringsize.width, stringsize.height)];

...you'll have set the button's frame to the height and width of the string you're rendering.

You'll probably want to experiment with some buffer space around that CGSize, but you'll be starting in the right place.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Dan Ray
  • 21,623
  • 6
  • 63
  • 87
104

The way to do this in code is:

[button sizeToFit];

If you are subclassing and want to add extra rules you can override:

- (CGSize)sizeThatFits:(CGSize)size;
Daniel Wood
  • 4,487
  • 3
  • 38
  • 36
  • 5
    sizeToFit works perfectly on standard controls like UIButton or UILabel. If you are doing it on a custom UIView you will need to implement `-(CGSize)sizeThatFits:(CGSize)size`. – Cameron Lowell Palmer Jul 15 '13 at 11:50
  • 4
    It only works if you put it after setting the text, otherwise there is not enough information about how big it should be: `[button setTitle:@"Your text here" forState:UIControlStateNormal];` `[button sizeToFit];` Also if you update text later, do not forget to call that again. – Juan Boero Aug 13 '15 at 22:13
  • Make sure that you set text only that way: [myButton setTitle:@"my title" forState:UIControlStateNormal]; For me it did not work till I repeated that statement. – Darius Miliauskas Jun 08 '16 at 14:30
  • 13
    `sizeToFit()` doesn't respect title edge insets. – kelin Apr 06 '17 at 10:56
37

If your button was made with Interface Builder, and you're changing the title in code, you can do this:

[self.button setTitle:@"Button Title" forState:UIControlStateNormal];
[self.button sizeToFit];
estemendoza
  • 3,023
  • 5
  • 31
  • 51
abc123
  • 8,043
  • 7
  • 49
  • 80
25

To accomplish this using autolayout, try setting a variable width constraint:

Width Constraint

You may also need to adjust your Content Hugging Priority and Content Compression Resistance Priority to get the results you need.


UILabel is completely automatically self-sizing:

This UILabel is simply set to be centered on the screen (two constraints only, horizontal/vertical):

It changes widths totally automatically:

You do not need to set any width or height - it's totally automatic.

enter image description here

enter image description here

Notice the small yellow squares are simply attached ("spacing" of zero). They automatically move as the UILabel resizes.

Adding a ">=" constraint sets a minimum width for the UILabel:

enter image description here

Fattie
  • 27,874
  • 70
  • 431
  • 719
Tad
  • 4,668
  • 34
  • 35
24

Swift:

The important thing here is when will you call the .sizeToFit() , it should be after setting the text to display so it can read the size needed, if you later update text, then call it again.

var button = UIButton()
button.setTitle("Length of this text determines size", forState: UIControlState.Normal)
button.sizeToFit()
self.view.addSubview(button)
Juan Boero
  • 6,281
  • 1
  • 44
  • 62
18

If you want to resize the text as opposed to the button, you can use ...

button.titleLabel.adjustsFontSizeToFitWidth = YES;
button.titleLabel.minimumScaleFactor = .5;
// The .5 value means that I will let it go down to half the original font size 
// before the texts gets truncated

// note, if using anything pre ios6, you would use I think
button.titleLabel.minimumFontSize = 8;
Erik van der Neut
  • 2,245
  • 2
  • 22
  • 21
Logan
  • 52,262
  • 20
  • 99
  • 128
14

Swift 4.2

Thank god, this solved. After setting a text to the button, you can retrieve intrinsicContentSize which is the natural size from an UIView (the official document is here). For UIButton, you can use it like below.

button.intrinsicContentSize.width

For your information, I adjusted the width to make it look properly.

button.frame = CGRect(fx: xOffset, y: 0.0, width: button.intrinsicContentSize.width + 18, height: 40)

Simulator

UIButtons with intrinsicContentSize

Source: https://riptutorial.com/ios/example/16418/get-uibutton-s-size-strictly-based-on-its-text-and-font

nator333
  • 141
  • 1
  • 5
  • 1
    Could you give a bit more context? How does your line of code relate to the code in the question? Could you also summarize what the code you suggest does? Only providing the link as an explanation is not the right format for this site (see https://meta.stackexchange.com/a/8259/266197 for reasons), but if you would add some explanation and context, your answer would become more valuable! – NOhs Jun 20 '19 at 23:10
11

sizeToFit doesn't work correctly. instead:

myButton.size = myButton.sizeThatFits(CGSize.zero)

you also can add contentInset to the button:

myButton.contentEdgeInsets = UIEdgeInsetsMake(8, 8, 4, 8)

Hashem Aboonajmi
  • 13,077
  • 8
  • 66
  • 75
  • From Apple docs regarding sizeToFit: // calls sizeThatFits: with current view bounds and changes bounds size. So it works correctly you just have to set it afterwards you set the text. – Markus Apr 24 '17 at 19:06
6

Simply:

  1. Create UIView as wrapper with auto layout to views around.
  2. Put UILabel inside that wrapper. Add constraints that will stick tyour label to edges of wrapper.
  3. Put UIButton inside your wrapper, then simple add the same constraints as you did for UILabel.
  4. Enjoy your autosized button along with text.
Bartłomiej Semańczyk
  • 59,234
  • 49
  • 233
  • 358
6

For some reason, func sizeToFit() does not work for me. My set up is I am using a button inside a UITableViewCell and I am using auto layout.

What worked for me is:

  1. get the width constraint
  2. get the intrinsicContentSize width because according the this document auto layout is not aware of the intrinsicContentSize. Set the width constraint to the intrinsicContentSize width

enter image description here

Here're two titles from the Debug View Hierachry enter image description here

enter image description here

MobileDev
  • 3,750
  • 4
  • 32
  • 35
3

I have some additional needs related to this post that sizeToFit does not solve. I needed to keep the button centered in it's original location, regardless of the text. I also never want the button to be larger than its original size.

So, I layout the button for its maximum size, save that size in the Controller and use the following method to size the button when the text changes:

+ (void) sizeButtonToText:(UIButton *)button availableSize:(CGSize)availableSize padding:(UIEdgeInsets)padding {    

     CGRect boundsForText = button.frame;

    // Measures string
    CGSize stringSize = [button.titleLabel.text sizeWithFont:button.titleLabel.font];
    stringSize.width = MIN(stringSize.width + padding.left + padding.right, availableSize.width);

    // Center's location of button
    boundsForText.origin.x += (boundsForText.size.width - stringSize.width) / 2;
    boundsForText.size.width = stringSize.width;
    [button setFrame:boundsForText];
 }
Dhaval Bhadania
  • 3,090
  • 1
  • 20
  • 35
raider33
  • 1,633
  • 1
  • 19
  • 21
2

This button class with height autoresizing for text (for Xamarin but can be rewritten for other language)

[Register(nameof(ResizableButton))]
public class ResizableButton : UIButton
{
    NSLayoutConstraint _heightConstraint;
    public bool NeedsUpdateHeightConstraint { get; private set; } = true;

    public ResizableButton(){}

    public ResizableButton(UIButtonType type) : base(type){}

    public ResizableButton(NSCoder coder) : base(coder){}

    public ResizableButton(CGRect frame) : base(frame){}

    protected ResizableButton(NSObjectFlag t) : base(t){}

    protected internal ResizableButton(IntPtr handle) : base(handle){}

    public override void LayoutSubviews()
    {
        base.LayoutSubviews();
        UpdateHeightConstraint();
        InvalidateIntrinsicContentSize();
    }

    public override void SetTitle(string title, UIControlState forState)
    {
        NeedsUpdateHeightConstraint = true;
        base.SetTitle(title, forState);
    }

    private void UpdateHeightConstraint()
    {
        if (!NeedsUpdateHeightConstraint)
            return;
        NeedsUpdateHeightConstraint = false;
        var labelSize = TitleLabel.SizeThatFits(new CGSize(Frame.Width - TitleEdgeInsets.Left - TitleEdgeInsets.Right, float.MaxValue));

        var rect = new CGRect(Frame.X, Frame.Y, Frame.Width, labelSize.Height + TitleEdgeInsets.Top + TitleEdgeInsets.Bottom);

        if (_heightConstraint != null)
            RemoveConstraint(_heightConstraint);

        _heightConstraint = NSLayoutConstraint.Create(this, NSLayoutAttribute.Height, NSLayoutRelation.Equal, 1, rect.Height);
        AddConstraint(_heightConstraint);
    }

     public override CGSize IntrinsicContentSize
     {
         get
         {
             var labelSize = TitleLabel.SizeThatFits(new CGSize(Frame.Width - TitleEdgeInsets.Left - TitleEdgeInsets.Right, float.MaxValue));
             return new CGSize(Frame.Width, labelSize.Height + TitleEdgeInsets.Top + TitleEdgeInsets.Bottom);
         }
     }
}
Felipe Augusto
  • 7,733
  • 10
  • 39
  • 73
elamaunt
  • 19
  • 1
1

To ultimately solve this, though need to make it more modern (ie. button config for iOS15 and above), just literally subclass the UIButton, like so:

class AdaptableSizeButton: UIButton {
    override var intrinsicContentSize: CGSize {
        let labelSize = titleLabel?.sizeThatFits(CGSize(width: frame.size.width, height: CGFloat.greatestFiniteMagnitude)) ?? .zero
        let desiredButtonSize = CGSize(width: labelSize.width + titleEdgeInsets.left + titleEdgeInsets.right, height: labelSize.height + titleEdgeInsets.top + titleEdgeInsets.bottom)
        
        return desiredButtonSize
    }
}

Try it out!

import UIKit

class ViewController: UIViewController {

    let button = AdaptableSizeButton()
    let v = UIView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.translatesAutoresizingMaskIntoConstraints = false

        button.setTitle("Ako ay may lobo lorem", for: .normal)
        button.titleLabel?.textAlignment = .center
        button.titleLabel?.lineBreakStrategy = .pushOut
        button.titleEdgeInsets = .init(top: 0, left: 16, bottom: 0, right: 16)
        button.backgroundColor = .red
        
        view.addSubview(button)
        button.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            button.heightAnchor.constraint(equalToConstant: 50),
            button.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            button.centerXAnchor.constraint(equalTo: view.centerXAnchor)
        ])
        
        DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3)) {
            print("BOOM")
            self.button.setTitle("aaaa bla  bla lumipad sa langit bbbb", for: .normal)
        }
    }
}
Glenn Posadas
  • 12,555
  • 6
  • 54
  • 95
0

To be honest I think that it's really shame that there is no simple checkbox in storyboard to say that you want to resize buttons to accommodate the text. Well... whatever.

Here is the simplest solution using storyboard.

  1. Place UIView and put constraints for it. Example: enter image description here
  2. Place UILabel inside UIView. Set constraints to attach it to edges of UIView.

  3. Place your UIButton inside UIView. Set the same constraints to attach it to the edges of UIView.

  4. Set 0 for UILabel's number of lines. enter image description here

  5. Set up the outlets.

    @IBOutlet var button: UIButton!
    @IBOutlet var textOnTheButton: UILabel!
  1. Get some long, long, long title.

    let someTitle = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
  1. On viewDidLoad set the title both for UILabel and UIButton.

    override func viewDidLoad() {
        super.viewDidLoad()
        textOnTheButton.text = someTitle
        button.setTitle(someTitle, for: .normal)
        button.titleLabel?.numberOfLines = 0
    }
  1. Run it to make sure that button is resized and can be pressed.

enter image description here

Shalugin
  • 1,092
  • 2
  • 10
  • 15
0

As sizeToFit will ignore titleEdgeInsets settings. In my case, the button title is fixed text, so I use autolayout to give each button a width constraint.

And I think if dynamic text length, you need to get the string size first and update the button constraint in viewDidLayoutSubview.

    lazy var importButton: UIButton = {
        let btn = UIButton()
        btn.translatesAutoresizingMaskIntoConstraints = false
        btn.setTitle("Import", for: .normal)
        btn.titleLabel?.font = .systemFont(ofSize: 18, weight: .bold)
        btn.setTitleColor(.white, for: .normal)
        btn.titleEdgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
        btn.backgroundColor = .myBlue
        btn.layer.cornerRadius = 8
        btn.clipsToBounds = true
        btn.addTarget(self, action: #selector(importButtonTapped), for: .touchUpInside)
        return btn
    }()
    
    lazy var stitchButton: UIButton = {
        let btn = UIButton()
        btn.translatesAutoresizingMaskIntoConstraints = false
        btn.setTitle("Stitch", for: .normal)
        btn.titleLabel?.font = .systemFont(ofSize: 18, weight: .bold)
        btn.setTitleColor(.white, for: .normal)
        btn.titleEdgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
        btn.backgroundColor = .myGreen
        btn.layer.cornerRadius = 8
        btn.clipsToBounds = true
        btn.addTarget(self, action: #selector(stitchButtonTapped), for: .touchUpInside)
        return btn
    }()

    func setViews() {
        title = "Image Stitcher"
        view.backgroundColor = .systemBackground
        
        view.addSubview(importButton)
        view.addSubview(stitchButton)

        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            NSLayoutConstraint(item: importButton, attribute: .centerX, relatedBy: .equal, toItem: g, attribute: .trailing, multiplier: 0.3, constant: 0),
            importButton.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -50),
            importButton.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.25),
            importButton.heightAnchor.constraint(equalTo: importButton.widthAnchor, multiplier: 0.5),
            
            NSLayoutConstraint(item: stitchButton, attribute: .centerX, relatedBy: .equal, toItem: g, attribute: .trailing, multiplier: 0.7, constant: 0),
            stitchButton.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -50),
            stitchButton.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.25),
            stitchButton.heightAnchor.constraint(equalTo: stitchButton.widthAnchor, multiplier: 0.5),
        ])
    }

enter image description here

Zhou Haibo
  • 1,681
  • 1
  • 12
  • 32
0

In my case sizeToFit() didn't work , but I was using UIFont.preferredFont(forTextStyle: _) for UIButton title label text. Setting adjustsFontSizeToFitWidth fixed the issue.

button.setTitle(" title ", for: .normal)
button.titleLabel?.font = UIFont.preferredFont(forTextStyle: .body)
button.titleLabel?.adjustsFontSizeToFitWidth = true
Udaya Sri
  • 2,372
  • 2
  • 25
  • 35
-3

In Xcode 4.5 and above, this can now be done by using 'Auto-layouting / Constraints'.

Major advantages are that:

  1. You don't need to programmatically set frames at all!
  2. If done right, you don't need to bother about resetting frames for orientation changes.
  3. Also, device changes needn't bother you (read, no need to code separately for different screen sizes).

A few disadvantages:

  1. Not backward compatible - works only for iOS 6 and above.
  2. Need to get familiarised (but will save time later on).

Coolest thing is we get to focus on declaring an intent such as:

  • I want these two buttons to be of the same width; or
  • I need this view to be vertically centered and extend to a max entent of 10 pts from the superview's edge; or even,
  • I want this button/label to resize according to the label it is displaying!

Here is a simple tutorial to get introduced to auto-layouting.

For a more details.

It takes some time at first, but it sure looks like it will be well worth the effort.

Cœur
  • 37,241
  • 25
  • 195
  • 267
codeburn
  • 1,994
  • 17
  • 20
  • 3
    I nearly missed this answer - it really needs plenty more up votes. With this solution one can avoid embedding font facenames and sizes or iOS 7-only calls. I simply set the constraint of the side I wanted my UIButton to extend on and that's it. – Carl Mar 04 '14 at 09:55
  • Almost a perfect solution, unfortunately it doesn't work if you use titleEdgeInsets :/ – Zoltán Jul 29 '14 at 10:24
  • 146
    This is a poor answer - it details the merits of AutoLayout itself, rather than how AutoLayout can be applied in this situation to solve the asker's problem. – mattsven Apr 20 '15 at 16:15
  • 5
    **I was wondering if this was possible to do programmatically** says the answer... – Juan Boero Aug 13 '15 at 22:08
  • @JuanPabloBoeroAlvarez Auto Layout constraints can be specified in the Interface Builder and/or programmatically. – Slipp D. Thompson Jun 03 '16 at 19:37
  • Answer is completely out of date. Fortunately today just follow Tad's answer. – Fattie Dec 27 '16 at 17:14