I want to center image to center Y position of first line of text of my UILabel. I use masonry to set Auto Layout constraints like that:
[_haveReadIndicatorImgView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.left.equalTo(self.contentView).offset(SMALL_OFFSET);
make.height.width.equalTo(#(8));
}];
[_topTxtlbl mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(_haveReadIndicatorImgView.mas_right).offset(TINY_OFFSET);
make.top.equalTo(_haveReadIndicatorImgView.mas_top);
make.right.equalTo(self.arrowImgView.mas_left).offset(-SMALL_OFFSET);
make.bottom.equalTo(_dateTxtLbl.mas_top).offset(-SMALL_OFFSET);
}];
It should be pretty strightforward. I simply attach top of UIImageView to top of my Label.
But take a look at screen.
Top edges of UIImageView (gray dot) and label are equal, but how to make UIImageView to be centered to first line of text like that?
Thanks.
Actually there is a way of doing this! If you use plain old AutoLayout this can be done with the following snippet:
// Aligns the icon to the center of a capital letter in the first line
let offset = label.font.capHeight / 2.0
// Aligns the icon to the center of the whole line, which is different
// than above. Especially with big fonts this makes a visible difference.
let offset = (label.font.ascender + label.font.descender) / 2.0
let constraints: [NSLayoutConstraint] = [
imageView.centerYAnchor.constraint(equalTo: label.firstBaselineAnchor, constant: -offset),
imageView.trailingAnchor.constraint(equalTo: label.leadingAnchor, constant: -10)
]
NSLayoutConstraint.activate(constraints)
The first constraint will display your icon at the Y center of the first line of your label. The second one puts your icon left of the label and creates a 10pt space between them.
Hope this helps!
You derive the middle of the first line by using the lineHeight for the font of your label.
let lineHeight = ceil(multiLineLabel.lineHeight)
let center = lineHeight / 2
Now that you have the center, you can center the haveReadIndicatorImgView's centerYAnchor to the top of your label with a constant: center
I solved this recently by adding a hidden single line label in exactly the same location and font as the multiline one, without a bottom constraint.
Then you can simply align the icon image .centerY to the hidden label's .centerY.
I achieve following by 2 steps:
1) Calculate expected height of label line of text with specific font:
+(CGSize)getSimpleSizeBasedOnFont:(CGFloat)font{
UILabel *lbl = [UILabel new];
lbl.text = #"Simple text";
lbl.font = [UIFont systemFontOfSize:font];
return [lbl.text sizeWithFont:lbl.font
constrainedToSize:lbl.frame.size
lineBreakMode:NSLineBreakByWordWrapping];
}
Then i add constraints to center Y of UIImage View with offset equal to 50% of that height:
CGFloat lblOffs = [Helper getSimpleSizeBasedOnFont:14].height;
[_haveReadIndicatorImgView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(_topTxtlbl.mas_top).offset(lblOffs/2);
make.left.equalTo(self.contentView).offset(SMALL_OFFSET);
make.height.width.equalTo(#(8));
}];
I have done this differently.
At first i align my imageView with label by FirstBaseLine.
And then i took an outlet of that LayoutConstraint
I have calculated an offset like below:
let offset = (label.font.capHeight + imageView.frame.size.height) / 2 //your bulleted image
I have discarded that offset from FirstBaseLine constant
firstBaseLineConstraintWithLabel.constant -= offset
Here is the output
I'm getting into iOS programming and mastered more or less autolayout with fixed number of items. Say, we have a UILabel for title, UILabel for subtitle, then the visual format for the constraint is 'V:|-[title]-10-[subtitle]-|'
But what if I create subviews dynamically based on some API response. There are for example 40 subviews I need to add. It's not realistic anymore that I specify each subview with the visual format and keep track of them. What is the the proper way?
I imagine that after each subview I add, I then set the constraint based on the previous view with constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:. Is that the way to go, or there is a better way?
It's not realistic anymore that I specify each subview with the visual format and keep track of them
Why on earth not? You seem to think this is some sort of "either/or" situation. Nothing prevents you from using visual formatting to build up a collection of constraints. No law says that you have to put all your constraints into one visual format.
And the layout engine doesn't know or care what notation you used to form your constraints. A constraint is a constraint!
Consider this code where I build up the constraints for a scroll view and thirty labels inside it:
var con = [NSLayoutConstraint]()
con.appendContentsOf(
NSLayoutConstraint.constraintsWithVisualFormat(
"H:|[sv]|",
options:[], metrics:nil,
views:["sv":sv]))
con.appendContentsOf(
NSLayoutConstraint.constraintsWithVisualFormat(
"V:|[sv]|",
options:[], metrics:nil,
views:["sv":sv]))
var previousLab : UILabel? = nil
for i in 0 ..< 30 {
let lab = UILabel()
// lab.backgroundColor = UIColor.redColor()
lab.translatesAutoresizingMaskIntoConstraints = false
lab.text = "This is label \(i+1)"
sv.addSubview(lab)
con.appendContentsOf(
NSLayoutConstraint.constraintsWithVisualFormat(
"H:|-(10)-[lab]",
options:[], metrics:nil,
views:["lab":lab]))
if previousLab == nil { // first one, pin to top
con.appendContentsOf(
NSLayoutConstraint.constraintsWithVisualFormat(
"V:|-(10)-[lab]",
options:[], metrics:nil,
views:["lab":lab]))
} else { // all others, pin to previous
con.appendContentsOf(
NSLayoutConstraint.constraintsWithVisualFormat(
"V:[prev]-(10)-[lab]",
options:[], metrics:nil,
views:["lab":lab, "prev":previousLab!]))
}
previousLab = lab
}
con.appendContentsOf(
NSLayoutConstraint.constraintsWithVisualFormat(
"V:[lab]-(10)-|",
options:[], metrics:nil,
views:["lab":previousLab!]))
NSLayoutConstraint.activateConstraints(con)
I'm using visual formatting, but I'm doing it one constraint as a time as I add views (which is exactly what you're asking about).
The answer really depends on what layout you want, because there are higher-level tools for making some layouts.
Starting with iOS 9, you can use UIStackView to manage a row or column of views. By nesting stack views, you can easily make a grid of views. For an introduction to UIStackView, start by watching “Mysteries of Auto Layout, Part 1” from WWDC 2015. If you're doing all your layout in code, you can use TZStackView, a reimplementation of UIStackView that works on iOS 7 and later.
UICollectionView is another tool for laying out views in a grid, or in many other arrangements. It's more complex than UIStackView, but there's a lot of introductory material available to teach you how to use it, such as “Introducing Collection Views” from WWDC 2012.
I built a dynamic layout engine (Horizontal layout and Vertical layout).
It accepts an array of views.
It adds the views as subviews to a container.
Then composes a vertical and horizontal Visual layout string dynamically. (It generates a unique "Object Address" string for each view being laid out using v%p.
Simply call: [myView addControls:#[view1, view2, view3]] in viewDidLoad, and it will lay them out vertically for you.
Here's a generic layout engine for a vertical layout. Enjoy.
#implementation UIView (VerticalLayout)
- (void)addControls:(NSArray*)controls
align:(VerticalAlignment)alignment
withHeight:(CGFloat)height
verticalPadding:(CGFloat)verticalPadding
horizontalPadding:(CGFloat)horizontalPadding
topPadding:(CGFloat)topPadding
bottomPadding:(CGFloat)bottomPadding
withWidth:(CGFloat)width
{
NSMutableDictionary* bindings = [[NSMutableDictionary alloc] init];
NSDictionary *metrics = #{
#"topPadding": #(topPadding),
#"bottomPadding": #(bottomPadding),
#"verticalPadding": #(verticalPadding),
#"horizontalPadding":#(horizontalPadding) };
NSMutableString* verticalConstraint = [[NSMutableString alloc] initWithString:#"V:"];
if (alignment == VerticalAlignTop || alignment == VerticalAlignStretchToFullHeight) {
[verticalConstraint appendString:#"|"];
}
for (UIView* view in controls) {
BOOL isFirstView = [controls firstObject] == view;
BOOL isLastView = [controls lastObject] == view;
[self addSubview: view];
// Add to vertical constraint string
NSString* viewName = [NSString stringWithFormat:#"v%p",view];
[bindings setObject:view forKey:viewName];
NSString* yLeadingPaddingString = #"";
NSString* yTrailingPadding = #"";
if (isFirstView && topPadding > 0) {
yLeadingPaddingString = #"-topPadding-";
}
else if (!isFirstView && verticalPadding > 0) {
yLeadingPaddingString = #"-verticalPadding-";
}
if (isLastView && bottomPadding > 0) {
yTrailingPadding = #"-bottomPadding-";
}
[verticalConstraint appendFormat:#"%#[%#%#]%#",
yLeadingPaddingString,
viewName,
height > 0 ? [NSString stringWithFormat:#"(%f)", height] : #"",
yTrailingPadding];
NSArray* constraints = [NSLayoutConstraint constraintsWithVisualFormat:
[NSString stringWithFormat: #"H:|-horizontalPadding-[%#%#]%#|",
viewName,
(width > 0 ? [NSString stringWithFormat:#"(%lf)", width] : #""),
width > 0 ? #"-" : #"-horizontalPadding-"]
options:NSLayoutFormatDirectionLeadingToTrailing
metrics:metrics
views:bindings];
// high priority, but not required.
for (NSLayoutConstraint* constraint in constraints) {
constraint.priority = 900;
}
// Add the horizontal constraint
[self addConstraints: constraints];
}
if (alignment == VerticalAlignBottom || alignment == VerticalAlignStretchToFullHeight) {
[verticalConstraint appendString:#"|"];
}
NSArray* constraints = [NSLayoutConstraint
constraintsWithVisualFormat:verticalConstraint
options:NSLayoutFormatAlignAllLeading
metrics:metrics
views:bindings];
for (NSLayoutConstraint* constraint in constraints) {
constraint.priority = 900;
}
// Add the vertical constraints for all these views.
[self addConstraints:constraints];
}
I want the label below (in yellow) to be at least two lines rather one.
I've made sure to uncheck Use Autolayout in Interface Builder. When I set the numberOfLines from 0 to 2, I get two words stacked on top of each other, with the yellow background tightly fitting the words. The result is the same regardless of whether the lineBreakMode is NSLineBreakByWordWrapping or NSLineBreakByTruncatingTail. It's also the same if I set the frame of the terms Label using the result of sizeWithAttributes or not, and it's the same if I use sizeToFit or not. I've also tried making the label a UILabel rather than a subclass of UILabel, which is TTTAttributedLabel, but the result is the same.
_termsLabel.font = [UIFont systemFontOfSize:12];
_termsLabel.textColor = [UIColor grayColor];
_termsLabel.textAlignment = NSTextAlignmentCenter;
_termsLabel.lineBreakMode = NSLineBreakByWordWrapping;
_termsLabel.numberOfLines = 0;
_termsLabel.delegate = self;
_termsLabel.backgroundColor = [UIColor yellowColor];
// Terms label
NSString *termsText = [NSString stringWithFormat:#"%# %# %# %#", NSLocalizedString(#"TermsIAgree", nil),
NSLocalizedString(#"SettingsTOS", nil),
NSLocalizedString(#"LocalizedAnd", nil),
NSLocalizedString(#"SettingsPrivacyPolicy", nil)];
_termsLabel.text = termsText;
_termsLabel.linkAttributes = #{ (__bridge NSString *)kCTUnderlineStyleAttributeName : [NSNumber numberWithBool:YES]};
CGSize termsSize = [_termsLabel.text sizeWithAttributes: #{ NSFontAttributeName : _termsLabel.font}];
_termsLabel.frame = CGRectMake(65,
395,
termsSize.width, termsSize.height);
[_termsLabel addLinkToURL:[NSURL URLWithString:TOS_URL] withRange:[termsText rangeOfString:NSLocalizedString(#"SettingsTOS", nil)]];
[_termsLabel addLinkToURL:[NSURL URLWithString:PRIVACY_POLICY_URL] withRange:[termsText rangeOfString:NSLocalizedString(#"SettingsPrivacyPolicy", nil)]];
EDIT: By finding the terms text size using CGSize termsSize = [_termsLabel.text sizeWithFont:_termsLabel.font forWidth:200 lineBreakMode:NSLineBreakByWordWrapping];
Yet the height of the termsSize is then 14, resulting in just one line:
How can I get the second line? SOLUTION At this point, just add [_termsLabel sizeToFit].
If you've got static text, just set the break mode to wrap, set lines to the number you want, and adjust the label's frame in interface builder until it wraps the way you like. Of you've got dynamic text, you can use sizeToFit after setting the label's text to have it automatically adjust it's height to fit the specified width:
Set frame to max desired width
Set lines to 0
Set break mode to wrap
Call sizeToFit
Determine the maximum width of your label and try sizeWithFont:forWidth:lineBreakMode: method with this value and desired NSLineBreakMode to get the size of resulting string's bounding box.
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?
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"
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)
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)
])
}
}
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,
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.
Swift 5 , For multi Line text in UIButton
let button = UIButton()
button.titleLabel?.lineBreakMode = .byWordWrapping
button.titleLabel?.textAlignment = .center
button.titleLabel?.numberOfLines = 0 // for Multi line text
To fix title label's spacing to the button, set titleEdgeInsets and other properties before setTitle:
let button = UIButton()
button.titleLabel?.lineBreakMode = .byWordWrapping
button.titleLabel?.numberOfLines = 0
button.titleEdgeInsets = UIEdgeInsets(top: 10, left: 10, bottom: 20, right: 20)
button.setTitle("Dummy button with long long long long long long long long title", for: .normal)
P.S. I tested setting titleLabel?.textAlignment is not necessary and the title aligns in .natural.
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
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.
You have to add this code:
buttonLabel.titleLabel.numberOfLines = 0;
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.
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;
In Swift 5.0 and Xcode 10.2
//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
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>
If you use auto-layout.
button.titleLabel?.adjustsFontSizeToFitWidth = true
button.titleLabel?.numberOfLines = 2
swift 4.0
btn.titleLabel?.lineBreakMode = .byWordWrapping
btn.titleLabel?.textAlignment = .center
btn.setTitle( "Line1\nLine2", for: .normal)
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.
In iOS 15 in 2021, Apple for the first time officially supports multi-line UIButtons via the UIButton.Configuration API.
UIButton.Configuration
A configuration that specifies the appearance and behavior of a button and its contents.
This new API is explored in What's new in UIKit as well as the session:
Meet the UIKit button system
Every app uses Buttons. With iOS 15, you can adopt updated styles to create gorgeous buttons that fit effortlessly into your interface. We'll explore features that make it easier to create different types of buttons, learn how to provide richer interactions, and discover how you can get great buttons when using Mac Catalyst.
https://developer.apple.com/videos/play/wwdc2021/10064/
self.btnError.titleLabel?.lineBreakMode = NSLineBreakMode.byWordWrapping
self.btnError.titleLabel?.textAlignment = .center
self.btnError.setTitle("Title", 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.
Adding Buttons constraints and subviews. This is how i do it in my projects, lets say its much easier like this. I literally 99% of my time making everything programmatically.. Since its much easier for me. Storyboard can be really buggy sometimes
[1]: https://i.stack.imgur.com/5ZSwl.png
My experience:
Go to "Attribut" tab.
Texting in title, press "alt+Enter" while you want to jump to next line.
And check "Word Wrap" under "Attribut --> Control" field.
picture