How do I set adaptive multiline UILabel text? - ios

I have a UILabel named titleLabel in my storyboard nib set to its default height. I want it to programatically expand in height to fit it's content. Here is what I have tried so far:
// just setting content
titleLabel.text = "You don't always know what you are getting with mass-market cloud computing services. But with SimpliCompute, the picture is clear. SimpliCompute gives you powerful virtual servers you can deploy using just your web browser. That’s enterprise grade technology you can deploy and control on-the-fly."
titleLabel.numberOfLines = 0
titleLabel.preferredMaxLayoutWidth = 700
titleLabel.lineBreakMode = NSLineBreakMode.ByWordWrapping
titleLabel.sizeToFit()
None of this works for me in any combination! I always only see one line of text in my UILabel. What am I doing wrong?
I absolutely need the text content to be variable.

I kind of got things working by adding auto layout constraints:
But I am not happy with this. Took a lot of trial and error and couldn't understand why this worked.
Also I had to add to use titleLabel.numberOfLines = 0 in my ViewController

I know it's a bit old but since I recently looked into it :
let l = UILabel()
l.numberOfLines = 0
l.lineBreakMode = .ByWordWrapping
l.text = "BLAH BLAH BLAH BLAH BLAH"
l.frame.size.width = 300
l.sizeToFit()
First set the numberOfLines property to 0 so that the device understands you don't care how many lines it needs.
Then specify your favorite BreakMode
Then the width needs to be set before sizeToFit() method. Then the label knows it must fit in the specified width

This is much better approach if you are looking for multiline dynamic text label which exactly takes the space based on its text.
No sizeToFit, preferredMaxLayoutWidth used
Below is how it will work.
Lets set up the project. Take a Single View application and in Storyboard Add a UILabel and a UIButton. Define constraints to UILabel as below snapshot:
Set the Label properties as below image:
Add the constraints to the UIButton. Make sure that vertical spacing of 100 is between UILabel and UIButton
Now set the priority of the trailing constraint of UILabel as 749
Now set the Horizontal Content Hugging and Horizontal Content Compression properties of UILabel as 750 and 748
Below is my controller class. You have to connect UILabel property and Button action from storyboard to viewcontroller class.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var textLabel: UILabel!
var count = 0
let items = ["jackson is not any more in this world", "Jonny jonny yes papa eating sugar no papa", "Ab", "What you do is what will happen to you despite of all measures taken to reverse the phenonmenon of the nature"]
#IBAction func updateLabelText(sender: UIButton) {
if count > 3 {
count = 0
}
textLabel.text = items[count]
count = count + 1
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//self.textLabel.sizeToFit()
//self.textLabel.preferredMaxLayoutWidth = 500
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Thats it. This will automatically resize the UILabel based on its content and also you can see the UIButton is also adjusted accordingly.

It should work. Try this
var label:UILabel = UILabel(frame: CGRectMake(10
,100, 300, 40));
label.textAlignment = NSTextAlignment.Center;
label.numberOfLines = 0;
label.font = UIFont.systemFontOfSize(16.0);
label.text = "First label\nsecond line";
self.view.addSubview(label);

With Graphical User Interface (GUI) in Xcode, you can do the following:
Go to "Attribute Inspector" and set Lines value to 0. By default, it is set to 1.
The Label text can be written in multi-line by hitting option + return.
Now, go to "Size Inspector" and set the width, height, X & Y position of the Label.
That's all.

Programmatically, Swift
label.lineBreakMode = NSLineBreakMode.byWordWrapping
label.titleView.numberOfLines = 2

