How do I vertically center UITextField Text? - ios

I am simply instantiating a UITextField and noticing that the text doesn't center vertically. Instead, it is flush with the top of my button, which I find kind of odd since I would expect the default to center it vertically. How can I center it vertically, or is there some default setting that I am missing?

textField.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
In swift use:
textField.contentVerticalAlignment = UIControlContentVerticalAlignment.center

This has potentially got several complicating factors, some alluded to in previous answers:
What you're trying to align (just numbers, just letters, just uppercase letters or a mix)
Placeholders
Clear button
What you're trying to align is important because of which point in the font should be vertically centered due to line height, ascenders, descenders etc.
(source: ilovetypography.com)
(Image thanks to http://ilovetypography.com/2009/01/14/inconspicuous-vertical-metrics/ )
When you're dealing with just numbers for example, the standard center alignment won't look quite right. Compare the difference in the two below, which use the same alignment (in the code below), one of which looks correct and the other which looks slightly off:
Not quite right with a mix of letters:
but looks right if it's just numbers
So, unfortunately, it may need a bit of trial and error and manual adjustment to get it looking visually correct.
I placed the below code in a subclass of UITextField. It calls superclass methods as this takes into account the clear button being present.
override func awakeFromNib() {
contentVerticalAlignment = UIControlContentVerticalAlignment.Center
}
override func textRectForBounds(bounds: CGRect) -> CGRect {
let boundsWithClear = super.textRectForBounds(bounds)
let delta = CGFloat(1)
return CGRect(x: boundsWithClear.minX, y: delta, width: boundsWithClear.width, height: boundsWithClear.height - delta/2)
}
override func editingRectForBounds(bounds: CGRect) -> CGRect {
let boundsWithClear = super.editingRectForBounds(bounds)
let delta = CGFloat(1)
return CGRect(x: boundsWithClear.minX, y: delta, width: boundsWithClear.width, height: boundsWithClear.height - delta/2)
}
override func placeholderRectForBounds(bounds: CGRect) -> CGRect {
let delta = CGFloat(1)
return CGRect(x: bounds.minX, y: delta, width: bounds.width, height: bounds.height - delta/2)
}

In the storyboard: Under control -> change the vertical as well as horizontal alignment, as to your needs.

This works fine for textField's text. But if you want to use placeholder text (a text that will appear while textfield is blank), on iOS 7 you will encounter problems.
I solved it by overriding TextField class and
- (void) drawPlaceholderInRect:(CGRect)rect
method.
Like this:
- (void) drawPlaceholderInRect:(CGRect)rect
{
[[UIColor blueColor] setFill];
CGRect placeholderRect = CGRectMake(rect.origin.x, (rect.size.height- self.font.pointSize)/2, rect.size.width, self.font.pointSize);
[[self placeholder] drawInRect:placeholderRect withFont:self.font lineBreakMode:NSLineBreakByWordWrapping alignment:self.textAlignment];
}
Works for both iOS7 and earlier versions.

You can also resolve the issue by adjusting the text baseline.
// positive: up, negative:down
// NSAttributedStringKey.baselineOffset:0
let attDic: [NSAttributedString.Key: Any] = [
.font: UIFont.systemFont(ofSize: 16),
.baselineOffset: 0
];
textfield.placeholder = NSAttributedString(string: "....", attributes: attDic);

Related

UITextField shrinks text before necessary

I have a UITextField for which I've set autoAdjustFontSizeToFitWidth to true and minimumFontSize to 0. The problem is the setting shrinks the text noticeably sooner than it really should. For example, here is an image of a UITextField with the above settings:
The green is the background color of the UITextField. In this example, the text has not shrunk yet, but no matter what I type as the next character the text field always begins shrinking; despite clearly being enough room on the left side for a few more characters. Here is another image with additional characters entered:
As you can see, there is a relatively large area on the left side that the text field won't place text in when auto adjusting. This is for a right aligned text field. The same can be said of center aligned text fields as well, where there is space on the left and right that seems as if an auto adjusting text field won't place text inside.
How do I get it so that auto adjusting text fields use the entire available space?
Update:
You can do the text calculations and font resizing manually. By doing so you will avoid hacks and future compatibility issues.
A simple implementation looks like this:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
var originalFont: UIFont!
override func viewDidLoad() {
super.viewDidLoad()
self.textField.font = self.textField.font?.withSize(44)
self.textField.adjustsFontSizeToFitWidth = false
self.originalFont = textField.font
self.textField.addTarget(self, action: #selector(textFieldDidChange), for: .editingChanged)
}
#objc
func textFieldDidChange(sender: UITextField) {
let textRect = sender.textRect(forBounds: sender.bounds)
let textWidth = textRect.width
var currentFont = self.originalFont!
var i = 0
while (i < 10) {
let unrestrictedTextWidth = sender.text!.boundingRect(with: CGSize(width: .greatestFiniteMagnitude,
height: textRect.height),
attributes: [.font : currentFont],
context: nil).width
if unrestrictedTextWidth <= textWidth {
break
}
let factor = textWidth / max(textWidth, unrestrictedTextWidth)
let originalSize = currentFont.pointSize
currentFont = self.originalFont!.withSize(originalSize * factor)
i += 1
}
sender.font = currentFont
}
}
Interestingly the actual relationship between text rect and font size is non-linear and non-trivial. So I added multiple iteration steps to approximate the correct size. I chose a maximum of 10 iterations to avoid infinite loops on very small sizes and rounding errors.
Original Answer:
There has always been some magic around UITextField and adjustsFontSizeToFitWidth. See for example this post from 2015 about how the initial font size affects the minimum font size:
https://stackoverflow.com/a/30881385/921573
A UITextField with:
Font size 17, minimum size 15 will go down to 15 if need be
Font size 17, minimum size 10 will only go down to 14
Font size 13, minimum size 4 will stay at 13
In my tests, setting the minimum font size in IB to 0 just gets ignored – in order so see the shrinking effect it has to be a small value like 1.
Setting it in code to 0 works fine.
So I think it is safe to say that UITextField might be considered historically buggy when it comes to adjustsFontSizeToFitWidth.
That being said, I found a workaround for you:
class FixedTextField: UITextField {
override func textRect(forBounds bounds: CGRect) -> CGRect {
let magicNumber = -15.0
if self.adjustsFontSizeToFitWidth {
return CGRect(
x: bounds.origin.x + magicNumber,
y: bounds.origin.y,
width: bounds.size.width - magicNumber,
height: bounds.size.height
)
} else {
return super.textRect(forBounds: bounds)
}
}
override func editingRect(forBounds bounds: CGRect) -> CGRect {
return self.textRect(forBounds: bounds)
}
}
This custom text field uses countermagic to mitigate the issue.
You may have to play with the magicNumber according to your font or dimensions or device. For me 15 works ok:
This works for me and the textField.textAlignment is set to .right (it will depend on how many characters you put in the textField though) :
class TextFieldOne: UITextField {
override func alignmentRect(forFrame frame: CGRect) -> CGRect {
// let newWidth = frame.width + 10 // if you want to reduce the right side too.
let x = frame.origin.x - 15 // suit yourself here
let newFrame = CGRect(x: x, y: frame.origin.y, width: frame.width, height: frame.height)
return newFrame
}
override func editingRect(forBounds bounds: CGRect) -> CGRect {
return self.alignmentRect(forFrame: self.bounds)
}
override func textRect(forBounds bounds: CGRect) -> CGRect {
return self.alignmentRect(forFrame: self.bounds)
}
}

How to change the size of blinking bar/line for specific UITextField?

i'm working on a project(Swift4,Xcode 9.2) which has a feature to get text input and the blinking bar/line should be of big size (it should be Square instead of bar/line), so i placed a UITextField for Text but i don't understand how to change the size of that blinking line/bar.
So, is it possible to change the size of line/bar? and if Yes then how to do it?
i know how to change the color of that line/bar but this is something different.
You can change the size by overriding the frame method for cursor as follows,
class CustomTextField: UITextField {
override func caretRect(for position: UITextPosition) -> CGRect {
var rect = super.caretRect(for: position)
let size = CGSize(width: 10, height: 50)
// Calculating center y
let y = rect.origin.y - (size.height - rect.size.height)/2
rect = CGRect(origin: CGPoint(x: rect.origin.x, y: y), size: size)
return rect
}
}
Set CustomTextField class in xib/storyboard identity inspector for that textField.
We can't change the cursor height, but we can do some trick, select your textfield and change your textfield border style as UITextBorderStyleNone
Check the below link which is already given answer
there after increase the font size of your textfield whatever you want, then you get the output as
There are some unnecessary lines of codes, so this is the revised:
class CustomTextField: UITextField {
override func caretRect(for position: UITextPosition) -> CGRect {
var rect = super.caretRect(for: position)
rect = CGRect(x: rect.origin.x, y: .zero, width: 15, height: 30)
return rect
}
}

Adding Bottom Line Border To A TextView - iOS

What is the best way to add a bottom line border to a TextView with a dynamic height?
I tried this:
func addBottomBorderWithColor(color: UIColor, width: CGFloat) {
let border = CALayer()
border.backgroundColor = color.CGColor
border.frame = CGRectMake(0, self.frame.size.height - width,
self.frame.size.width, width)
self.layer.addSublayer(border)
self.layer.masksToBounds = true
}
Swift 4 Version
func addBottomBorderWithColor() {
let border = CALayer()
border.backgroundColor = UIColor.black.cgColor
border.frame = CGRect(x: 0, y: yourTextArea.frame.height - 1, width: yourTextArea.frame.width, height: 1)
yourTextArea.layer.addSublayer(border)
self.view.layer.masksToBounds = true
}
Update:
I thought a lot about my original solution.
If you are subclassing UITextView to add a bottom line, then the bottom line had better be a subview of the text view itself, rather than its super view's.
And finally, I figure out one solution that can add bottom line as a subview of TextView itself and the bottom line will not move when the user scrolls the text of TextView. In your view controller, you can also change the frame of TextView dynamically, and the bottom line will also stick to the the bottom.
Here is the reference code:
import UIKit
class TextView: UITextView {
var border: UIView
var originalBorderFrame: CGRect
var originalInsetBottom: CGFloat
deinit {
removeObserver(self, forKeyPath: "contentOffset")
}
override var frame: CGRect {
didSet {
border.frame = CGRectMake(0, frame.height+contentOffset.y-border.frame.height, frame.width, border.frame.height)
originalBorderFrame = CGRectMake(0, frame.height-border.frame.height, frame.width, border.frame.height);
}
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if keyPath == "contentOffset" {
border.frame = CGRectOffset(originalBorderFrame, 0, contentOffset.y)
}
}
func addBottomBorderWithColor(color: UIColor, width: CGFloat) {
border.backgroundColor = color
border.frame = CGRectMake(0, frame.height+contentOffset.y-width, self.frame.width, width)
originalBorderFrame = CGRectMake(0, frame.height-width, self.frame.width, width)
textContainerInset.bottom = originalInsetBottom+width
}
}
Note: Since I used to write code in Objective-C, I am not familiar with Swift. The code above is only for your reference (though I have tested the corresponding Objective-C code, and it works as expected):
As you can see, there is no initialisation code. I have tried to write such code, but it always shows an error and I still have no idea about that. Just make sure to add the below code to your TextView initialisation code:
border = UIView()
addSubview(border)
originalInsetBottom = textContainerInset.bottom
addObserver(self, forKeyPath: "contentOffset", options: .New, context: nil)
I am not familiar with the concept of optional value, wrap, unwrap... So you should add ?, ! to the code if needed.
Original answer:
Does self in your code mean TextView?
If so, when you add border as a sublayer of the TextView, the border will move up and down when the user scrolls the text of TextView.
Try to add border as a sublayer of TextView's super view rather than TextView itself.
Here is the code (Note that I change border from CALayer to UIView):
func addBottomBorderWithColor(color: UIColor, width: CGFloat) {
let border = UIView()
border.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y+self.frame.height-width, textView.frame.width, width)
border.backgroundColor = color
self.superview!.insertSubview(border, aboveSubview: textView)
}
Here is the capture:
PS. I suggest you to change the second paramter name from width to height since width is ambiguous in this context.
It works better with following line:
border.autoresizingMask = [.flexibleWidth, .flexibleHeight]
extension UITextView {
func addBottomBorderWithColor(color: UIColor, height: CGFloat) {
let border = UIView()
border.autoresizingMask = [.flexibleWidth, .flexibleHeight]
border.frame = CGRect(self.frame.origin.x,
self.frame.origin.y+self.frame.height-height, self.frame.width, height)
border.backgroundColor = color
self.superview!.insertSubview(border, aboveSubview: self)
}
}
I've tried other answers, they are either hard to implement, or come with bugs, however, I've found a simple and perhaps the best way to achieve this:
Just add a UIView next to your UITextView, with a height of 1 (or maybe 2 of your choice), set up the constraints, make sure the UIView is above the UITextView.
All of this can be achieved in storyboard so I'm not giving code here, since this UIView can be replaced with an UIImageView, you can add mustache to your UITextView if you wish
:)
Set the mask to bounds to be false then only it show the bounds of the line that you want to show..
Try following code it may helps you
func addBottomBorderWithColor(color: UIColor, width: CGFloat) {
let border = CALayer()
border.backgroundColor = color.CGColor
border.frame = CGRectMake(0, yourTextview.frame.origin.y+yourTextview.frame.size.height+1 , width, 1)
self.view.layer.addSublayer(border)
self.view.layer.masksToBounds = true
}
Sometimes it's easier to just keep things a little more independent of one another.
UIImageView * border = [UIImageView new];
border.frame = CGRectMake(0,y,100,0.5f);
border.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.3f];
[self.view addSubview:border];

