iOS: UIButton resize according to text length

IosUibutton

Ios Problem Overview


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];

Ios Solutions


Solution 1 - Ios

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.

Solution 2 - Ios

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;

Solution 3 - Ios

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];

Solution 4 - Ios

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)

Solution 5 - Ios

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

Solution 6 - Ios

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;

Solution 7 - Ios

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

Solution 8 - Ios

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)

Solution 9 - Ios

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.

Solution 10 - Ios

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

Solution 11 - Ios

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

Solution 12 - Ios

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

Solution 13 - Ios

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

Solution 14 - Ios

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

Solution 15 - Ios

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionOh Danny BoyView Question on Stackoverflow
Solution 1 - IosDan RayView Answer on Stackoverflow
Solution 2 - IosDaniel WoodView Answer on Stackoverflow
Solution 3 - Iosabc123View Answer on Stackoverflow
Solution 4 - IosJuan BoeroView Answer on Stackoverflow
Solution 5 - IosTadView Answer on Stackoverflow
Solution 6 - IosLoganView Answer on Stackoverflow
Solution 7 - Iosnator333View Answer on Stackoverflow
Solution 8 - IosHashem AboonajmiView Answer on Stackoverflow
Solution 9 - IosBartłomiej SemańczykView Answer on Stackoverflow
Solution 10 - IosMobileDevView Answer on Stackoverflow
Solution 11 - Iosraider33View Answer on Stackoverflow
Solution 12 - IoselamauntView Answer on Stackoverflow
Solution 13 - IosShaluginView Answer on Stackoverflow
Solution 14 - IosChuckZHBView Answer on Stackoverflow
Solution 15 - IoscodeburnView Answer on Stackoverflow