Programmatically in Swift 5 with Xcode 10.2
Building on top of #La masse's solution, but using autolayout to support rotation
Set anchors for the view's position (left, top, centerY, centerX, etc). You can also set the width anchor or set the frame.width dynamically with the UIScreen extension provided (to support rotation)
label = UILabel()
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
self.view.addSubview(label)
// SET AUTOLAYOUT ANCHORS
label.translatesAutoresizingMaskIntoConstraints = false
label.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 20).isActive = true
label.rightAnchor.constraint(equalTo: self.view.rightAnchor, constant: -20).isActive = true
label.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 20).isActive = true
// OPTIONALLY, YOU CAN USE THIS INSTEAD OF THE WIDTH ANCHOR (OR LEFT/RIGHT)
// label.frame.size = CGSize(width: UIScreen.absoluteWidth() - 40.0, height: 0)
label.text = "YOUR LONG TEXT GOES HERE"
label.sizeToFit()
If setting frame.width dynamically using UIScreen:
extension UIScreen { // OPTIONAL IF USING A DYNAMIC FRAME WIDTH
class func absoluteWidth() -> CGFloat {
var width: CGFloat
if UIScreen.main.bounds.width > UIScreen.main.bounds.height {
width = self.main.bounds.height // Landscape
} else {
width = self.main.bounds.width // Portrait
}
return width
}
}

extension UILabel {
var textSize: CGSize { text?.size(withAttributes: [.font: font!]) ?? .zero }
func setSizeForText(_ str: String, maxWidth: CGFloat) {
text = str
let dividedByMaxWidth = Int(textSize.width / maxWidth)
if dividedByMaxWidth == 0 {
frame.size = textSize
} else {
numberOfLines = dividedByMaxWidth + 1
frame.size = CGSize(width: maxWidth, height: frame.size.height * CGFloat(numberOfLines))
sizeToFit()
}
}
}
sizeToFit() in the end will shrink the label's width to the widest line after word break

This has worked for me:
Set the numberOfLines property of UILabel to 0
add this line: yourLabel.sizeToFit() after assigning text to the UILabel

Related

How to change the height of the UISegmentedControl? [duplicate]

This question already has answers here:
iOS: change the height of UISegmentedcontrol
(16 answers)
Closed 3 years ago.
I am using an UISegmentedControl in an UITableView as follows-
let segmentedControl = UISegmentedControl(items: ["Segment1", "Segment2"])
tableView.tableHeaderView = segmentedControl
The above code works as expected. I would like to change the height of the UISegmentedControl. I tried to set a height constraint on the UISegmentedControl as follows-
let segmentedControl = UISegmentedControl(items: ["Segment1", "Segment2"])
segmentedControl.translatesAutoresizingMaskIntoConstraints = false
segmentedControl.heightAnchor.constraint(equalToConstant: 50).isActive = true
tableView.tableHeaderView = segmentedControl
When the above code is run, the height of the UISegmentedControl is set to the expected custom height. However, the leading and trailing edges of the UISegmentedControl do not snap to the leading and trailing edges of the UITableView.
I also tried to create a custom UISegmentedControl to specify the height of the UISegmentedControl without setting a height constraint as follows-
class CustomSegmentedControl: UISegmentedControl {
override var intrinsicContentSize: CGSize {
return CGSize(width: super.intrinsicContentSize.width, height: 50)
}
}
The above code does not work as expected. Can anyone point out how to set the height of the UISegmentedControl so that it appears as expcted when used in UITableView?
By setting segmentedControl.translatesAutoresizingMaskIntoConstraints = false you're losing the default constraints.
If you are happy with the appearance and layout of the segmented control, you just want to change its height, try this approach:
// probably in
override func viewDidLoad() {
super.viewDidLoad()
let segmentedControl = UISegmentedControl(items: ["Segment1", "Segment2"])
segmentedControl.frame.size.height = 50.0
tableView.tableHeaderView = segmentedControl
}
Same question as this:
iOS: change the height of UISegmentedcontrol
By the way, you can also change the scale by changing views transform.
var segmentedControl = UISegmentedControl(frame: CGRect(x: 0, y: 0, width: w, height: h))
segmentedControl.transform = CGAffineTransform(scaleX: x, y: y)
Just set the frame to tableView.tableHeaderView. Don't use constraints.
tableView.tableHeaderView?.frame.size.height = 50