Vertically align text within a UILabel (Note : Using AutoLayout)

I am Copying the same Question asked Before Question.
I have tried the solutions given and was not able to solve it since sizetofit was not effective when I use Autolayout.
The expected display is like below.
Edit
In my original answer I was using the paragraph style of the label. Turns out that for multi-line labels this actually prevents the label from being multi-line. As a result I removed it from the calculation. See more about this in Github
For those of you more comfortable with using Open Source definitely look at TTTAttributedLabel where you can set the label's text alignment to TTTAttributedLabelVerticalAlignmentTop
The trick is to subclass UILabel and override drawTextInRect. Then enforce that the text is drawn at the origin of the label's bounds.
Here's a naive implementation that you can use right now:
Swift
#IBDesignable class TopAlignedLabel: UILabel {
override func drawTextInRect(rect: CGRect) {
if let stringText = text {
let stringTextAsNSString = stringText as NSString
var labelStringSize = stringTextAsNSString.boundingRectWithSize(CGSizeMake(CGRectGetWidth(self.frame), CGFloat.max),
options: NSStringDrawingOptions.UsesLineFragmentOrigin,
attributes: [NSFontAttributeName: font],
context: nil).size
super.drawTextInRect(CGRectMake(0, 0, CGRectGetWidth(self.frame), ceil(labelStringSize.height)))
} else {
super.drawTextInRect(rect)
}
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
layer.borderWidth = 1
layer.borderColor = UIColor.blackColor().CGColor
}
}
Swift 3
#IBDesignable class TopAlignedLabel: UILabel {
override func drawText(in rect: CGRect) {
if let stringText = text {
let stringTextAsNSString = stringText as NSString
let labelStringSize = stringTextAsNSString.boundingRect(with: CGSize(width: self.frame.width,height: CGFloat.greatestFiniteMagnitude),
options: NSStringDrawingOptions.usesLineFragmentOrigin,
attributes: [NSFontAttributeName: font],
context: nil).size
super.drawText(in: CGRect(x:0,y: 0,width: self.frame.width, height:ceil(labelStringSize.height)))
} else {
super.drawText(in: rect)
}
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
layer.borderWidth = 1
layer.borderColor = UIColor.black.cgColor
}
}
Objective-C
IB_DESIGNABLE
#interface TopAlignedLabel : UILabel
#end
#implementation TopAlignedLabel
- (void)drawTextInRect:(CGRect)rect {
if (self.text) {
CGSize labelStringSize = [self.text boundingRectWithSize:CGSizeMake(CGRectGetWidth(self.frame), CGFLOAT_MAX)
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
attributes:#{NSFontAttributeName:self.font}
context:nil].size;
[super drawTextInRect:CGRectMake(0, 0, ceilf(CGRectGetWidth(self.frame)),ceilf(labelStringSize.height))];
} else {
[super drawTextInRect:rect];
}
}
- (void)prepareForInterfaceBuilder {
[super prepareForInterfaceBuilder];
self.layer.borderWidth = 1;
self.layer.borderColor = [UIColor blackColor].CGColor;
}
#end
Since I used IBDesignable you can add this label to a storyboard and watch it go, this is what it looks like for me
If you're not restricted by having UILabel of fixed size, instead of aligning the text within a UILabel, simply use ≥ constraint on the given label to change the size of it.
It's the most elegant solution using Auto Layout. Don't forget to set numberOfLines to zero though.
You can use UITextView instead of UILabel:
Uncheck "Scrolling enabled"
Uncheck "Editable"
Uncheck "Selectable"
Set background color to ClearColor
I had the same problem, and this is how I solved it. I just edited the Baseline under Attribute Inspector for the Label. Set it to "Align Centers".
Instead, I changed the Bottom Space Constant to priority #250 and solved my problem. And my label has height constant with <= constant
You would do that by removing the minimum height.
If you need the minimum height to something else below the label then you would use a container view that resized based on the label contents but used a minimum.
Auto layout only work with edges/sizes of controller, not with controllers content.so its not a good idea to use auto layout to display your label text on top of first line.
according to me sizetofit is a best option to do so.
I used #Daniel Golasko's solution and whenever the text inside the UILabel was longer than the UILabel could contain, the text would start moving down instead of staying aligned to top.
I changed this line to make sure the text is aligned properly
[super drawTextInRect:CGRectMake(0, 0, ceilf(CGRectGetWidth(self.frame)),MIN(ceilf(labelStringSize.height), self.frame.size.height))];
I had a similiar issue where there were 3 labels. The middle label could have much longer text than the other two, so its height could grow much larger.
Like this:
I set the middle label's bottom space constraint to be >= the bottom label.
That solved my problem.
Here's an improvement on the Swift 3 solution by Daniel Galasko (here you can also set the maximum line number without an offset on the top):
import UIKit
#IBDesignable class TopAlignedLabel: UILabel {
override func drawText(in rect: CGRect) {
if let stringText = text {
let stringTextAsNSString = stringText as NSString
let labelString = stringTextAsNSString.boundingRect(with: CGSize(width: frame.width, height: .greatestFiniteMagnitude),
options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
super.drawText(in: CGRect(x: 0, y: 0, width: frame.width, height: ceil(labelString.size.height) > frame.height ? frame.height : ceil(labelString.size.height)))
} else {
super.drawText(in: rect)
}
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
layer.borderWidth = 1
layer.borderColor = UIColor.black.cgColor
}
}
There is an easy solution for cases where the height of a label doesn't need to be constant: put your Label in a Stack View. Be sure to add leading and trailing constants to the Stack View. Here is a screenshot of how to do it in storyboard:
Swift 4
You should subclass UILabel and override text display rendering.
class UITopAlignedLabel: UILabel {
override func drawText(in rect: CGRect) {
guard let string = text else {
super.drawText(in: rect)
return
}
let size = (string as NSString).boundingRect(
with: CGSize(width: rect.width, height: .greatestFiniteMagnitude),
options: [.usesLineFragmentOrigin],
attributes: [.font: font],
context: nil).size
var rect = rect
rect.size.height = size.height.rounded()
super.drawText(in: rect)
}
}
You can try if button [button setContentVerticalAlignment:UIControlContentVerticalAlignmentTop];
Edit :
You can try with this if you want to use label only:
https://stackoverflow.com/a/11278660/1223897
For me, I didn't set the height constraint, the text always grows from the top of the label.
The constraints for this label are top, left, right.
By the way, my label has fixed line numbers, so no worries about the height.
#IBInspectable var alignTop: Bool = false
func setAlignTop() {
let text = self.text!
let lines = text.characters.split(separator: "\n").count
if lines < self.numberOfLines {
var newLines = ""
for _ in 0..<(self.numberOfLines - lines) {
newLines = newLines.appending("\n ")
}
self.text! = text.appending(newLines)
}
}
override var text: String? {
didSet {
if alignTop {
self.setAlignTop()
}
}
}
use this my class, you can change text alignment by contentMode.
supported case: .top, .bottom, .left, .right, .topLeft, .topRight, .bottomLeft, .bottomRight
Swift4
import Foundation
import UIKit
#IBDesignable
class UIAlignedLabel: UILabel {
override func drawText(in rect: CGRect) {
if let text = text as NSString? {
func defaultRect(for maxSize: CGSize) -> CGRect {
let size = text
.boundingRect(
with: maxSize,
options: NSStringDrawingOptions.usesLineFragmentOrigin,
attributes: [
NSAttributedStringKey.font: font
],
context: nil
).size
let rect = CGRect(
origin: .zero,
size: CGSize(
width: min(frame.width, ceil(size.width)),
height: min(frame.height, ceil(size.height))
)
)
return rect
}
switch contentMode {
case .top, .bottom, .left, .right, .topLeft, .topRight, .bottomLeft, .bottomRight:
let maxSize = CGSize(width: frame.width, height: frame.height)
var rect = defaultRect(for: maxSize)
switch contentMode {
case .bottom, .bottomLeft, .bottomRight:
rect.origin.y = frame.height - rect.height
default: break
}
switch contentMode {
case .right, .topRight, .bottomRight:
rect.origin.x = frame.width - rect.width
default: break
}
super.drawText(in: rect)
default:
super.drawText(in: rect)
}
} else {
super.drawText(in: rect)
}
}
}
In the Interface Builder, just make the height <= some value instead of =. This will enable to text to start at the top and expand the height as needed. For example, I have a label with a height proportional to the size of the main view. So my height constraint looks like this:
Height Constraint

