How to calculate UITextView first baseline position relatively to the origin - ios

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)

Related

Text not vertically centered in UILabel for some fonts

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

iOS Text Drawing to Context is Clear

I have a simple application which I've added a custom line chart to. While drawing this chart in on of my UIViews everything is fine, but when I added the chart to a simplified view I suddenly experience a situation where all the text I draw is clear/not present.
There are some static variables involved because of the way the animations need to be handled (iOS/Android/Web) compatibility in the algorithm so the full example is rather complex.
To explain this I added a simple drawing method at the end of my CALayer.draw override
//TEST CODE
let box = CGRect(x: 0, y: 0, width: 100, height: 100)
let textToDraw = "I AM A TEST"
let font = UIFont.boldSystemFont(ofSize: 10)
let boxSize = fontSize.space
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .right
let attrs = [
NSFontAttributeName: font,
NSParagraphStyleAttributeName: paragraphStyle,
NSForegroundColorAttributeName: UIColor.black
]
ctx.setStrokeColor(UIColor.orange.cgColor)
ctx.beginPath()
ctx.move(to: CGPoint.zero)
ctx.addLine(to: CGPoint(x: box.width, y: box.height))
ctx.addLine(to: CGPoint(x: box.origin.x + box.width, y: box.origin.y + box.height))
ctx.strokePath()
// print("fontSize:\(fontSize) string:\(textToDraw)")
//textToDraw.draw(with: offset , options: .usesLineFragmentOrigin, attributes: attrs, context: nil)
textToDraw.draw( in: box, withAttributes: attrs)
However, and this is frustrating, if I visit the OTHER view where the chart draws (or any other custom drawings happens) I end up with this:
I'm having trouble tracing this down and I have a (justifiably) annoyed client.
The Code for "reloading the view" is basically view.dismiss(animated:true)
followed by a
performSegue("brokenView")
Has anyone run into this behavior before? Are there any pointers you can offer to getting the text to stick. I'm at the point I'm debating dropping a hidden UIView with drawing on my home screen to fix it but there has to be a better way.
The orange lines were put in to verify that my "bounding boxes" were not off the screen.
The layer in question is basically this:

iOS - UITextField: extend the bottom line for ANY resolution

I created a UITextFiled with a bottom line using this:
let Bottomline CALayer = ()
bottomLine.frame CGRect = (x: 0, y: usernameTextField.frame.height-7, width: usernameTextField.frame.width, height: 1)
bottomLine.backgroundColor = UIColor.black.cgColor
TextField.borderStyle = UITextBorderStyle.none
TextField.layer.addSublayer (Bottomline)
and the result of an iPhone 6 (right) is this:
Ok.
✄------------------------
The problem is to run the same application on a Pro iPad, because the
bottom line does not extend following the UITextField, but is shorter
This is the result on iPad Pro:
I do not understand why the bottom line does not follow the UITextField. When I called the bottom line I defined as:
bottomLine.frame CGRect = (x: 0, y: usernameTextField.frame.height-7, width: usernameTextField.frame.width, height: 1)
I have specified that the length of the line at the bottom must be:
width: usernameTextField.frame.width
What's the problem?
EDIT 1: The contrains are correct, because the UITextField adapts to
all types of resolution
EDIT:2 Thanks Matt! Now work!!!
I do not understand why the bottom line does not follow the UITextField
Because it's a layer. Layers do not automatically change size when their superlayer (the text field) changes size.
So, you need to redraw the bottom line every time the text field changes size.
At the moment, though, you are configuring the "bottom line" layer in viewDidLoad. So you are basing it on the frame that the text field has at that moment. But the text field has not yet attained its real size. Then it does change size, and meanwhile your "bottom line" layer just sits there — so now it is the wrong size.
An easy solution is to subclass UITextField and redraw the line every time layoutSubviews is called:
class MyTextField : UITextField {
var lineLayer : CALayer?
override func layoutSubviews() {
super.layoutSubviews()
self.lineLayer?.removeFromSuperlayer()
let bottomLine = CALayer()
bottomLine.frame = CGRect(x: 0, y: self.bounds.height-7, width: self.bounds.width, height: 1)
bottomLine.backgroundColor = UIColor.black.cgColor
self.layer.addSublayer(bottomLine)
self.lineLayer = bottomLine
}
}
If your text field is a MyTextField, it will behave exactly as you desire.

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

Swift Dynamically created UILabel shows up twice

I have written a custom graph UIView subclass, and I use it to graph some basic data, and insert some user-defined data. As a final step, I'd like to add a UILabel on top of the graph with the user-defined data-point called out.
I highlight the point, and then create and add the UILabel:
if(graphPoints[i] == highlightPoint){
var point2 = CGPoint(x:columnXPoint(i), y:columnYPoint(graphPoints[i]))
point2.x -= 8.0/2
point2.y -= 8.0/2
let circle2 = UIBezierPath(ovalInRect:
CGRect(origin: point2,
size: CGSize(width: 8.0, height: 8.0)))
highlightColor.setFill()
highlightColor.setStroke()
circle2.fill()
let circle = UIBezierPath(ovalInRect:
CGRect(origin: point,
size: CGSize(width: 5.0, height: 5.0)))
UIColor.whiteColor().setFill()
UIColor.whiteColor().setStroke()
circle.fill()
var pointLabel : UILabel = UILabel()
pointLabel.text = "Point = \(graphPoints[i])"
pointLabel.frame = CGRectMake(point2.x, point2.y, 100, 50)
self.addSubview(pointLabel)
} else {
let circle = UIBezierPath(ovalInRect:
CGRect(origin: point,
size: CGSize(width: 5.0, height: 5.0)))
UIColor.whiteColor().setFill()
UIColor.whiteColor().setStroke()
circle.fill()
}
This looks like it should work, but the UILabel is added twice. What am I doing wrong?
This code is probably in drawRect. You're doing subview-adding in drawRect which is incorrect. drawRect gets called at least twice as the view appears, and perhaps many times after that.
You should be overriding some early lifecycle method, like awakeFromNib() or the constructor. In that, construct your label and add it as a child view. This way the addSubView() happens once as it should. The label will be invisible having no contents, so don't worry about that.
In drawRect, just update all the necessary attributes of the label including the frame.
If you find you're actually needing lots of text "labels" that come and go, different quantity for every graph, you don't really want UILabels as subviews after all. Consider directly drawing text on screen with someString.drawAtPoint(...)

Resources