Auto-size view with dynamic font in enclosed textview

So here's one I just can't seem to find a matching case for in searching on here.
I have a small UIView that contains a UITextView, and the UIView needs to auto-size around the TextView for presentation over another view. Basically the TextView needs to fully fill the UIView, and the UIView should only be big enough to contain the TextView.
The TextView just contains a couple sentences that are meant to stay on the screen until an external thing happens, and certain values change.
Everything is great when I used a fixed-size font.
But hey... I'm an old guy, and I have the text size jacked up a bit on my phone. Testing it on my device shows where I must be missing something.
When using the dynamic font style "Title 2" in the textview properties, and turning on "Automatically adjust font" in the TextView properties, and having the text larger than the default, it seems as if I'm not properly capturing the size of the TextView's growth (with the bigger text) when creating the new bounding rect to toss at the frame. It's returning values that look a lot like the smaller, default-size text values rather than the increased text size.
Code is below, the view's class code as well as the calling code (made super explicit for posting here). I figure I'm either missing something silly like capturing the size after something happens to the fonts, but even moving this code to a new function and explicitly calling it after the controls fully draw doesn't seem to do it.
I hope this make sense.
Thanks, all.
Calling code:
let noWView:NoWitnessesYetView = (Bundle.main.loadNibNamed("NoWitnessesYetView", owner: nil, options: nil)!.first as! NoWitnessesYetView)
//if nil != noWView {
let leftGutter:CGFloat = 20.0
let bottomGutter:CGFloat = 24.0
let newWidth = self.view.frame.width - ( leftGutter + leftGutter )
let newTop = (eventMap.frame.minY + eventMap.frame.height) - ( noWView.frame.height + bottomGutter ) // I suspect here is the issue
// I suspect that loading without drawing is maybe not allowing
// the fonts to properly draw and the
// TextView to figure out the size...?
noWView.frame = CGRect(x: 20, y: newTop, width: newWidth, height: noWView.frame.height)
self.view.addSubview(noWView)
//}
Class code:
import UIKit
class NoWitnessesYetView: UIView {
#IBOutlet weak var textView: EyeneedRoundedTextView!
override func draw(_ rect: CGRect) {
let newWidth = self.frame.width
// form up a dummy size just to get the proper height for the popup
let workingSize:CGSize = self.textView.sizeThatFits(CGSize(width: newWidth, height: CGFloat(MAXFLOAT)))
// then build the real newSize value
let newSize = CGSize(width: newWidth, height: workingSize.height)
textView.frame.size = newSize
self.textView.isHidden = false
}
override func awakeFromNib() {
super.awakeFromNib()
self.backgroundColor = UIColor.clear // .blue
self.layer.cornerRadius = 10
}
}
This perfect way to do it the content comes from : https://www.youtube.com/watch?v=0Jb29c22xu8 .
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// let's create our text view
let textView = UITextView()
textView.frame = CGRect(x: 0, y: 0, width: 200, height: 100)
textView.backgroundColor = .lightGray
textView.text = "Here is some default text that we want to show and it might be a couple of lines that are word wrapped"
view.addSubview(textView)
// use auto layout to set my textview frame...kinda
textView.translatesAutoresizingMaskIntoConstraints = false
[
textView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
textView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
textView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
textView.heightAnchor.constraint(equalToConstant: 50)
].forEach{ $0.isActive = true }
textView.font = UIFont.preferredFont(forTextStyle: .headline)
textView.delegate = self
textView.isScrollEnabled = false
textViewDidChange(textView)
}
}
extension ViewController: UITextViewDelegate {
func textViewDidChange(_ textView: UITextView) {
print(textView.text)
let size = CGSize(width: view.frame.width, height: .infinity)
let estimatedSize = textView.sizeThatFits(size)
textView.constraints.forEach { (constraint) in
if constraint.firstAttribute == .height {
constraint.constant = estimatedSize.height
}
}
}
}

UILabel's height is to big for it's width