UITextField cursor size [duplicate]

i'm working on a project(Swift4,Xcode 9.2) which has a feature to get text input and the blinking bar/line should be of big size (it should be Square instead of bar/line), so i placed a UITextField for Text but i don't understand how to change the size of that blinking line/bar.
So, is it possible to change the size of line/bar? and if Yes then how to do it?
i know how to change the color of that line/bar but this is something different.
You can change the size by overriding the frame method for cursor as follows,
class CustomTextField: UITextField {
override func caretRect(for position: UITextPosition) -> CGRect {
var rect = super.caretRect(for: position)
let size = CGSize(width: 10, height: 50)
// Calculating center y
let y = rect.origin.y - (size.height - rect.size.height)/2
rect = CGRect(origin: CGPoint(x: rect.origin.x, y: y), size: size)
return rect
}
}
Set CustomTextField class in xib/storyboard identity inspector for that textField.
We can't change the cursor height, but we can do some trick, select your textfield and change your textfield border style as UITextBorderStyleNone
Check the below link which is already given answer
there after increase the font size of your textfield whatever you want, then you get the output as
There are some unnecessary lines of codes, so this is the revised:
class CustomTextField: UITextField {
override func caretRect(for position: UITextPosition) -> CGRect {
var rect = super.caretRect(for: position)
rect = CGRect(x: rect.origin.x, y: .zero, width: 15, height: 30)
return rect
}
}

Resources