UIButton that resizes to fit its titleLabel
Solution 1
I've gotten this to work, but you have to use a custom button, not a system type. Give the button both width and height constraints, and make an IBOutlet to the height constraint (heightCon in my code) so you can adjust it in code.
- (void)viewDidLoad {
[super viewDidLoad];
self.button.titleLabel.numberOfLines = 0;
self.button.titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
[self.button setTitle:@"A real real real real real real real real long long name." forState:UIControlStateNormal];
[self.button addTarget:self action:@selector(doStuff:) forControlEvents:UIControlEventTouchUpInside];
self.button.backgroundColor = [UIColor redColor];
self.button.titleLabel.backgroundColor = [UIColor blueColor];
[self.button layoutIfNeeded]; // need this to update the button's titleLabel's size
self.heightCon.constant = self.button.titleLabel.frame.size.height;
}
After Edit:
I found that you can also do this more simply, and with a system button if you make a subclass, and use this code,
@implementation RDButton
-(CGSize)intrinsicContentSize {
return CGSizeMake(self.frame.size.width, self.titleLabel.frame.size.height);
}
The overridden intrinsicContentSize method is called when you set the title. You shouldn't set a height constraint in this case.
Solution 2
Swift 4.x version of Kubba's answer:
Need to Update Line Break as Clip/WordWrap/ in Interface builder to corresponding buttons.
class ResizableButton: UIButton {
override var intrinsicContentSize: CGSize {
let labelSize = titleLabel?.sizeThatFits(CGSize(width: frame.width, height: .greatestFiniteMagnitude)) ?? .zero
let desiredButtonSize = CGSize(width: labelSize.width + titleEdgeInsets.left + titleEdgeInsets.right, height: labelSize.height + titleEdgeInsets.top + titleEdgeInsets.bottom)
return desiredButtonSize
}
}
Solution 3
Got same issue. It happens only if UIButton
's titleLabel
has more than one line. Is it a bug in UIKit?
My Swift solution:
class ResizableButton: 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
}
}
Solution 4
I struggled with this for a while and ended up making it work by subclassing UIButton and adding these two functions
class GoalsButton: UIButton {
override var intrinsicContentSize: CGSize {
return self.titleLabel!.intrinsicContentSize
}
// Whever the button is changed or needs to layout subviews,
override func layoutSubviews() {
super.layoutSubviews()
titleLabel?.preferredMaxLayoutWidth = self.titleLabel!.frame.size.width
}
}
Solution 5
I am away from my computer so I can't add the code right now, but I have found a workaround of this before.
What you can do is create a UILabel
and add a UITapGestureRecognizer
to the label. Do whatever you want for the button's action by handling the tap event. And also make sure that you enable user interactions on the UILabel
.
This label will now behave as a auto resizing button.
Darren
Updated on July 09, 2022Comments
-
Darren almost 2 years
I have a
UIButton
that I add to my view controller's view in a storyboard. I add centering constraints to position it and leading space constraints to limit its width. In code I add:self.button.titleLabel.numberOfLines = 0; self.button.titleLabel.lineBreakMode = NSLineBreakByWordWrapping; [self.button setTitle:@"A real real real real real real real real long long name." forState:UIControlStateNormal]; self.button.backgroundColor = [UIColor redColor]; self.button.titleLabel.backgroundColor = [UIColor blueColor];
The result is shown below:
I want the button to size to its content. How can I do this?
I've tried
[self.button sizeToFit];
and I've tried setting the content hugging and compression resistance autolayout constraints priorities to required.
I've also tried explicitly setting the
contentEdgeInsets
andtitleEdgeInsets
toUIEdgeInsetsZero
and callinginvalidateIntrinsicContentSize
.I've also noticed that if I place newline characters in the title string, the button does seem to resize to fit its content.
I'm running on Xcode 6 and iOS 8 on the iPhone 6 Simulator.
-
Darren over 9 yearsThis works exactly the way I want in terms of sizing, and it's what I tried first, but I want the text highlighting behaviour of a button when the user touches it.
-
Vlad about 8 yearsHey Darren, you can set both
UILabel.textColor
andUILabel.highlightedTextColor
properties. Then later you can updateUILabel.highlighted
property in order to change text color (for instance depends of UITapGestureRecognizer state). Gist -
Neo about 7 yearsYou may want to consider adding some context ( An explanation ) around all of this code.
-
looseyi about 7 yearswell, as a common solution when cal the button title, we should know if the title is set by String, or NSAttributedString. so i add the value check, and cal extension. In the end, we should consider if the button also set the image to imageView, above all in consider, intrinsicContentSize should be right
-
xaphod almost 7 yearsDidn't work for me: titleLabel still exceeds the frame of the UIButton.
-
Ahmed Khedr about 6 years@xaphod - forget to set the subclass in the attribute inspector?
-
Lal Krishna about 5 yearsI added
imageView
's size andcontentEdgeInsets
too. to this code. -
Daniel Kuta about 5 yearsIt's work, but you should implement override func setTitle(_ title: String?, for state: UIControl.State) { super.setTitle(title, for: state) layoutIfNeeded() }
-
Alexander Khitev almost 5 yearsHow can we animate this change?
-
Alexander Khitev almost 5 yearsHow can we animate this change?
-
HaZe over 4 yearsJust a little completion: let with = labelSize.width + (image(for:[])?.size.width)! + titleEdgeInsets.left + titleEdgeInsets.right + imageEdgeInsets.left + imageEdgeInsets.right + contentEdgeInsets.left + contentEdgeInsets.right
-
Gerd Castan over 4 yearsIn my case, I have frame.width == 0.0. This leads to a wrong result (height of 1 line when the height of multiple lines would be correct). I had ti get a suitable width from somewhere else. So if this approach doesn't work, check the value of frame.width
-
Sergei Navka almost 4 years@AlexanderKhitev you can call
invalidateIntrinsicContentSize()
inUIView.animation
block