As you can see right here, this is a UILabel with random text:
The height of that UILabel's text is too big. I just want the height to be adapted from what it is needed to be, for every different content and size of the width. Adding this extensions:
extension UILabel{
func requiredHeight() -> CGFloat{
let label:UILabel = UILabel(frame: CGRect(0, 0, self.frame.width, CGFloat.greatestFiniteMagnitude))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.byWordWrapping
label.font = self.font
label.text = self.text
label.sizeToFit()
return label.frame.height
}
}
extension CGRect{
init(_ x:CGFloat,_ y:CGFloat,_ width:CGFloat,_ height:CGFloat) {
self.init(x:x,y:y,width:width,height:height)
}
}
are not working aswell... Is this possible to do in Storyboard? I tried setting an aspect ratio to it, but when programmatically making the label larger this fails. Is there an easy way to set the height of a UILabel to match it's content, and maybe even in storyboard? Because of the height is not corresponding with what it is needed to be, my whole layout is screwing up. The UILabel needs to be for example 10 points from the top layout. Because the height is to big, the UILabel is setting itself far more below than needed when running it on a different device than the settee layout in storyboard.
I think you can make it setting top and bottom constraints of your UILabel in Storyboard
In this example independent of the font size of "LABEL BIG" there will always be a separation from top layout of 10 points and the "LABEL SMALL" will always be vertically separated by 8 points from "LABEL BIG"

UIView dynamic height depending on Label Height

