UILabel does not wrap and shows more than one line.
According to the docs, my text should wrap on word boundary and be displayed in 2 lines. However, this does not occur. I'm using Swift 4 and the latest version of XCode.
let instruction = UILabel()
instruction.text = "Click And Touch Number To Make A Choice"
instruction.backgroundColor = .white
instruction.textColor = .black
instruction.font = UIFont.systemFont(ofSize: 20)
instruction.lineBreakMode = .byWordWrapping
instruction.textAlignment = .left
instruction.numberOfLines = 0
instruction.sizeToFit()
The label to wrap should have a frame whether with frame layout by setting width value or by auto layout by setting leading and trailing constraints that makes the label know it's boundaries and wrap when hitting the right most boundary to another line and so on according to it's content
let instruction = UILabel.init(frame: CGRect.init(x: 0, y: 0, width: 100, height: 100))
instruction.text = "Click And Touch Number To Make A Choice"
instruction.backgroundColor = .white
instruction.textColor = .black
instruction.font = UIFont.systemFont(ofSize: 20)
instruction.lineBreakMode = .byWordWrapping
instruction.textAlignment = .left
instruction.numberOfLines = 0
instruction.sizeToFit()
self.view.addSubview(instruction)
instruction.center = self.view.center
According to the docs:
In some cases, if a view does not have a superview, it may size itself to the screen bounds.
But in your case it doesn't. You just need to constrain label's width somehow and sizeToFit() wouldn't be even needed (Auto Layout will do its job) e.g.
instruction.translatesAutoresizingMaskIntoConstraints = false
instruction.widthAnchor.constraint(equalToConstant: 200.0).isActive = true
Related
I have a UIStackView, and as arranged subviews, I have two UIViews, and a UILabel. The UIViews are stacked one after another, while the UILabel is aligned to the leading edge of the subview.
Code:
let stack = UIStackView()
stack.axis = .horizontal
stack.distribution = .fillProportionally
stack.alignment = .center
stack.spacing = 7
view.addSubview(stack)
stack.translatesAutoresizingMaskIntoConstraints = false
stack.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
stack.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
let icon = UIView()
icon.backgroundColor = UIColor.red
stack.addArrangedSubview(icon)
icon.translatesAutoresizingMaskIntoConstraints = false
icon.heightAnchor.constraint(equalToConstant: 42).isActive = true
icon.widthAnchor.constraint(equalToConstant: 42).isActive = true
let icon2 = UIView()
icon2.backgroundColor = UIColor.red
stack.addArrangedSubview(icon2)
icon2.translatesAutoresizingMaskIntoConstraints = false
icon2.heightAnchor.constraint(equalToConstant: 42).isActive = true
icon2.widthAnchor.constraint(equalToConstant: 42).isActive = true
let label = UILabel()
label.lineBreakMode = .byWordWrapping
label.numberOfLines = 0
label.sizeToFit()
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = .yellow
label.text = "Hello World! Again"
label.textColor = .black
stack.addArrangedSubview(label)
Output:
For clarity...
First, don't use .fillProportionally. Whatever you think that will do, it's wrong. You have explicitly set your two "icons" to be 42-pts wide each. If the stack view is using .fillProportionally, it will try to change the widths of those views, and you will get auto-layout conflicts.
Second, a UILabel with .numberOfLines = 0 must have a width. Otherwise, there is no way to know where to break the text... with a lot of text, it will extend way off the sides of the view.
Third, the line label.sizeToFit() isn't going to accomplish anything here. Just delete it.
If you add this line:
stack.widthAnchor.constraint(equalToConstant: 200.0).isActive = true
then the stack view will expand to 200-pts wide...
Auto-layout will give the first arranged view a width of 42 (because that's what you declared it to be), and the same for the second view. Since you've set the spacing to 7, it will then calculate
42 + 7 + 42 + 7
which equals 98. It subtracts that from the stack view's width:
200 - 98 = 102
and that is the width it will give your label.
Result:
If you don't want to explicitly set the width to 200, you can set it to a percentage of the superview's width, or give it leading and trailing constraints.
Try to change distribution to fill
stack.distribution = .fill
With multiline set
After commenting it
I'm in the process of adding dynamic type to my app and I'm trying to update the frame of a programmatically created UILabel when the UIContentSizeCategoryDidChangeNotification notification is fired through the following code:
private func configureNotificationCenter() {
NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange) , name: NSNotification.Name("UIContentSizeCategoryDidChangeNotification"), object: nil)
}
#objc private func contentSizeCategoryDidChange() {
self.delegate?.didChangeContentSize()
}
and then in the view where the UILabel is going to be updated, I update the frame:
func didChangeContentSize() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: {
self.label.sizeToFit()
})
}
For some reason, the frame is not set properly without calling DispatchQueue.main.asyncAfter .... At first, I tried calling DispatchQueue.main without the 0.1 second delay since I know UI updates should always be on the main thread, but it didn't seem to make any difference.
While delaying 0.1 seconds isn't a huge deal and I don't think any users would notice, it would be great to understand what's going on and why the delay is necessary.
Edit: Here's how I'm creating the label
label = UILabel(frame: CGRect(x: 0, y: 100, width: view.frame.width, height: 200))
label.backgroundColor = .red
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.text = "Test title that should resize"
label.adjustsFontForContentSizeCategory = true
label.textAlignment = .center
let userFont = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .title1)
let pointSize = userFont.pointSize
let customFont = UIFont(name: "AvenirNext-DemiBold", size: pointSize)
label.font = UIFontMetrics.default.scaledFont(for: customFont!)
label.sizeToFit()
view.addSubview(label)
This sounds very much like an Auto Layout issue. When you don't delay, Auto Layout is adjusting the intrinsic size of the label and running after you modify the frame for the label, so your sizeToFit comes too early and uses the previous intrinsic size.
When you delay by 0.1 seconds, Auto Layout runs first and sets the intrinsic size of the label, and then your sizeToFit() call uses that new intrinsic size to set the frame.
Use Auto Layout
Make things easier on yourself by using Auto Layout. Instead of messing with frame sizes, sizeToFit, and notifications, just set constraints for the leading, trailing, and top edges of your label and Auto Layout will automatically resize your label when the font size changes:
label = UILabel()
label.backgroundColor = .red
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
label.text = "Test title that should resize"
label.adjustsFontForContentSizeCategory = true
label.textAlignment = .center
let userFont = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .title1)
let pointSize = userFont.pointSize
let customFont = UIFont(name: "AvenirNext-DemiBold", size: pointSize)
label.font = UIFontMetrics.default.scaledFont(for: customFont!)
view.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
label.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
label.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
label.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
The bottom part of characters that extend below the baseline is cut off in a UILabel.
The '|' character is the easiest to notice this issue. See in the below screenshot that the '|' character extended well below the bottom of the comma in the 2nd line, but is then cut off in the 3rd line.
In the code I have:
let moreDetailsLabel : UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIHelper.HIRAGINO_SANS_FONT.withSize(30)
label.backgroundColor = UIColor.clear
label.text = "For more details|, check out our website|,"
label.textAlignment = .right
label.backgroundColor = UIColor.red
label.textColor = UIColor.white
label.numberOfLines = 0
return label
}()
I add it to the viewcontroller view's hierarchy:
view.addSubview(descriptionLabel)
then for the constraints, I have:
allConstraints += [moreDetailsLabel.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width/2)]
allConstraints += [moreDetailsLabel.leftAnchor.constraint(equalTo: moreDetailsContainerView.leftAnchor)]
allConstraints += [moreDetailsLabel.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 130)
You will notice that I do not set a height constraint. I am hoping that I did not have to set that for UILabels because they will just shrink/stretch accordingly with the fixed width. I might be wrong in that believe, but it seems like that is the behavior.
The spacing between the top of the F and the bounding view seems to have a small off that I am hoping is the reason the text is cut off.
I have mentioned my code below where I want to adjust my label height according to the text. In the picture below, black background is the label and this label is inserted into a UIScrollView. Label is aligned with the top left corner of the scroll view but when I run my app following is the display of view controller i.e. label has gone far below the top left corner of the scrollview. Please help me in this matter
func getLabelsHeightAccordingToTheTextContent() -> CGFloat
{
let label:UILabel = UILabel(frame: CGRectMake(0, 0, 250, CGFloat.max))
label.numberOfLines = 0
label.font = outletLabel.font
label.text = outletLabel.text
label.lineBreakMode = NSLineBreakMode.ByWordWrapping
var fontName: String = outletLabel.font.fontName
label.font = UIFont (name: fontName, size: outletLabel.font.pointSize)
label.sizeToFit()
return label.frame.height
}
when i get the height of label, I set the height of scrollview
let height = getLabelsHeightAccordingToTheTextContent()
let width = outletScrollView.frame.width
following are the properties of the label....................................
The easiest way to add scrolling functionality for text is to use a UITextView. The setup is more or less the same as a label but scrolling is built in.
One thing to note is that users are allowed to edit the text by default, so either in the storyboard or programmatically you should set editable to false for your textView.
I have created a label via and a container view via:
let label = UILabel(frame: CGRectMake(0,0, 50, 50))
label.text = "omnomnom"
let labelView = UIView(frame: CGRectMake(50, 50, 100, 100))
labelView.addSubview(label)
labelView.bringSubviewToFront(label)
labelView.backgroundColor = UIColor.orangeColor()
label.textAlignment = .Center
But the label text might change so as a result for a longer word the whole thing won't be shown. I was wondering whether there is way to determine the intrinsic content size after the label is added, so that I can use those values to create a container view that is just a little bit bigger than that of the label.
You can use sizeToFit to accomplish that. So you only need to add label.sizeToFit() to the end of your label configuration.