Text not vertically centered in UILabel for some fonts - ios

I want the text to always be vertically centered. It doesn't work for some specific fonts such as "DamascusSemiBold", What is the solution?
let label = VerticalLabel(frame: CGRect(x: 10, y: 100, width: 300, height: 50))
label.layer.borderColor = UIColor.red.cgColor
label.layer.borderWidth = 1
label.text = "Text Sticker"
label.font = UIFont(name: "DamascusSemiBold", size: 30)
label.numberOfLines = 0
label.textColor = .white
label.textAlignment = .center
self.view.addSubview(label)
public override func textRect(forBounds bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect {
var textRect = super.textRect(forBounds: bounds, limitedToNumberOfLines: numberOfLines)
print(textRect, bounds)
textRect.origin.y = bounds.origin.y + (bounds.size.height - textRect.size.height) * 0.5
print(textRect)
return textRect
}
public override func drawText(in rect: CGRect) {
let actualRect = self.textRect(forBounds: rect, limitedToNumberOfLines: self.numberOfLines)
super.drawText(in: actualRect)
}
It's not centered, just top!
I know there are a lot of threads similar to this, but I've read all of them (as far as I can tell) and haven't seen the same problem I'm having.
I'm trying to get all of the text to show in a label VERTICALLY.
Let me explain - Fonts are not always created the same way, so while their total height may be the same (24pts, for example). However, the Ascender and Descender vary widely - one font may be mostly above the baseline, while another is mostly below. Therefore, the same text, with different fonts may not always show in the same view/label.
The first is Helvetica-Bold 300pts. The second is Apple Gothic 300pts.
Notice how the bottom of the "g" is cut off with Helvetica (and many other fonts too - try it, you'll see).
So my issue is this: I'd like to be able to see the entire text, regardless of the font. If the text in the "Helvetica" example could be moved up (and centered) within the label, it would solve my problem. To make it easier, I only need to display a single line.
Unfortunately, none of the solutions I've seen involve the descenders and ascenders of the font and figuring out how to draw the text within a Rect and not have it cropped. Note that the "VerticalAlignment" solutions in various threads don't fix this particular problem.
I calculate boundRect for attributedString and update the frame of label, and center text vertically
super.drawText(in: rect.inset(by: UIEdgeInsets(top: -(font.ascender - font.capHeight), left: 0, bottom: font.descender, right: 0)))
Above code cuts descender even though center vertically, how can I center text vertically without cutting of ascender, descender?
Screenshot
Does anyone have any ideas or solutions for this?

f you have a label with longer text that will make more than one line, set numberOfLines to 0 (zero here means an unlimited number of lines).
label.numberOfLines = 0

Related

How to force word wrapping of UILabel that adjusts font size and is multiline

The problem I am facing is that UILabel will break line in the middle of the word although I am using word wrapping.
You can create a new project and replace content of view controller to see the result:
override func viewDidLoad() {
super.viewDidLoad()
let label = UILabel(frame: CGRect(x: 0.0, y: 0.0, width: 100.0, height: 100.0))
label.center = CGPoint(x: view.frame.midX, y: view.bounds.midY)
label.numberOfLines = 2 // Setting this to 1 produces expected result
label.lineBreakMode = .byWordWrapping
label.adjustsFontSizeToFitWidth = true
label.minimumScaleFactor = 0.5
view.addSubview(label)
label.text = "Singlewordtext"
label.backgroundColor = .red
}
This produces 2 lines of text which is broken in the middle of the word. The reason this naturally happens is because the word itself is wider than the label itself so it makes sense (I guess). But I would hope that it would use adjustsFontSizeToFitWidth and minimumScaleFactor prior to breaking it. If I set it to single line (label.numberOfLines = 1) I get expected result which is that the text will shrink instead of break. Note that doing so in this case will fit all of the text inside the label.
The question is, is there a configuration on UILabel to prevent line break in such case? Or is there some other elegant solution?
A current result:
Desired result (produced by using label.numberOfLines = 1):
Do note that I still do need to have 2 lines enabled to nicely display for instance label.text = "Three words fit".

Word Wrap Occurs Inconsistently in UILabel

I have a UILabel that is designed to expand in height when the width of the text's CGSize is greater than the width of the label. I accomplish that with this code:
func viewHeight(_ locationName: String) -> CGFloat {
let locationName = tappedLocation[0].name
var size = CGSize()
if let font = UIFont(name: ".SFUIText", size: 17.0) {
let fontAttributes = [NSAttributedStringKey.font: font]
size = (locationName as NSString).size(withAttributes: fontAttributes)
}
let normalCellHeight = horizontalStackViewHeightConstraint.constant
let extraLargeCellHeight = horizontalStackViewHeightConstraint.constant + 20.33
let textWidth = ceil(size.width)
let cellWidth = ceil(nameLabel.frame.width)
if textWidth > cellWidth {
return extraLargeCellHeight
} else {
return normalCellHeight
}
}
Lines = 0 and line break style = Word Wrap:
The label lives inside a vertical stackView, and is constrained to its top, leading and trailing edges and a stackView beneath it. The height of the label and the UIView properly expand in height when the CGSize width of the text is longer than the width of the label. All well and good.
However, the words do not wrap consistently. This behavior is intentional:
Bobby Mao's Chinese Kitchen & Bar:
XL cell. Width: 184.0,
Text width: 287.0
This behavior is not (why isn't "steak" on the prior line?):
Ruth's Chris Steak House:
XL cell. Width: 184.0,
Text width: 204.0
And neither is this (why didn't Gina wrap if it's over the label width parameter?):
Ristorante Mamma Gina:
XL cell. Width: 184.0,
Text width: 191.0
I have also set a temporary background color on my label to ensure that it does, in fact correspond to the intended width. The label in this example creates another line when the label's width is exceeded, but the text does not wrap:
I have read the other entries on Stack Overflow about word wrapping. I don't believe this is a duplicate. I do not have trouble creating two lines for my text. I don't have trouble with word wrapping occurring. I have trouble with how and when it is occurring.
I think the intent is clear... what am I missing?

Dynamically adjust width of UILabel in Swift 3

I'm trying to get my UILabel to get wider (along with its border and background color) as the content gets more - and then less when the content is reduced.
Where do I go to get started, I've looked at the Attributes Inspectors and it looks like this can only be done with code (which I'm fine with).
I thought adding two labels in a horizontal stack would do the trick, but it doesn't update in real-time (it will update the label only on launch).
Try using :
myLabel.sizeToFit()
on your label.This should update the label's frame to fit the content.
let label:UILabel = UILabel()
label.textColor=UIColor.black
label.font = UIFont(name: "Halvetica", size: 17)
label.numberOfLines = 1
label.text = "your string"
label.sizeToFit()
label.frame = CGRect(x: 5, y: imageView.frame.height+10, width: label.frame.width, height:label.frame.height)

Bounding rect of multiline string in UILabel swift

I have been trying for hours now to find the boundingRect of a string in a UILabel I have, but nothing seems to be working.
From what I understand, boundingRect returns the size of the actual text in the label, not the label's size or something like that. This is true, right?
I have a UILabel called messageLabel which contains some text that wraps to an unlimited number of lines.
The code I have now is this:
let labelRect = (message as NSString).boundingRect(with: messageLabel.frame.size,
options: .usesLineFragmentOrigin,
attributes: [NSFontAttributeName : messageLabel.font],
context: nil)
Unfortunately, this returns totally wrong dimensions for my text.
What is the correct way to return the dimensions of text in a multiline UILabel?
Use:
let sizeToFit = CGSize(width: messageLabel.frame.size.width,
height: CGFloat.greatestFiniteMagnitude)
let textSize = messageLabel.sizeThatFits(sizeToFit)
Anyway, the way you did it should work as well (you can see on playground both functions return same size):
I've added a sample view to the playground, so you can see, the label has black border, and the text fits inside, and is smaller than label. Size is computer properly with both sizeToFit and boundingRect methods (but boundingRect returns not rounded values). I've use this computed size to create a green background view under the text, and it fits it properly.
I think you need to Try this
let messageLabel = UILabel(frame: CGRect(x: 0, y: 0, width: _screenSize.width - 30, height: 5))
messageLabel.font = self.txtDescription.font
messageLabel.numberOfLines = 0
messageLabel.text = "Your Massage"
messageLabel.numberOfLines = 0
messageLabel.sizeToFit()
print(messageLabel.frame.size.height)
Remove all code Just try this Hope it will wirk

How to calculate UITextView first baseline position relatively to the origin

How can I calculate UITextView first baseline position?
I tried calculating it this way:
self.textView.font!.descender + self.textView.font!.leading
However, the value I'm getting is not correct. Any ideas, hints?
There is a diagram from the Apple document
From my understanding, font.descender + font.leading gives the distance between the first baseline and starting of the second line.
I would imagine font.lineHeight would give you the right number, if UITextField doesn't have a secret padding.
EDIT
UITextField and UITextView shared same UIFont property.
For UITextView, there's an extra textContainerInset can be set.
EDIT 2
A further look at the UITextView gives more clue about what can be achieved:
It actually has a textContainer property, which is a NSTextContainer.
The NSTextContainer class defines a region in which text is laid out. An NSLayoutManager object uses one or more NSTextContainer objects to determine where to break lines, lay out portions of text, and so on.
And it's been used by layoutManager, theoretically the padding to the top of first line of text could be found by usedRectForTextContainer(_:).
I will need to test this once I have a Mac in hand. :)
Based on the documentation zcui93 gives, font.ascender will give you the offset to the baseline within the font. To get the baseline of the font within the UITextView you need to add in textContainerInset.top:
extension UITextView {
func firstBaseline() -> CGFloat {
let font = self.font ?? UIFont.systemFontOfSize(UIFont.systemFontSize())
return font.ascender + textContainerInset.top
}
}
There's a little bit of assumption here in defaulting to the system font if no font is set, but I'm not sure what a better guess would be.
Running the following in the playground will demonstrate the effect:
class Container : UITextView {
override func drawRect(rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
CGContextSaveGState(context)
let containerRect = UIEdgeInsetsInsetRect(bounds, textContainerInset)
UIColor(red: 1, green: 0, blue: 0, alpha: 0.25).setFill()
CGContextFillRect(context, containerRect)
let baseline = firstBaseline()
UIColor(red: 1, green: 0, blue: 0, alpha: 0.75).setStroke()
CGContextSetLineWidth(context, 1)
CGContextMoveToPoint(context, containerRect.origin.x, baseline)
CGContextAddLineToPoint(context, containerRect.origin.x + containerRect.width, baseline)
CGContextStrokePath(context)
super.drawRect(rect)
}
}
let textView = Container(frame: CGRect(x: 0, y: 0, width: 100, height: 30))
textView.font = UIFont.systemFontOfSize(UIFont.systemFontSize())
textView.text = "My Text"
And the result:
In my case I was extending UITextField class and I just wanted to draw a line in the textfield baseline and I succeed with the following chunk:
self.svwLine = UIView(frame: CGRectMake(0,self.frame.size.height+(self.font?.descender)!,self.frame.size.width,2))
self.svwLine.backgroundColor = UIColor.whiteColor()
self.addSubview(self.svwLine)

Resources