I have a Label which takes dynamicaly some data from database.
These data are strings which can sometimes be 3-4-5 rows etc.
So this labe is inside a UIView.
--UIView
--Label
How can i make the UIView to take the certain height of the Label dynamicaly??
you can just do it with storyboard this pics
set the label height relation to greater than or equal
and set the view height relation to greater than or equal
it work like a magic
Bellow is working solution of your problem. I used autoLayout. In testView you don't set heightAnchor
let testView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = UIColor.redColor()
return view
}()
let testLabel: UILabel = {
let label = UILabel()
label.numberOfLines = 0
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "jashfklhaslkfhaslkjdhflksadhflkasdhlkasdhflkadshkfdsjh"
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(testView)
testView.centerXAnchor.constraintEqualToAnchor(view.centerXAnchor).active = true
testView.centerYAnchor.constraintEqualToAnchor(view.centerYAnchor).active = true
testView.widthAnchor.constraintEqualToConstant(100).active = true
testView.addSubview(testLabel)
testLabel.topAnchor.constraintEqualToAnchor(testView.topAnchor, constant: 10).active = true
testLabel.leftAnchor.constraintEqualToAnchor(testView.leftAnchor, constant: 10).active = true
testLabel.bottomAnchor.constraintEqualToAnchor(testView.bottomAnchor, constant: -10).active = true
testLabel.rightAnchor.constraintEqualToAnchor(testView.rightAnchor, constant: -10).active = true
}
I know this is late answer, but it might help someone else.
To make the Dynamic height for UIView follow the simple steps in Storyboard
Add a UIView in UIViewController and set your favourite background colour
Now set the following constraints Leading, Top, Trailing and Height(as of now). We can adjust the Height constraint to achieve dynamic height further.
Update Height Constraints as shown below:
Now probably there storyboard will show you inequality constraint ambiguity. But we are going to fix this now. Just add a label inside UIView as shown
Now Set the Constraints for Label Leading, Trailing, Top and Bottom
Hurrah, Now the UIView height will increase based on the label's height. just make the following changes to label
This technique works with other views inside this UIView. The thing is you must specify bottom constraints for the views present inside this UIView.
first calculate the size of label with the text it contains, using this function
func calculateSizeOfLabel(text:String,labelWidth:CGFloat,labelFont:UIFont)->CGSize{
let constrainedSize = CGSizeMake(labelWidth , 9999)
var attributesDictionary:[String:AnyObject] = [:]
attributesDictionary = [NSFontAttributeName:labelFont] as [String:AnyObject]
let string:NSMutableAttributedString = NSMutableAttributedString(string:text, attributes:attributesDictionary)
var boundingRect = string.boundingRectWithSize(constrainedSize, options:.UsesLineFragmentOrigin, context:nil)
if (boundingRect.size.width > labelWidth) {
boundingRect = CGRectMake(0,0, labelWidth, boundingRect.size.height);
}
return boundingRect.size
}
and then apply the height of returned size to the UIView like this
let labelText = description.text
let labelWidth = description.bounds.width
let labelFont = description.font
let calculatedHeight = calculateSizeOfLabel(labelText,labelWidth:labelWidth,labelFont:labelFont).height
DescView.frame = CGRectMake(DescView.frame.origin.x, DescView.frame.origin.y, DescView.bounds.width,calculatedHeight)
Below code will resolved your issue :
//Adjust View Height
[yourView setFrame:CGRectMake(yourView.frame.origin.x, yourView.frame.origin.y, yourView.frame.size.width, yourLable.frame.size.height + yourLable.frame.origin.y + extraspace)];
- (IBAction)action:(id)sender {
self.label.text = #"UIImage *imageOne = [UIImage imageNamed:#RosePot.jpUIImageJPEGRepresentationg";
NSLog(#"%f",self.label.bounds.size.height);
float height = [self getHeightForText:#"UIImage *imageOne = [UIImage imageNamed:#RosePot.jpUIImageJPEGRepresentationg" withFont:[UIFont fontWithName:#"HelveticaNeue" size:15] andWidth:self.label.bounds.size.width];
NSLog(#"%f",height);
self.constraint.constant = height + self.viewOne.bounds.size.height;
}
-(float) getHeightForText:(NSString*) text withFont:(UIFont*) font andWidth:(float) width{
CGSize constraint = CGSizeMake(width , 20000.0f);
CGSize title_size;
float totalHeight;
SEL selector = #selector(boundingRectWithSize:options:attributes:context:);
if ([text respondsToSelector:selector]) {
title_size = [text boundingRectWithSize:constraint
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{ NSFontAttributeName : font }
context:nil].size;
totalHeight = ceil(title_size.height);
} else {
title_size = [text sizeWithFont:font
constrainedToSize:constraint
lineBreakMode:NSLineBreakByWordWrapping];
totalHeight = title_size.height ;
}
CGFloat height = MAX(totalHeight, 40.0f);
return height;
}
Give leading ,top, trailing and height constraint for view .
And height outlet of view name as constraint [because i used outlet name constraint]

UILabel subclass - text cut off in bottom despite label being correct height

I have a problem with UILabel subclass cutting off text in the bottom. Label is of proper height to fit the text, there is some space left in the bottom, but the text is still being cut off.
The red stripes are border added to label's layer.
I subclass the label to add edge insets.
override func sizeThatFits(size: CGSize) -> CGSize {
var size = super.sizeThatFits(size)
size.width += insets.left + insets.right
size.height += insets.top + insets.bottom
return size
}
override func drawTextInRect(rect: CGRect) {
super.drawTextInRect(UIEdgeInsetsInsetRect(rect, insets))
}
However, in this particular case the insets are zero.
Turns out the problem was with
self.lineBreakMode = .ByClipping
changing it to
self.lineBreakMode = .ByCharWrapping
Solved the problem
I was facing the same issue with Helvetica Neue Condensed Bold font. Changing label's Baseline property from Align Baselines to Align Centers did the trick for me. You can change this easily in storyboard by selecting your label.
My problem was that the label's (vertical) content compression resistance priority was not high enough; setting it to required (1000) fixed it.
It looks like the other non-OP answers may be some sort of workaround for this same underlying issue.
TL'DR
Probably the property you are looking for is UILabel's baselineAdjustment.
It is needed because of an old UILabel's known bug. Try it:
label.baselineAdjustment = .none
Also it could be changed through interface builder. This property could be found under UILabel's Attributes inspector with the name "Baseline".
Explanation
It's a bug
There is some discussions like this one about a bug on UILabel's text bounding box. What we observe here in our case is some version of this bug. It looks like the bounding box grows in height when we shrink the text through AutoShrink .minimumFontScale or .minimumFontSize.
As a consequence, the bounding box grows bigger than the line height and the visible portion of UILabel's height. That said, with baselineAdjustment property set to it's default state, .alignBaselines, text aligns to the cropped bottom and we could observe line clipping.
Understanding this behaviour is crucial to explain why set .alignCenters solve some problems but not others. Just center text on the bigger bounding box could still clip it.
Solution
So the best approach is to set
label.baselineAdjustment = .none
The documentation for the .none case said:
Adjust text relative to the top-left corner of the bounding box. This
is the default adjustment.
Since bonding box origin matches the label's frame, it should fix any problem for a one-lined label with AutoShrink enabled.
Also it could be changed through interface builder. This property could be found under UILabel's Attributes inspector with the name "Baseline".
Documentation
You could read more here about UILabel's baselineAdjustmenton official documentation.
Happened for me when providing topAnchor and centerYAnchor for label at the same time.
Leaving just one anchor fixed the problem.
Other answers didn't help me, but what did was constraining the height of the label to whatever height it needed, like so:
let unconstrainedSize = CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
label.heightAnchor.constraint(equalToConstant: label.sizeThatFits(unconstrainedSize).height).isActive = true
Also, sizeThatFits(_:) will return a 0 by 0 size if your label's text field is nil or equal to ""
I ran into this too, but wanted to avoid adding a height constraint. I'd already created a UILabel subclass that allowed me to add content insets (but for the purpose of setting tableHeaderView straight to a label without having to contain it in another view). Using the class I could set the bottom inset to solve the issue with the font clipping.
import UIKit
#IBDesignable class InsetLabel: UILabel {
#IBInspectable var topInset: CGFloat = 16
#IBInspectable var bottomInset: CGFloat = 16
#IBInspectable var leftInset: CGFloat = 16
#IBInspectable var rightInset: CGFloat = 16
var insets: UIEdgeInsets {
get {
return UIEdgeInsets(
top: topInset,
left: leftInset,
bottom: bottomInset,
right: rightInset
)
}
}
override func drawText(in rect: CGRect) {
super.drawText(in: rect.inset(by: insets))
}
override var intrinsicContentSize: CGSize {
return addInsetsTo(size: super.intrinsicContentSize)
}
override func sizeThatFits(_ size: CGSize) -> CGSize {
return addInsetsTo(size: super.sizeThatFits(size))
}
func addInsetsTo(size: CGSize) -> CGSize {
return CGSize(
width: size.width + leftInset + rightInset,
height: size.height + topInset + bottomInset
)
}
}
This could be simplified just for the font clipping to:
import UIKit
class FontFittingLabel: UILabel {
var inset: CGFloat = 16 // Adjust this
override func drawText(in rect: CGRect) {
super.drawText(in: rect.inset(by: UIEdgeInsets(
top: 0,
left: 0,
bottom: inset,
right: 0
)))
}
override var intrinsicContentSize: CGSize {
let size = super.intrinsicContentSize
return CGSize(
width: size.width,
height: size.height + inset
)
}
}
I had a vertical UIStackView with a UILabel at the bottom. This UILabel was cutting off the letters that go below the baseline (q, g, y, etc), but only when nested inside a horizontal UIStackView. The fix was to add the .lastBaseline alignment modifier to the outer stack view.
lazy var stackView: UIStackView = {
let stackView = UIStackView(arrangedSubviews: [
aVerticalStackWithUILabelAtBottom, // <-- bottom UILabel was cutoff
UIView(),
someOtherView
])
stackView.axis = .horizontal
stackView.spacing = Spacing.one
stackView.alignment = .lastBaseline // <-- BOOM fixed it
stackView.isUserInteractionEnabled = true
return stackView
}()

Categories

Resources