ChatGPT解决这个技术问题 Extra ChatGPT

iOS: UIButton resize according to text length

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];
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 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 stackoverflow.com/a/50575588/1033581
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.
(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 demonstration. In particular, at runtime, it behaves like that: github.com/Coeur/autolayoutButton/blob/master/buttonSelfSizing/…

C
Cœur

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.


Looks like iOS default padding is 12px, 8px.
sizeWithFont is deprecated in iOS 7.0. Use sizeWithAttributes: instead
I strongly suggest not playing with frames anymore, but ONLY with constraints instead.
@GilSand Is there a way to use interface builder for this?
Yes, a google search should give you plenty of useful results
D
Daniel Wood

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;

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.
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.
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.
sizeToFit() doesn't respect title edge insets.
e
estemendoza

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

And you can to that even if your button was made with code.
J
Juan Boero

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)

F
Fattie

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

https://i.stack.imgur.com/iM1Vk.png

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.

https://i.stack.imgur.com/Us3b2.png

https://i.stack.imgur.com/pVfMC.png

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:

https://i.stack.imgur.com/raRNQ.png


E
Erik van der Neut

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;

* adjustsFontSizeToFitWidth
H
Hashem Aboonajmi

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)


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.
It just works. :) Thanks
n
nator333

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


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 meta.stackexchange.com/a/8259/266197 for reasons), but if you would add some explanation and context, your answer would become more valuable!
B
Bartłomiej Semańczyk

Simply:

Create UIView as wrapper with auto layout to views around. Put UILabel inside that wrapper. Add constraints that will stick tyour label to edges of wrapper. Put UIButton inside your wrapper, then simple add the same constraints as you did for UILabel. Enjoy your autosized button along with text.


Does this play well with accessibility?
M
MobileDev

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:

get the width constraint 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

https://i.stack.imgur.com/IoblM.png

https://i.stack.imgur.com/Jo445.png

https://i.stack.imgur.com/D8UKk.png


Kudos, this worked a charm for me to resize a button within a stackview
D
Dhaval Bhadania

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

F
Felipe Augusto

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

S
Shalugin

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.

Place UIView and put constraints for it. Example: Place UILabel inside UIView. Set constraints to attach it to edges of UIView. Place your UIButton inside UIView. Set the same constraints to attach it to the edges of UIView. Set 0 for UILabel's number of lines. Set up the outlets.

    @IBOutlet var button: UIButton!
    @IBOutlet var textOnTheButton: UILabel!

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."

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
    }

Run it to make sure that button is resized and can be pressed.

https://i.stack.imgur.com/9qjfz.png


C
ChuckZHB

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),
        ])
    }

https://i.stack.imgur.com/AJfza.jpg


C
Cœur

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

Major advantages are that:

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

A few disadvantages:

Not backward compatible - works only for iOS 6 and above. 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.


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.
Almost a perfect solution, unfortunately it doesn't work if you use titleEdgeInsets :/
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.
I was wondering if this was possible to do programmatically says the answer...
@JuanPabloBoeroAlvarez Auto Layout constraints can be specified in the Interface Builder and/or programmatically.