How do you add multi-line text to a UIButton?
-
03-07-2019 - |
Question
I have the following code...
UILabel *buttonLabel = [[UILabel alloc] initWithFrame:targetButton.bounds];
buttonLabel.text = @"Long text string";
[targetButton addSubview:buttonLabel];
[targetButton bringSubviewToFront:buttonLabel];
...the idea being that I can have multi-line text for the button, but the text is always obscured by the backgroundImage of the UIButton. A logging call to show the subviews of the button shows that the UILabel has been added, but the text itself cannot be seen. Is this a bug in UIButton or am I doing something wrong?
Solution
For iOS 6 and above, use the following to allow multiple lines:
button.titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
// you probably want to center it
button.titleLabel.textAlignment = NSTextAlignmentCenter; // if you want to
[button setTitle: @"Line1\nLine2" forState: UIControlStateNormal];
For iOS 5 and below use the following to allow multiple lines:
button.titleLabel.lineBreakMode = UILineBreakModeWordWrap;
// you probably want to center it
button.titleLabel.textAlignment = UITextAlignmentCenter;
[button setTitle: @"Line1\nLine2" forState: UIControlStateNormal];
2017, for iOS9 forward,
generally, just do these two things:
- choose "Attributed Text"
- on the "Line Break" popup select "Word Wrap"
OTHER TIPS
The selected answer is correct but if you prefer to do this sort of thing in Interface Builder you can do this:
If you want to add a button with the title centered with multiple lines, set your Interface Builder's settings for the button:
[]
For IOS 6 :
button.titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
button.titleLabel.textAlignment = NSTextAlignmentCenter;
As
UILineBreakModeWordWrap and UITextAlignmentCenter
are deprecated in IOS 6 onwards..
To restate Roger Nolan's suggestion, but with explicit code, this is the general solution:
button.titleLabel?.numberOfLines = 0
SWIFT 3
button.titleLabel?.lineBreakMode = .byWordWrapping
button.titleLabel?.textAlignment = .center
button.setTitle("Button\nTitle",for: .normal)
There is a much easier way:
someButton.lineBreakMode = UILineBreakModeWordWrap;
(Edit for iOS 3 and later:)
someButton.titleLabel.lineBreakMode = UILineBreakModeWordWrap;
Left align on iOS7 with autolayout:
button.titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
button.titleLabel.textAlignment = NSTextAlignmentLeft;
button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
First of all, you should be aware that UIButton already has a UILabel inside it. You can set it using –setTitle:forState:
.
The problem with your example is that you need to set UILabel's numberOfLines
property to something other than its default value of 1. You should also review the lineBreakMode
property.
In Xcode 9.3 you can do it by using storyboard like below,
You need to set button title textAlignment to center
button.titleLabel?.textAlignment = .center
You don't need to set title text with new line (\n) like below,
button.setTitle("Good\nAnswer",for: .normal)
Simply set title,
button.setTitle("Good Answer",for: .normal)
Here is the result,
For those who are using Xcode 4's storyboard, you can click on the button, and on the right side Utilities pane under Attributes Inspector, you'll see an option for Line Break. Choose Word Wrap, and you should be good to go.
Answers here tell you how to achieve multiline button title programmatically.
I just wanted to add that if you are using storyboards, you can type [Ctrl+Enter] to force a newline on a button title field.
HTH
You have to add this code:
buttonLabel.titleLabel.numberOfLines = 0;
As to Brent's idea of putting the title UILabel as sibling view, it doesn't seem to me like a very good idea. I keep thinking in interaction problems with the UILabel due to its touch events not getting through the UIButton's view.
On the other hand, with a UILabel as subview of the UIButton, I'm pretty confortable knowing that the touch events will always be propagated to the UILabel's superview.
I did take this approach and didn't notice any of the problems reported with backgroundImage. I added this code in the -titleRectForContentRect: of a UIButton subclass but the code can also be placed in drawing routine of the UIButton superview, which in that case you shall replace all references to self with the UIButton's variable.
#define TITLE_LABEL_TAG 1234
- (CGRect)titleRectForContentRect:(CGRect)rect
{
// define the desired title inset margins based on the whole rect and its padding
UIEdgeInsets padding = [self titleEdgeInsets];
CGRect titleRect = CGRectMake(rect.origin.x + padding.left,
rect.origin.x + padding.top,
rect.size.width - (padding.right + padding.left),
rect.size.height - (padding.bottom + padding].top));
// save the current title view appearance
NSString *title = [self currentTitle];
UIColor *titleColor = [self currentTitleColor];
UIColor *titleShadowColor = [self currentTitleShadowColor];
// we only want to add our custom label once; only 1st pass shall return nil
UILabel *titleLabel = (UILabel*)[self viewWithTag:TITLE_LABEL_TAG];
if (!titleLabel)
{
// no custom label found (1st pass), we will be creating & adding it as subview
titleLabel = [[UILabel alloc] initWithFrame:titleRect];
[titleLabel setTag:TITLE_LABEL_TAG];
// make it multi-line
[titleLabel setNumberOfLines:0];
[titleLabel setLineBreakMode:UILineBreakModeWordWrap];
// title appearance setup; be at will to modify
[titleLabel setBackgroundColor:[UIColor clearColor]];
[titleLabel setFont:[self font]];
[titleLabel setShadowOffset:CGSizeMake(0, 1)];
[titleLabel setTextAlignment:UITextAlignmentCenter];
[self addSubview:titleLabel];
[titleLabel release];
}
// finally, put our label in original title view's state
[titleLabel setText:title];
[titleLabel setTextColor:titleColor];
[titleLabel setShadowColor:titleShadowColor];
// and return empty rect so that the original title view is hidden
return CGRectZero;
}
I did take the time and wrote a bit more about this here. There, I also point a shorter solution, though it doesn't quite fit all the scenarios and involves some private views hacking. Also there, you can download an UIButton subclass ready to be used.
If you use auto-layout on iOS 6 you might also need to set the preferredMaxLayoutWidth
property:
button.titleLabel.lineBreakMode = NSLineBreakByWordWrapping;
button.titleLabel.textAlignment = NSTextAlignmentCenter;
button.titleLabel.preferredMaxLayoutWidth = button.frame.size.width;
It works perfectly.
Add to use this with config file like Plist, you need to use CDATA to write the multilined title, like this:
<string><![CDATA[Line1
Line2]]></string>
Setting lineBreakMode to NSLineBreakByWordWrapping (either in IB or code) makes button label multiline, but doesn't affect button's frame.
If button has dynamic title, there is one trick: put hidden UILabel with same font and tie it's height to button's height with layout; when set text to button and label and autolayout will make all the work.
Note
Intrinsic size height of one-line button is bigger than label's, so to prevent label's height shrink it's vertical Content Hugging Priority must be greater than button's vertical Content Compression Resistance.
If you use auto-layout.
button.titleLabel?.adjustsFontSizeToFitWidth = true
button.titleLabel?.numberOfLines = 2
I had an issue with auto-layout, after enabling multi-line the result was like this:
so the titleLabel
size doesn't affect the button size
I've added Constraints
based on contentEdgeInsets
(in this case contentEdgeInsets was (10, 10, 10, 10)
after calling makeMultiLineSupport()
:
hope it helps you (swift 5.0):
extension UIButton {
func makeMultiLineSupport() {
guard let titleLabel = titleLabel else {
return
}
titleLabel.numberOfLines = 0
titleLabel.setContentHuggingPriority(.required, for: .vertical)
titleLabel.setContentHuggingPriority(.required, for: .horizontal)
addConstraints([
.init(item: titleLabel,
attribute: .top,
relatedBy: .greaterThanOrEqual,
toItem: self,
attribute: .top,
multiplier: 1.0,
constant: contentEdgeInsets.top),
.init(item: titleLabel,
attribute: .bottom,
relatedBy: .greaterThanOrEqual,
toItem: self,
attribute: .bottom,
multiplier: 1.0,
constant: contentEdgeInsets.bottom),
.init(item: titleLabel,
attribute: .left,
relatedBy: .greaterThanOrEqual,
toItem: self,
attribute: .left,
multiplier: 1.0,
constant: contentEdgeInsets.left),
.init(item: titleLabel,
attribute: .right,
relatedBy: .greaterThanOrEqual,
toItem: self,
attribute: .right,
multiplier: 1.0,
constant: contentEdgeInsets.right)
])
}
}
These days, if you really need this sort of thing to be accessible in interface builder on a case-by-case basis, you can do it with a simple extension like this:
extension UIButton {
@IBInspectable var numberOfLines: Int {
get { return titleLabel?.numberOfLines ?? 1 }
set { titleLabel?.numberOfLines = newValue }
}
}
Then you can simply set numberOfLines as an attribute on any UIButton or UIButton subclass as if it were a label. The same goes for a whole host of other usually-inaccessible values, such as the corner radius of a view's layer, or the attributes of the shadow that it casts.
Roll your own button class. It's by far the best solution in the long run. UIButton and other UIKit classes are very restrictive in how you can customize them.
self.btnError.titleLabel?.lineBreakMode = NSLineBreakMode.byWordWrapping
self.btnError.titleLabel?.textAlignment = .center
self.btnError.setTitle("Title", for: .normal)
swift 4.0
btn.titleLabel?.lineBreakMode = .byWordWrapping
btn.titleLabel?.textAlignment = .center
btn.setTitle( "Line1\nLine2", for: .normal)
I incorporated jessecurry's answer within STAButton which is part of my STAControls open source library. I currently use it within one of the apps I am developing and it works for my needs. Feel free to open issues on how to improve it or send me pull requests.
In Swift 5.0 and Xcode 10.2
SharedClass example write once and use every ware
This is your shared class(like this you use all components properties)
import UIKit
class SharedClass: NSObject {
static let sharedInstance = SharedClass()
private override init() {
}
}
//UIButton extension
extension UIButton {
//UIButton properties
func btnMultipleLines() {
titleLabel?.numberOfLines = 0
titleLabel?.lineBreakMode = .byWordWrapping
titleLabel?.textAlignment = .center
}
}
In your ViewController call like this
button.btnMultipleLines()//This is your button
Although it's okay to add a subview to a control, there's no guarantee it'll actually work, because the control might not expect it to be there and might thus behave poorly. If you can get away with it, just add the label as a sibling view of the button and set its frame so that it overlaps the button; as long as it's set to appear on top of the button, nothing the button can do will obscure it.
In other words:
[button.superview addSubview:myLabel];
myLabel.center = button.center;