How to add padding to UITextField? [duplicate] - iphone
I would like to inset the text of a UITextField.
Is this possible?
Overriding -textRectForBounds: will only change the inset of the placeholder text. To change the inset of the editable text, you need to also override -editingRectForBounds:
// placeholder position
- (CGRect)textRectForBounds:(CGRect)bounds {
return CGRectInset(bounds, 10, 10);
}
// text position
- (CGRect)editingRectForBounds:(CGRect)bounds {
return CGRectInset(bounds, 10, 10);
}
I was able to do it through:
myTextField.layer.sublayerTransform = CATransform3DMakeTranslation(5, 0, 0);
Of course remember to import QuartzCore and also add the Framework to your project.
If you need just a left margin, you can try this:
UItextField *textField = [[UITextField alloc] initWithFrame:...];
UIView *leftView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10, textField.frame.size.height)];
leftView.backgroundColor = textField.backgroundColor;
textField.leftView = leftView;
textField.leftViewMode = UITextFieldViewModeAlways;
It works for me. I hope this may help.
In a class derived from UITextField, override at least this two methods:
- (CGRect)textRectForBounds:(CGRect)bounds;
- (CGRect)editingRectForBounds:(CGRect)bounds;
It might be as simple as this if you have no additional content:
return CGRectInset(bounds , 10, 10);
UITextField provides several positioning methods you can override.
How about an #IBInspectable, #IBDesignable swift class.
#IBDesignable
class TextField: UITextField {
#IBInspectable var insetX: CGFloat = 6 {
didSet {
layoutIfNeeded()
}
}
#IBInspectable var insetY: CGFloat = 6 {
didSet {
layoutIfNeeded()
}
}
// placeholder position
override func textRectForBounds(bounds: CGRect) -> CGRect {
return CGRectInset(bounds , insetX , insetY)
}
// text position
override func editingRectForBounds(bounds: CGRect) -> CGRect {
return CGRectInset(bounds , insetX , insetY)
}
}
You'll see this in your storyboard.
Update - Swift 3
#IBDesignable
class TextField: UITextField {
#IBInspectable var insetX: CGFloat = 0
#IBInspectable var insetY: CGFloat = 0
// placeholder position
override func textRect(forBounds bounds: CGRect) -> CGRect {
return bounds.insetBy(dx: insetX, dy: insetY)
}
// text position
override func editingRect(forBounds bounds: CGRect) -> CGRect {
return bounds.insetBy(dx: insetX, dy: insetY)
}
}
If you have a clear button, the accepted answer won't work for you. We should also guard against Apple changing things in the future by calling super.
So, to make sure the text doesn't overlap the clear button, let's get the 'default' value from super first, then adjust as necessary.
This code will add a 10px insets on the top, left and bottom of the text field:
#interface InsetTextField : UITextField
#end
#implementation InsetTextField
// Placeholder position
- (CGRect)textRectForBounds:(CGRect)bounds {
CGRect rect = [super textRectForBounds:bounds];
UIEdgeInsets insets = UIEdgeInsetsMake(10, 10, 10, 0);
return UIEdgeInsetsInsetRect(rect, insets);
}
// Text position
- (CGRect)editingRectForBounds:(CGRect)bounds {
CGRect rect = [super editingRectForBounds:bounds];
UIEdgeInsets insets = UIEdgeInsetsMake(10, 10, 10, 0);
return UIEdgeInsetsInsetRect(rect, insets);
}
// Clear button position
- (CGRect)clearButtonRectForBounds:(CGRect)bounds {
CGRect rect = [super clearButtonRectForBounds:bounds];
return CGRectOffset(rect, -5, 0);
}
#end
Note: UIEdgeInsetsMake takes parameters in the order: top, left, bottom, right.
Thought I would supply a Swift Solution
import UIKit
class TextField: UITextField {
let inset: CGFloat = 10
// placeholder position
override func textRectForBounds(bounds: CGRect) -> CGRect {
return CGRectInset(bounds , inset , inset)
}
// text position
override func editingRectForBounds(bounds: CGRect) -> CGRect {
return CGRectInset(bounds , inset , inset)
}
override func placeholderRectForBounds(bounds: CGRect) -> CGRect {
return CGRectInset(bounds, inset, inset)
}
}
Swift 3+
import UIKit
class TextField: UITextField {
let inset: CGFloat = 10
// placeholder position
override func textRect(forBounds: CGRect) -> CGRect {
return forBounds.insetBy(dx: self.inset , dy: self.inset)
}
// text position
override func editingRect(forBounds: CGRect) -> CGRect {
return forBounds.insetBy(dx: self.inset , dy: self.inset)
}
override func placeholderRect(forBounds: CGRect) -> CGRect {
return forBounds.insetBy(dx: self.inset, dy: self.inset)
}
}
Swift
class TextField: UITextField {
let inset: CGFloat = 8
// placeholder position
override func textRect(forBounds bounds: CGRect) -> CGRect {
return bounds.insetBy(dx: inset, dy: inset)
}
// text position
override func editingRect(forBounds bounds: CGRect) -> CGRect {
return bounds.insetBy(dx: inset, dy: inset)
}
}
Using textRectForBounds: is the correct approach. I have wrapped this up in my subclass so you can simply use textEdgeInsets. See SSTextField.
You can set text inset for UITextField by setting the leftView.
Like this:
UITextField *yourTextField = [[UITextField alloc] init];
UIView *leftView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 5, 5)];
leftView.backgroundColor = [UIColor clearColor];
yourTextField.leftViewMode = UITextFieldViewModeAlways;
yourTextField.leftView = leftView;
Swift
// adjust place holder text
let paddingView = UIView(frame: CGRectMake(0, 0, 10, usernameOrEmailField.frame.height))
usernameOrEmailField.leftView = paddingView
usernameOrEmailField.leftViewMode = UITextFieldViewMode.Always
For people, who are searching for a easier solution.
Add the UITextField inside a UIView. To simulate an inset around text field I keep 10 px left and width is 20px less than the view. For a rounded corner border around the textfield, use the view's border
viewBG.layer.cornerRadius = 8.0;
viewBG.layer.borderColor = [UIColor darkGrayColor].CGColor;
viewBG.layer.borderWidth = 1.0;
A good approach to add padding to UITextField is to subclass UITextField and add an edgeInsets property. You then set the edgeInsets and the UITextField will be drawn accordingly. This will also function correctly with a custom leftView or rightView set.
OSTextField.h
#import <UIKit/UIKit.h>
#interface OSTextField : UITextField
#property (nonatomic, assign) UIEdgeInsets edgeInsets;
#end
OSTextField.m
#import "OSTextField.h"
#implementation OSTextField
- (id)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if (self) {
self.edgeInsets = UIEdgeInsetsMake(0, 0, 0, 0);
}
return self;
}
-(id)initWithCoder:(NSCoder *)aDecoder{
self = [super initWithCoder:aDecoder];
if(self){
self.edgeInsets = UIEdgeInsetsMake(0, 0, 0, 0);
}
return self;
}
- (CGRect)textRectForBounds:(CGRect)bounds {
return [super textRectForBounds:UIEdgeInsetsInsetRect(bounds, self.edgeInsets)];
}
- (CGRect)editingRectForBounds:(CGRect)bounds {
return [super editingRectForBounds:UIEdgeInsetsInsetRect(bounds, self.edgeInsets)];
}
#end
Swift 5 version of Christopher's answer with extra usage sample
import UIKit
private class InsetTextField: UITextField {
var insets: UIEdgeInsets
init(insets: UIEdgeInsets) {
self.insets = insets
super.init(frame: .zero)
}
required init(coder aDecoder: NSCoder) {
fatalError("not intended for use from a NIB")
}
// placeholder position
override func textRect(forBounds bounds: CGRect) -> CGRect {
return super.textRect(forBounds: bounds.inset(by: insets))
}
// text position
override func editingRect(forBounds bounds: CGRect) -> CGRect {
return super.editingRect(forBounds: bounds.inset(by: insets))
}
}
extension UITextField {
class func textFieldWithInsets(insets: UIEdgeInsets) -> UITextField {
return InsetTextField(insets: insets)
}
}
Usage: -
class ViewController: UIViewController {
private let passwordTextField: UITextField = {
let textField = UITextField.textFieldWithInsets(insets: UIEdgeInsets(top: 10, left: 15, bottom: 10, right: 15))
// ---
return textField
}()
}
I did this in IB where I created a UIView Behind the textView that was a little bit longer. With the textField background color set to clear.
Swift 3 / Designable in Interface builder / Separate horizontal & vertical insects / usable out of the box
#IBDesignable
class TextFieldWithPadding: UITextField {
#IBInspectable var horizontalInset: CGFloat = 0
#IBInspectable var verticalInset: CGFloat = 0
override func textRect(forBounds bounds: CGRect) -> CGRect {
return bounds.insetBy(dx: horizontalInset, dy: verticalInset)
}
override func editingRect(forBounds bounds: CGRect) -> CGRect {
return bounds.insetBy(dx: horizontalInset , dy: verticalInset)
}
override func placeholderRect(forBounds bounds: CGRect) -> CGRect {
return bounds.insetBy(dx: horizontalInset, dy: verticalInset)
}
}
usage:
&
Swift 4.2 version:
import UIKit
class InsetTextField: UITextField {
let inset: CGFloat = 10
override func textRect(forBounds bounds: CGRect) -> CGRect {
return bounds.insetBy(dx: inset, dy: inset)
}
override func editingRect(forBounds bounds: CGRect) -> CGRect {
return bounds.insetBy(dx: inset, dy: inset)
}
override func placeholderRect(forBounds bounds: CGRect) -> CGRect {
return bounds.insetBy(dx: inset, dy: inset)
}
}
It's the quickest way I've found without doing any subclasses:
UIView *spacerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 10., 10.)];
[textField setLeftViewMode:UITextFieldViewModeAlways];
[textField setLeftView:spacerView];
In Swift:
let spacerView = UIView(frame:CGRect(x:0, y:0, width:10, height:10))
textField.leftViewMode = UITextFieldViewMode.Always
textField.leftView = spacerView
It's absurd you have to subclass, since UITextField already implements the methods, as #Adam Waite points out. Here's a swift extension that exposes a factory method, also available in our categories repo:
private class InsetTextField: UITextField {
var insets: UIEdgeInsets
init(insets: UIEdgeInsets) {
self.insets = insets
super.init(frame: CGRectZero)
}
required init(coder aDecoder: NSCoder) {
fatalError("not intended for use from a NIB")
}
// placeholder position
override func textRectForBounds(bounds: CGRect) -> CGRect {
return super.textRectForBounds(UIEdgeInsetsInsetRect(bounds, insets))
}
// text position
override func editingRectForBounds(bounds: CGRect) -> CGRect {
return super.editingRectForBounds(UIEdgeInsetsInsetRect(bounds, insets))
}
}
extension UITextField {
class func textFieldWithInsets(insets: UIEdgeInsets) -> UITextField {
return InsetTextField(insets: insets)
}
}
Here is the same subclassed UITextField written in Swift 3. It is quite different from prior versions of Swift, as you'll see:
import UIKit
class MyTextField: UITextField
{
let inset: CGFloat = 10
// placeholder position
override func textRect(forBounds bounds: CGRect) -> CGRect
{
return bounds.insetBy(dx: inset, dy: inset)
}
// text position
override func editingRect(forBounds bounds: CGRect) -> CGRect
{
return bounds.insetBy(dx: inset, dy: inset)
}
override func placeholderRect(forBounds bounds: CGRect) -> CGRect
{
return bounds.insetBy(dx: inset, dy: inset)
}
}
Incidentally, you can also do something like the following, if you want to control the inset of just one side. This particular example of adjusting only the left inset comes in handy if you place an image on top of the UITextField but you want it to appear to the user to be within the text field:
override func editingRect(forBounds bounds: CGRect) -> CGRect
{
return CGRect.init(x: bounds.origin.x + inset, y: bounds.origin.y, width: bounds.width - inset, height: bounds.height)
}
You can adjust the positioning of the text within a text field by making it a subclass of UITextField and overriding the -textRectForBounds: method.
I subclased UITextField to handle this that supports left, top, right and bottom inset, and clear button positioning as well.
MRDInsetTextField.h
#import <UIKit/UIKit.h>
#interface MRDInsetTextField : UITextField
#property (nonatomic, assign) CGRect inset;
#end
MRDInsetTextField.m
#import "MRDInsetTextField.h"
#implementation MRDInsetTextField
- (id)init
{
self = [super init];
if (self) {
_inset = CGRectZero;
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
_inset = CGRectZero;
}
return self;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
_inset = CGRectZero;
}
return self;
}
- (void)setInset:(CGRect)inset {
_inset = inset;
[self setNeedsLayout];
}
- (CGRect)getRectForBounds:(CGRect)bounds withInset:(CGRect)inset {
CGRect newRect = CGRectMake(
bounds.origin.x + inset.origin.x,
bounds.origin.y + inset.origin.y,
bounds.origin.x + bounds.size.width - inset.origin.x - inset.size.width,
bounds.origin.y + bounds.size.height - inset.origin.y - inset.size.height
);
return newRect;
}
- (CGRect)textRectForBounds:(CGRect)bounds {
return [self getRectForBounds:[super textRectForBounds:bounds] withInset:_inset];
}
- (CGRect)placeholderRectForBounds:(CGRect)bounds {
return [self getRectForBounds:bounds withInset:_inset];
}
- (CGRect)editingRectForBounds:(CGRect)bounds {
return [self getRectForBounds:[super editingRectForBounds:bounds] withInset:_inset];
}
- (CGRect)clearButtonRectForBounds:(CGRect)bounds {
return CGRectOffset([super clearButtonRectForBounds:bounds], -_inset.size.width, _inset.origin.y/2 - _inset.size.height/2);
}
#end
Example of usage where *_someTextField* comes from nib/storyboard view with MRDInsetTextField custom class
[(MRDInsetTextField*)_someTextField setInset:CGRectMake(5, 0, 5, 0)]; // left, top, right, bottom inset
This is not as short as the other examples, but takes an entirely different approach to solving this problem. Note, the caret will still begin flush to the left edge but the text will be properly indented when typed/displayed. This works without subclassing if your looking for just a left margin and you are already using UITextFieldDelegate for your text fields. You need to set both the default text attributes and the typing attributes. You set the default text attributes when you create the text field. The typing attributes you need to set in the delegate. If you are also using a placeholder you will want to set that to the same margin as well. Putting it altogether you get something like this.
First create a category on the UITextField class.
// UITextField+TextAttributes.h
#import <UIKit/UIKit.h>
#interface UITextField (TextAttributes)
- (void)setIndent:(CGFloat)indent;
#end
// UITextField+TextAttributes.m
#import "UITextField+TextAttributes.h"
#implementation UITextField (TextAttributes)
- (void)setTextAttributes:(NSDictionary*)textAttributes indent:(CGFloat)indent
{
if (!textAttributes) return;
NSMutableParagraphStyle *paragraphStyle = [textAttributes objectForKey:NSParagraphStyleAttributeName];
paragraphStyle.firstLineHeadIndent = indent;
paragraphStyle.headIndent = indent;
}
- (void)setIndent:(CGFloat)indent
{
[self setTextAttributes:self.defaultTextAttributes indent:indent];
[self setTextAttributes:self.typingAttributes indent:indent];
}
#end
Then, if you are using placed holders make sure to use an attributed placeholder setting the same indent. Create a default attributed dictionary with the proper attributes, something like this:
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.firstLineHeadIndent = 7;
paragraphStyle.headIndent = 7;
NSDictionary *placeholderAttributes = [NSDictionary dictionaryWithObjectsAndKeys: paragraphStyle, NSParagraphStyleAttributeName, nil];
Then, import the above category and whenever you create a text field set the default indent, the delegate and use the default placeholder attributes defined above. For example:
UITextField *textField = [[UITextField alloc] init];
textField.indent = 7;
textField.delegate = self;
textField.attributedPlaceholder = [[NSAttributedString alloc] initWithString:#"Placeholder Text" attributes:placeholderAttributes];
Lastly, in the delegate, implement the textFieldDidBeginEditing method, something like this:
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
textField.indent = 7;
}
A solution that actually works and covers all cases:
Should use offsetBy not insetBy.
Should also call the super function to get the original Rect.
Bounds is faulty. you need to offset the original X, Y. Bounds have X, Y as zeros.
Original x, y can be non-zero for instance when setting the leftView of the UITextField.
Sample:
override func textRect(forBounds bounds: CGRect) -> CGRect {
return super.textRect(forBounds: bounds).offsetBy(dx: 0.0, dy: 4)
}
override func editingRect(forBounds bounds: CGRect) -> CGRect {
return super.editingRect(forBounds: bounds).offsetBy(dx: 0.0, dy: 4)
}
I found the option posted by roberto.buratti the fastest solution, here it is in Swift:
let leftView = UIView(frame: CGRect(x: 0, y: 0, width: 10, height: textField.frame.size.height))
leftView.backgroundColor = textField.backgroundColor
textField.leftView = leftView
textField.leftViewMode = UITextField.ViewMode.always
I normally try avoid subclassing but this works if you have already:
// add a property
#property (nonatomic) UIEdgeInsets edgeInsets;
// and override:
- (CGRect)textRectForBounds:(CGRect)bounds
{
return [super textRectForBounds:UIEdgeInsetsInsetRect(bounds, self.edgeInsets)];
}
- (CGRect)editingRectForBounds:(CGRect)bounds
{
return [super editingRectForBounds:UIEdgeInsetsInsetRect(bounds, self.edgeInsets)];
}
To throw in another solution that has no need for subclassing:
UITextField *txtField = [UITextField new];
txtField.borderStyle = UITextBorderStyleRoundedRect;
// grab BG layer
CALayer *bgLayer = txtField.layer.sublayers.lastObject;
bgLayer.opacity = 0.f;
// add new bg view
UIView *bgView = [UIView new];
bgView.backgroundColor = [UIColor whiteColor];
bgView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
bgView.userInteractionEnabled = NO;
[txtField addSubview: bgView];
[txtField sendSubviewToBack: bgView];
Tested with iOS 7 and iOS 8. Both working. Still there might be the chance of Apple modifying the UITextField's layer hierarchy screwing up things badly.
Here's a comprehensive Swift answer that includes a leftView (custom icon) and a custom clear button, both set in Interface Builder with customizable insets.
import UIKit
#IBDesignable
class InsetTextField: UITextField {
#IBInspectable var leftInset:CGFloat = 0
#IBInspectable var rightInset:CGFloat = 0
#IBInspectable var icon:UIImage? { didSet {
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 16, height: 16))
imageView.image = icon
self.leftView = imageView
self.leftViewMode = .Always
} }
#IBInspectable var clearButton:UIImage? { didSet {
let button = UIButton(type: .Custom)
button.setImage(clearButton, forState: .Normal)
button.addTarget(self, action: "clear", forControlEvents: UIControlEvents.TouchUpInside)
button.frame = CGRect(x: 0, y: 0, width: 18, height: 18)
self.rightView = button
self.rightViewMode = .WhileEditing
} }
func clear() {
self.text = ""
}
override func leftViewRectForBounds(bounds: CGRect) -> CGRect {
var height:CGFloat = 0
var width:CGFloat = 0
if let leftView = self.leftView {
height = leftView.bounds.height
width = leftView.bounds.width
}
return CGRect(x: leftInset, y: bounds.height/2 - height/2, width: width, height: height)
}
override func rightViewRectForBounds(bounds: CGRect) -> CGRect {
var height:CGFloat = 0
var width:CGFloat = 0
if let rightView = self.rightView {
height = rightView.bounds.height
width = rightView.bounds.width
}
return CGRect(x: bounds.width - width - rightInset, y: bounds.height/2 - height/2, width: width, height: height)
}
}
You might need this solution which also supports leftView and rightView. 😃
class InsettedTextField: UITextField {
private let textInset: UIEdgeInsets
var rightViewInset: CGRect {
rightView.flatMap { $0.frame } ?? .zero
}
var leftViewInset: CGRect {
leftView.flatMap { $0.frame } ?? .zero
}
/// Init the text field with insets.
init(textInset: UIEdgeInsets) {
self.textInset = textInset
super.init(frame: .zero)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func textRect(forBounds bounds: CGRect) -> CGRect {
bounds
.inset(by: textInset)
.inset(by: UIEdgeInsets(top: 0, left: leftViewInset.width, bottom: 0, right: rightViewInset.width))
}
override func placeholderRect(forBounds bounds: CGRect) -> CGRect {
bounds
.inset(by: textInset)
.inset(by: UIEdgeInsets(top: 0, left: leftViewInset.width, bottom: 0, right: rightViewInset.width))
}
override func editingRect(forBounds bounds: CGRect) -> CGRect {
bounds
.inset(by: textInset)
.inset(by: UIEdgeInsets(top: 0, left: leftViewInset.width, bottom: 0, right: rightViewInset.width))
}
}
If you want to change TOP and LEFT indent only then
// placeholder position
- (CGRect)textRectForBounds:(CGRect)bounds {
CGRect frame = bounds;
frame.origin.y = 3;
frame.origin.x = 5;
bounds = frame;
return CGRectInset( bounds , 0 , 0 );
}
// text position
- (CGRect)editingRectForBounds:(CGRect)bounds {
CGRect frame = bounds;
frame.origin.y = 3;
frame.origin.x = 5;
bounds = frame;
return CGRectInset( bounds , 0 , 0 );
}
Related
how to pinch zoom in on code created scrollview
My code creates a scrollview and image view that displays a picture array from a previous view controller. However, I am trying to implement code to make it so the user may zoom in on a picture. But what ever I do, it does not work. Any suggestions on what I am doing wrong, or where to implement the zoom in code? Thank you! import UIKit class DestinationVC: UIViewController { #IBOutlet weak var myScrollView: UIScrollView! var mySelectedProtocol:Protocol? var pageControl:UIPageControl? var currentPageIndex:Int=0 fileprivate var count:Int=0 override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. if mySelectedProtocol == nil { self.navigationController?.popViewController(animated: true) } if mySelectedProtocol!.imagesName!.count == 0 { self.navigationController?.popViewController(animated: true) } /// We have Data print("Img Array with Name ==> \(mySelectedProtocol?.imagesName ?? [])") DispatchQueue.main.async { self.addPageView() } } func viewForZooming(in scrollView: UIScrollView) -> UIView? { myScrollView.delegate = self myScrollView.minimumZoomScale = 1.0 myScrollView.maximumZoomScale = 5.0 return myScrollView } private func addPageView() { myScrollView.backgroundColor=UIColor.black myScrollView.isUserInteractionEnabled=true myScrollView.showsHorizontalScrollIndicator=true myScrollView.isPagingEnabled=true myScrollView.delegate=self myScrollView.bounces=false self.count=mySelectedProtocol!.imagesName!.count for i in 0..<self.count { ///Get Origin let xOrigin : CGFloat = CGFloat(i) * myScrollView.frame.size.width ///Create a imageView let imageView = UIImageView() imageView.frame = CGRect(x: xOrigin, y: 0, width: myScrollView.frame.size.width, height: myScrollView.frame.size.height) imageView.contentMode = .scaleAspectFit imageView.image=UIImage(named: mySelectedProtocol!.imagesName![i]) myScrollView.addSubview(imageView) } setUpPageControl() ///Set Content Size to Show myScrollView.contentSize = CGSize(width: myScrollView.frame.size.width * CGFloat(self.count), height: myScrollView.frame.size.height) } private func setUpPageControl() { if pageControl == nil { pageControl=UIPageControl() } pageControl!.numberOfPages = self.count pageControl!.currentPageIndicatorTintColor = UIColor.red pageControl!.pageIndicatorTintColor = UIColor.white pageControl!.frame = CGRect(x: 0, y: 20, width: self.view.frame.width, height: self.view.frame.height*0.2) pageControl!.currentPage=currentPageIndex self.view.addSubview(pageControl!) self.view.bringSubview(toFront: pageControl!) } } extension DestinationVC: UIScrollViewDelegate { func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { let scrollW : CGFloat = scrollView.frame.size.width currentPageIndex = Int(scrollView.contentOffset.x / scrollW) self.pageControl!.currentPage=currentPageIndex } func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { let scrollW : CGFloat = scrollView.frame.size.width currentPageIndex = Int(scrollView.contentOffset.x / scrollW) self.pageControl!.currentPage=currentPageIndex } }
You set the minimumScale to 1.0 - that's just 1x the normal scale. If you want it to be zoom down closer, you could try setting the minimum zoom like this: let scaleWidth = scrollView.frame.size.width / scrollView.contentSize.width let scaleHeight = scrollView.frame.size.height / scrollView.contentSize.height let minScale = min(scaleWidth, scaleHeight) scrollView.minimumZoomScale = minScale scrollView.maximumZoomScale = 1.0 scrollView.zoomScale = minScale And set the maximumZoomScale to 1.0 - the size of the content.
UISlider track not increasing in thickness
I can't seem to increase the thickness of the track. Been trying other recommendations and looking for this option in the documentation but it doesn't seem to be working, anyone know why?:( class factionButton: UISlider { var factionSlider = UISlider() func factionBalanceSlider(){ factionSlider.frame = CGRect(x: 15, y: 542, width: 386, height: 57) factionSlider.minimumValueImage = #imageLiteral(resourceName: "Alliance Slider") factionSlider.maximumValueImage = #imageLiteral(resourceName: "Horde Slider") factionSlider.setThumbImage(#imageLiteral(resourceName: "Thumb Image"), for: .normal) factionSlider.minimumTrackTintColor = UIColor(red:0.08, green:0.33, blue:0.69, alpha:0.8) factionSlider.maximumTrackTintColor = UIColor(red:1.00, green:0.00, blue:0.00, alpha:0.59) factionSlider.setValue(0.5, animated: true) factionSlider.isContinuous = true factionSlider.addTarget(self, action: #selector(recordFactionBalance(sender:)) , for: .valueChanged) } func getSlider() -> UISlider { return factionSlider } override func trackRect(forBounds bounds: CGRect) -> CGRect { let customBounds = CGRect(x: 16, y: 21, width: 343, height: 7) super.trackRect(forBounds: customBounds) return customBounds }
As mentioned in many other answers, you can change the height by creating a custom slider as below, class CustomSlider: UISlider { override func trackRect(forBounds bounds: CGRect) -> CGRect { var rect = super.trackRect(forBounds: bounds) rect.size.height = 7 return rect } } But in your particular case, you are not seeing the change because your implementation is not allowing the factionSlider to use overridden trackRect. To use that you need to change that to CustomSlider as below, class FactionButton: UISlider { var factionSlider = CustomSlider() func factionBalanceSlider(){ factionSlider.frame = CGRect(x: 15, y: 542, width: 386, height: 57) factionSlider.minimumValueImage = #imageLiteral(resourceName: "Alliance Slider") factionSlider.maximumValueImage = #imageLiteral(resourceName: "Horde Slider") factionSlider.setThumbImage(#imageLiteral(resourceName: "Thumb Image"), for: .normal) factionSlider.minimumTrackTintColor = UIColor(red:0.08, green:0.33, blue:0.69, alpha:0.8) factionSlider.maximumTrackTintColor = UIColor(red:1.00, green:0.00, blue:0.00, alpha:0.59) factionSlider.setValue(0.5, animated: true) factionSlider.isContinuous = true factionSlider.addTarget(self, action: #selector(recordFactionBalance(sender:)) , for: .valueChanged) } func getSlider() -> UISlider { return factionSlider } } Note In Swift, class name should start with Capital as i updated above. Secondly, I think FactionButton should not be a subclass of UISlider.
You should get the current bounds from the super class first, then just change the height: override func trackRect(forBounds bounds: CGRect) -> CGRect { var customBounds = super.trackRect(forBounds: bounds) customBounds.size.height = 7 return customBounds }
Setting the rect size expands the slider to the bottom only. So the origin should be recalculated to keep the slider centered. #IBDesignable class CustomSlider: UISlider { #IBInspectable var trackHeight: CGFloat = 6 override func trackRect(forBounds bounds: CGRect) -> CGRect { var rect = super.trackRect(forBounds: bounds) rect.size.height = trackHeight rect.origin.y -= trackHeight / 2 return rect } }
I made this by adding this 1. class CustomSlider: UISlider { override func trackRect(forBounds bounds: CGRect) -> CGRect { let point = CGPoint(x: bounds.minX, y: bounds.midY) return CGRect(origin: point, size: CGSize(width: bounds.width, height: 10)) //this height is the thickness } } storyboard - change UISlider class to my CustomSlider FYI for newbie like me.. change color is here :)
UILabel preferredMaxLayout breaking self-sizing cells
I found some really annoying problem with UILabel not working with AutoLayout. I found multiple threads about this, but none of solutions worked for me. class AudiosHeaderCell: CollectionViewCell<AudiosHeaderItemViewModel> { var label: UILabelPreferedWidth? { didSet { self.label?.textAlignment = .center self.label?.numberOfLines = 0 self.label?.lineBreakMode = .byWordWrapping self.label?.font = Font.Standard.size14 self.label?.textColor = UIColor(netHex: 0x185B97) } } let labelLeftRightMargin = CGFloat(16) override func setupViews() { self.backgroundColor = UIColor.white self.label = UILabelPreferedWidth() self.contentView.addSubview(self.label!) } override func setupConstraints() { self.label?.snp.makeConstraints { (make) in make.edges.equalToSuperview().inset(UIEdgeInsets(top: 8, left: labelLeftRightMargin, bottom: 8, right: labelLeftRightMargin)) } } override func bindViewModel(viewModel: AudiosHeaderItemViewModel) { self.label?.text = viewModel.text } } class UILabelPreferedWidth : UILabel { override var bounds: CGRect { didSet { print("SET BOUNDS", bounds) if (bounds.size.width != oldValue.size.width) { self.setNeedsUpdateConstraints() } } } override func updateConstraints() { print("updateConstraints", preferredMaxLayoutWidth, bounds) if(preferredMaxLayoutWidth != bounds.size.width) { preferredMaxLayoutWidth = bounds.size.width } super.updateConstraints() } } I use a method to calculate the size of the cell like this: func sizeForCellWithViewModel(_ viewModel: IReusableViewModel, fittingSize: CGSize) -> CGSize { let cell = self.classRegistry.instances[viewModel.reuseIdentifier]! (cell as! ICollectionViewCell).setViewModel(viewModel) cell.translatesAutoresizingMaskIntoConstraints = false cell.contentView.translatesAutoresizingMaskIntoConstraints = false cell.frame = CGRect(x: 0, y: 0, width: fittingSize.width, height: fittingSize.height) cell.setNeedsLayout() cell.layoutIfNeeded() print("SIZE FOR ", cell, "FITTING ", fittingSize, "IS", cell.systemLayoutSizeFitting(fittingSize)) return cell.systemLayoutSizeFitting(fittingSize) } It works for multiple cells that has some images and other content, but it fails on such a simple problem like scaling to content of UILabel. Problem I have is that systemLayoutSizeFitting.width returns size that is larger than fittingSize.width parameter I pass. I've been debugging this long time and I found out that preferredMaxLayoutWidth is not updating properly, as bounds for this UILabel are going beyond cell frame - despite the constraints I use there. Does anyone have a good solution for that ? The only one I found is to use this on CollectionViewCell: override var frame: CGRect { didSet { self.label?.preferredMaxLayoutWidth = self.frame.size.width - 32 } } But I hate it because it forces me to synchronise that with constraints and it will be required to all other use-cases in my application to remember to copy that. What I'm looking for is AutoLayout, Constraint only solution.
Ok problem solved by adding width constraint to the Cell's contentView: func sizeForCellWithViewModel(_ viewModel: IReusableViewModel, fittingSize: CGSize) -> CGSize { let cell = self.classRegistry.instances[viewModel.reuseIdentifier]! (cell as! ICollectionViewCell).setViewModel(viewModel) cell.contentView.frame = CGRect(x: 0, y: 0, width: fittingSize.width, height: fittingSize.height) cell.contentView.snp.removeConstraints() if fittingSize.width != 0 { cell.contentView.snp.makeConstraints { (make) in make.width.lessThanOrEqualTo(fittingSize.width) } } if fittingSize.height != 0 { cell.contentView.snp.makeConstraints({ (make) in make.height.lessThanOrEqualTo(fittingSize.height) }) } cell.contentView.setNeedsLayout() cell.contentView.layoutIfNeeded() return cell.contentView.systemLayoutSizeFitting(fittingSize) } Seems that this somehow makes UILabel works and preferredWidth not going crazy.
UITextField - Rounded corner issue
I bring the issue forward which I face. I am creating a UITextField programmatically as below. UItextField *mobileNumberField = [[UITextField alloc] initWithFrame:CGRectMake(10, 195, 300, 41)]; mobileNumberField.delegate = self; mobileNumberField.borderStyle = UITextBorderStyleRoundedRect; [mobileNumberField.layer setCornerRadius:14.0f]; mobileNumberField.placeholder = #"Mobile Number"; [self.paymentsHomeView addSubview:mobileNumberField]; The output is the attached image. I dont know why is it breaking at the corners. Help me to fix my text field like the image attached below.
Just remove this line... mobileNumberField.borderStyle = UITextBorderStyleRoundedRect; and add this code also.. [mobileNumberField setBackgroundColor:[UIColor whiteColor]]; [mobileNumberField.layer setBorderColor:[UIColor grayColor].CGColor]; [mobileNumberField.layer setBorderWidth:1.0];
Update your like below. UITextField *mobileNumberField = [[UITextField alloc] initWithFrame:CGRectMake(10, 195, 300, 41)]; mobileNumberField.delegate = self; mobileNumberField.layer.borderWidth = 1.0f; mobileNumberField.layer.borderColor = [UIColor lightGrayColor].CGColor; mobileNumberField. // mobileNumberField.borderStyle = UITextBorderStyleRoundedRect; [mobileNumberField.layer setCornerRadius:14.0f]; mobileNumberField.placeholder = #"Mobile Number"; [self.paymentsHomeView addSubview:mobileNumberField];
textField.layer.cornerRadius=textfield.frame.size.height/2; textField.clipsToBounds=YES;
The reason the corners are cut is because there is an enclosing view to the text field. When you set the corner radius, applies to THAT view and thus the corners of the inside text field seem to be cut - in reality they have not even changed. The solution is to put the UITextField inside UIView, set textfield borderstyle to none. Then apply the border and corner radius specification to the uiview. Note the borderColor, which is very close, if not the same, to the UITextField borderColor. As of writing, tested and works in Xcode 7.3.1, Swift 2.2, iOS 8 and 9. Swift: textField.borderStyle = UITextBorderStyle.None textBorderView.layer.cornerRadius = 5 textBorderView.layer.borderWidth = 1 textBorderView.layer.borderColor = UIColor.lightGrayColor().colorWithAlphaComponent(0.2).CGColor
The following code has given me the following result in Swift 5, XCode 11.4. Definitely more bells and whistles can be added. I think this good MVP naviTextField = UITextField.init(frame: CGRect(x: 0, y: 0, width: (self.navigationController?.navigationBar.frame.size.width)!, height: 21)) self.navigationItem.titleView = naviTextField naviTextField.becomeFirstResponder() naviTextField.placeholder = "Type target name here" naviTextField.borderStyle = .roundedRect naviTextField.layer.cornerRadius = 5.0 naviTextField.textAlignment = .center
Here is the solution of your problem UITextField * txtField = [[UITextField alloc]initWithFrame:CGRectMake(0, 0, 200, 50)]; [txtField setBorderStyle:UITextBorderStyleNone]; [txtField.layer setMasksToBounds:YES]; [txtField.layer setCornerRadius:10.0f]; [txtField.layer setBorderColor:[[UIColor lightGrayColor]CGColor]]; [txtField.layer setBorderWidth:1]; [txtField setTextAlignment:UITextAlignmentCenter]; [txtField setContentVerticalAlignment:UIControlContentVerticalAlignmentCenter]; [self.view addSubview:txtField];
Swift 3 solution: I have written separate function to set border and corner radius to any layer in swift, you have to just pass the layer of any view, border width, corner radius and border color to the following function ` func setBorderAndCornerRadius(layer: CALayer, width: CGFloat, radius: CGFloat,color : UIColor ) { layer.borderColor = color.cgColor layer.borderWidth = width layer.cornerRadius = radius layer.masksToBounds = true } `
swift solution: textField.layer.borderWidth = anyWidth textField.layer.borderColor = anyColor textField.layer.cornerRadius = textField.frame.size.height/2 textField.clipsToBounds = true
All above answer is good , but here I am adding the code through #IBDesignable. #IBDesignable class DesignableUITextField: UITextField { // Provides left padding for images override func leftViewRect(forBounds bounds: CGRect) -> CGRect { var textRect = super.leftViewRect(forBounds: bounds) textRect.origin.x += leftPadding return textRect } // Provides right padding for images override func rightViewRect(forBounds bounds: CGRect) -> CGRect { var textRect = super.rightViewRect(forBounds: bounds) textRect.origin.x -= rightPadding return textRect } #IBInspectable var leftImage: UIImage? { didSet { updateView() } } #IBInspectable var rightImage: UIImage? { didSet { updateRightView() } } #IBInspectable var leftPadding: CGFloat = 0 #IBInspectable var rightPadding: CGFloat = 0 #IBInspectable var gapPadding: CGFloat = 0 #IBInspectable var color: UIColor = UIColor.lightGray { didSet { updateView() } } #IBInspectable var cornerRadius: CGFloat = 0 #IBInspectable var borderColor: UIColor? = .lightGray override func draw(_ rect: CGRect) { layer.cornerRadius = cornerRadius layer.masksToBounds = true layer.borderWidth = 1 layer.borderColor = borderColor?.cgColor } //#IBInspectable var roundCornersRadius: CGFloat = 0 { // didSet{ // roundCornersRadiusTextField(radius: roundCornersRadius) // } // } func roundCornersRadiusTextField(radius:CGFloat) { roundCorners(corners: [UIRectCorner.topLeft, UIRectCorner.topRight, UIRectCorner.bottomLeft, UIRectCorner.bottomRight], radius:radius) } func roundBottomCornersRadius(radius:CGFloat) { roundCorners(corners: [UIRectCorner.topLeft, UIRectCorner.topRight], radius:radius) } func updateView() { if let image = leftImage { leftViewMode = UITextField.ViewMode.always let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 20, height: 20)) imageView.contentMode = .scaleAspectFit imageView.image = image // Note: In order for your image to use the tint color, you have to select the image in the Assets.xcassets and change the "Render As" property to "Template Image". imageView.tintColor = color leftView = imageView } else { leftViewMode = UITextField.ViewMode.never leftView = nil } // Placeholder text color attributedPlaceholder = NSAttributedString(string: placeholder != nil ? placeholder! : "", attributes:[NSAttributedString.Key.foregroundColor: color]) } func updateRightView() { if let image = rightImage { rightViewMode = UITextField.ViewMode.always let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 20, height: 20)) imageView.contentMode = .scaleAspectFit imageView.image = image // Note: In order for your image to use the tint color, you have to select the image in the Assets.xcassets and change the "Render As" property to "Template Image". imageView.tintColor = color rightView = imageView } else { rightViewMode = UITextField.ViewMode.never rightView = nil } // Placeholder text color attributedPlaceholder = NSAttributedString(string: placeholder != nil ? placeholder! : "", attributes:[NSAttributedString.Key.foregroundColor: color]) } func roundCorners(corners:UIRectCorner, radius:CGFloat) { let bounds = self.bounds let maskPath = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius)) let maskLayer = CAShapeLayer() maskLayer.frame = bounds maskLayer.path = maskPath.cgPath self.layer.mask = maskLayer let frameLayer = CAShapeLayer() frameLayer.frame = bounds frameLayer.path = maskPath.cgPath frameLayer.strokeColor = UIColor.darkGray.cgColor frameLayer.fillColor = UIColor.init(red: 247, green: 247, blue: 247, alpha: 0).cgColor self.layer.addSublayer(frameLayer) } private var textPadding: UIEdgeInsets { let p: CGFloat = leftPadding + gapPadding + (leftView?.frame.width ?? 0) return UIEdgeInsets(top: 0, left: p, bottom: 0, right: 5) } override open func textRect(forBounds bounds: CGRect) -> CGRect { return bounds.inset(by: textPadding) } override open func placeholderRect(forBounds bounds: CGRect) -> CGRect { return bounds.inset(by: textPadding) } override open func editingRect(forBounds bounds: CGRect) -> CGRect { return bounds.inset(by: textPadding) }}
How to set top-left alignment for UILabel for iOS application?
I have added one label in my nib file, then its required to have top-left alignment for that lable. As I am providing text at runtime so its not sure that how much lines there are. So if text contains only single line then it appears as vertical-center aligned. That alignment is not matching with my respective lable in front of it. For example: Which is looking odd :( Is there any way where i can set label text proper to Top-Left alignment?
It's fairly easy to do. Create a UILabel sublcass with a verticalAlignment property and override textRectForBounds:limitedToNumberOfLines to return the correct bounds for a top, middle or bottom vertical alignment. Here's the code: SOLabel.h #import <UIKit/UIKit.h> typedef enum { VerticalAlignmentTop = 0, // default VerticalAlignmentMiddle, VerticalAlignmentBottom, } VerticalAlignment; #interface SOLabel : UILabel #property (nonatomic, readwrite) VerticalAlignment verticalAlignment; #end SOLabel.m #implementation SOLabel -(id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (!self) return nil; // set inital value via IVAR so the setter isn't called _verticalAlignment = VerticalAlignmentTop; return self; } -(VerticalAlignment) verticalAlignment { return _verticalAlignment; } -(void) setVerticalAlignment:(VerticalAlignment)value { _verticalAlignment = value; [self setNeedsDisplay]; } // align text block according to vertical alignment settings -(CGRect)textRectForBounds:(CGRect)bounds limitedToNumberOfLines:(NSInteger)numberOfLines { CGRect rect = [super textRectForBounds:bounds limitedToNumberOfLines:numberOfLines]; CGRect result; switch (_verticalAlignment) { case VerticalAlignmentTop: result = CGRectMake(bounds.origin.x, bounds.origin.y, rect.size.width, rect.size.height); break; case VerticalAlignmentMiddle: result = CGRectMake(bounds.origin.x, bounds.origin.y + (bounds.size.height - rect.size.height) / 2, rect.size.width, rect.size.height); break; case VerticalAlignmentBottom: result = CGRectMake(bounds.origin.x, bounds.origin.y + (bounds.size.height - rect.size.height), rect.size.width, rect.size.height); break; default: result = bounds; break; } return result; } -(void)drawTextInRect:(CGRect)rect { CGRect r = [self textRectForBounds:rect limitedToNumberOfLines:self.numberOfLines]; [super drawTextInRect:r]; } #end
Rather than re-explaining, I will link to this rather extensive & highly rated question/answer: Vertically align text to top within a UILabel The short answer is no, Apple didn't make this easy, but it is possible by changing the frame size.
I found a solution using AutoLayout in StoryBoard. 1) Set no of lines to 0 and text alignment to Left. 2) Set height constraint. 3) The height Constraint should be in Relation - Less Than or Equal 4) override func viewWillLayoutSubviews() { sampleLabel.sizeToFit() } I got the result as follows :
The SOLabel works for me. Swift 3 & 5: This version has been updated from the original to allow support for RTL languages: public class VerticalAlignLabel: UILabel { enum VerticalAlignment { case top case middle case bottom } var verticalAlignment : VerticalAlignment = .top { didSet { setNeedsDisplay() } } override public func textRect(forBounds bounds: CGRect, limitedToNumberOfLines: Int) -> CGRect { let rect = super.textRect(forBounds: bounds, limitedToNumberOfLines: limitedToNumberOfLines) if UIView.userInterfaceLayoutDirection(for: .unspecified) == .rightToLeft { switch verticalAlignment { case .top: return CGRect(x: self.bounds.size.width - rect.size.width, y: bounds.origin.y, width: rect.size.width, height: rect.size.height) case .middle: return CGRect(x: self.bounds.size.width - rect.size.width, y: bounds.origin.y + (bounds.size.height - rect.size.height) / 2, width: rect.size.width, height: rect.size.height) case .bottom: return CGRect(x: self.bounds.size.width - rect.size.width, y: bounds.origin.y + (bounds.size.height - rect.size.height), width: rect.size.width, height: rect.size.height) } } else { switch verticalAlignment { case .top: return CGRect(x: bounds.origin.x, y: bounds.origin.y, width: rect.size.width, height: rect.size.height) case .middle: return CGRect(x: bounds.origin.x, y: bounds.origin.y + (bounds.size.height - rect.size.height) / 2, width: rect.size.width, height: rect.size.height) case .bottom: return CGRect(x: bounds.origin.x, y: bounds.origin.y + (bounds.size.height - rect.size.height), width: rect.size.width, height: rect.size.height) } } } override public func drawText(in rect: CGRect) { let r = self.textRect(forBounds: rect, limitedToNumberOfLines: self.numberOfLines) super.drawText(in: r) } } Swift 1: class UIVerticalAlignLabel: UILabel { enum VerticalAlignment : Int { case VerticalAlignmentTop = 0 case VerticalAlignmentMiddle = 1 case VerticalAlignmentBottom = 2 } var verticalAlignment : VerticalAlignment = .VerticalAlignmentTop { didSet { setNeedsDisplay() } } required init(coder aDecoder: NSCoder){ super.init(coder: aDecoder) } override func textRectForBounds(bounds: CGRect, limitedToNumberOfLines: Int) -> CGRect { let rect = super.textRectForBounds(bounds, limitedToNumberOfLines: limitedToNumberOfLines) switch(verticalAlignment) { case .VerticalAlignmentTop: return CGRectMake(bounds.origin.x, bounds.origin.y, rect.size.width, rect.size.height) case .VerticalAlignmentMiddle: return CGRectMake(bounds.origin.x, bounds.origin.y + (bounds.size.height - rect.size.height) / 2, rect.size.width, rect.size.height) case .VerticalAlignmentBottom: return CGRectMake(bounds.origin.x, bounds.origin.y + (bounds.size.height - rect.size.height), rect.size.width, rect.size.height) default: return bounds } } override func drawTextInRect(rect: CGRect) { let r = self.textRectForBounds(rect, limitedToNumberOfLines: self.numberOfLines) super.drawTextInRect(r) } }
In my case, it was bottom space constraint issue. I had set it to = 16. When I set it to bottom to >= 16, this issue got solved. Also, if you have any height constraint in the label, then you need to remove it. Here's my label's constraint view in the size inspector:
In your code label.text = #"some text"; [label sizeToFit]; Beware that if you use that in table cells or other views that get recycled with different data, you'll need to store the original frame somewhere and reset it before calling sizeToFit.
I found another solution for the same problem. I used UITextView instead of UILabel and switched editable() function to false.
I was also having this problem but what I found was the the order in which you set the UILabel's properties and methods matters! If you call [label sizeToFit] before label.font = [UIFont fontWithName:#"Helvetica" size:14]; then the text does not align to the top but if you swap them around then it does! I also noticed that setting the text first makes a difference too. Hope this helps.
As you are using the interface builder, set the constraints for your label (be sure to set the height and width as well). Then in the Size Inspector, check the height for the label. There you will want it to read >= instead of =. Then in the implementation for that view controller, set the number of lines to 0 (can also be done in IB) and set the label [label sizeToFit]; and as your text gains length, the label will grow in height and keep your text in the upper left.
If what you need is non-editable text that by default starts at the top left corner you can simply use a Text View instead of a label and then set its state to non-editable, like this: textview.isEditable = false Way easier than messing with the labels... Cheers!
Solution with SoLabel works , Thanks. Bellow I have added monotouch version: public class UICustomLabel : UILabel { private UITextVerticalAlignment _textVerticalAlignment; public UICustomLabel() { TextVerticalAlignment = UITextVerticalAlignment.Top; } public UITextVerticalAlignment TextVerticalAlignment { get { return _textVerticalAlignment; } set { _textVerticalAlignment = value; SetNeedsDisplay(); } } public override void DrawText(RectangleF rect) { var bound = TextRectForBounds(rect, Lines); base.DrawText(bound); } public override RectangleF TextRectForBounds(RectangleF bounds, int numberOfLines) { var rect = base.TextRectForBounds(bounds, numberOfLines); RectangleF resultRect; switch (TextVerticalAlignment) { case UITextVerticalAlignment.Top: resultRect = new RectangleF(bounds.X, bounds.Y, rect.Size.Width, rect.Size.Height); break; case UITextVerticalAlignment.Middle: resultRect = new RectangleF(bounds.X, bounds.Y + (bounds.Size.Height - rect.Size.Height)/2, rect.Size.Width, rect.Size.Height); break; case UITextVerticalAlignment.Bottom: resultRect = new RectangleF(bounds.X, bounds.Y + (bounds.Size.Height - rect.Size.Height), rect.Size.Width, rect.Size.Height); break; default: resultRect = bounds; break; } return resultRect; } } public enum UITextVerticalAlignment { Top = 0, // default Middle, Bottom }
The simplest and easiest way is to embed Label in StackView and setting StackView's Axis to Horizontal, Alignment to Top in Attribute Inspector from Storyboard like shown here.
Use UITextView instead of UILabel. Also it works for UITableViewCell width automatic row height Set isScrollEnabled and isEditable to false. Add min height constraint for TextView One line text screenshot Multiline text screenshot final class TestVC: UIViewController { lazy var testTextLabel: UITextView = { $0.isScrollEnabled = false $0.isEditable = false $0.font = .systemFont(ofSize: 17, weight: .medium) $0.textColor = .black $0.layer.borderWidth = 1 $0.layer.borderColor = UIColor.black.cgColor $0.layer.cornerRadius = 5 return $0 }(UITextView()) override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white testTextLabel.text = "Your text" view.addSubview(testTextLabel) testTextLabel.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ testTextLabel.topAnchor.constraint(equalTo: testTextLabel.superview!.safeAreaLayoutGuide.topAnchor, constant: 12), testTextLabel.leadingAnchor.constraint(equalTo: testTextLabel.superview!.leadingAnchor, constant: 12), testTextLabel.widthAnchor.constraint(equalToConstant: 250), testTextLabel.heightAnchor.constraint(greaterThanOrEqualToConstant: 70) ]) } }
Building on top of totiG's awesome answer, I have created an IBDesignable class that makes it extremely easy to customize a UILabel's vertical alignment right from the StoryBoard. Just make sure that you set your UILabel's class to 'VerticalAlignLabel' from the StoryBoard identity inspector. If the vertical alignment doesn't take effect, go to Editor->Refresh All Views which should do the trick. How it works: Once you set your UILabel's class correctly, the storyboard should show you an input field that takes an integer (alignment code). Update: I've added support for centered labels ~Sev Enter 0 for Top Alignment Enter 1 for Middle Alignment Enter 2 for Bottom Alignment #IBDesignable class VerticalAlignLabel: UILabel { #IBInspectable var alignmentCode: Int = 0 { didSet { applyAlignmentCode() } } func applyAlignmentCode() { switch alignmentCode { case 0: verticalAlignment = .top case 1: verticalAlignment = .topcenter case 2: verticalAlignment = .middle case 3: verticalAlignment = .bottom default: break } } override func awakeFromNib() { super.awakeFromNib() self.applyAlignmentCode() } override func prepareForInterfaceBuilder() { super.prepareForInterfaceBuilder() self.applyAlignmentCode() } enum VerticalAlignment { case top case topcenter case middle case bottom } var verticalAlignment : VerticalAlignment = .top { didSet { setNeedsDisplay() } } override public func textRect(forBounds bounds: CGRect, limitedToNumberOfLines: Int) -> CGRect { let rect = super.textRect(forBounds: bounds, limitedToNumberOfLines: limitedToNumberOfLines) if #available(iOS 9.0, *) { if UIView.userInterfaceLayoutDirection(for: .unspecified) == .rightToLeft { switch verticalAlignment { case .top: return CGRect(x: self.bounds.size.width - rect.size.width, y: bounds.origin.y, width: rect.size.width, height: rect.size.height) case .topcenter: return CGRect(x: self.bounds.size.width - (rect.size.width / 2), y: bounds.origin.y, width: rect.size.width, height: rect.size.height) case .middle: return CGRect(x: self.bounds.size.width - rect.size.width, y: bounds.origin.y + (bounds.size.height - rect.size.height) / 2, width: rect.size.width, height: rect.size.height) case .bottom: return CGRect(x: self.bounds.size.width - rect.size.width, y: bounds.origin.y + (bounds.size.height - rect.size.height), width: rect.size.width, height: rect.size.height) } } else { switch verticalAlignment { case .top: return CGRect(x: bounds.origin.x, y: bounds.origin.y, width: rect.size.width, height: rect.size.height) case .topcenter: return CGRect(x: (self.bounds.size.width / 2 ) - (rect.size.width / 2), y: bounds.origin.y, width: rect.size.width, height: rect.size.height) case .middle: return CGRect(x: bounds.origin.x, y: bounds.origin.y + (bounds.size.height - rect.size.height) / 2, width: rect.size.width, height: rect.size.height) case .bottom: return CGRect(x: bounds.origin.x, y: bounds.origin.y + (bounds.size.height - rect.size.height), width: rect.size.width, height: rect.size.height) } } } else { // Fallback on earlier versions return rect } } override public func drawText(in rect: CGRect) { let r = self.textRect(forBounds: rect, limitedToNumberOfLines: self.numberOfLines) super.drawText(in: r) } }
you can also just change your UILabel to UITextView, because they basically do the same thing except the advantage of UITextView is that text is automatically aligned to the top left
Swift 3 version of #totiG's answer class UIVerticalAlignLabel: UILabel { enum VerticalAlignment : Int { case VerticalAlignmentTop = 0 case VerticalAlignmentMiddle = 1 case VerticalAlignmentBottom = 2 } #IBInspectable var verticalAlignment : VerticalAlignment = .VerticalAlignmentTop { didSet { setNeedsDisplay() } } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } override func textRect(forBounds bounds: CGRect, limitedToNumberOfLines: Int) -> CGRect { let rect = super.textRect(forBounds: bounds, limitedToNumberOfLines: limitedToNumberOfLines) switch(verticalAlignment) { case .VerticalAlignmentTop: return CGRect(x: bounds.origin.x, y: bounds.origin.y, width: rect.size.width, height: rect.size.height) case .VerticalAlignmentMiddle: return CGRect(x: bounds.origin.x, y: bounds.origin.y + (bounds.size.height - rect.size.height) / 2, width: rect.size.width, height: rect.size.height) case .VerticalAlignmentBottom: return CGRect(x: bounds.origin.x, y: bounds.origin.y + (bounds.size.height - rect.size.height), width: rect.size.width, height: rect.size.height) } } override func drawText(in rect: CGRect) { let r = self.textRect(forBounds: rect, limitedToNumberOfLines: self.numberOfLines) super.drawText(in: r) } }
#totiG's answer is correct and solved my problem. But I found a problem while implementing this method, in smaller devices like 5s , SE, this doesn't work for me. I have to set label.sizeToFit() in override func layoutSubViews() override func layoutSubViews() { super.layoutSubViews() // Do other works if needed label.sizeToFit() }
I have this problem to but my label was in UITableViewCell, and in fund that the easiest way to solve the problem was to create an empty UIView and set the label inside it with constraints to the top and to the left side only, on off curse set the number of lines to 0
Use textRect(forBounds:limitedToNumberOfLines:) class TopAlignedLabel: UILabel { override func drawText(in rect: CGRect) { let textRect = super.textRect(forBounds: bounds, limitedToNumberOfLines: numberOfLines) super.drawText(in: textRect) } }
For iOS 7 that's what i made and worked for me #implementation UILabel (VerticalAlign) - (void)alignTop { CGSize boundingRectSize = CGSizeMake(self.frame.size.width, CGFLOAT_MAX); NSDictionary *attributes = #{NSFontAttributeName : self.font}; CGRect labelSize = [self.text boundingRectWithSize:boundingRectSize options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading attributes:attributes context:nil]; int numberOfLines= ceil(labelSize.size.height / self.font.lineHeight); CGRect newFrame = self.frame; newFrame.size.height = numberOfLines * self.font.lineHeight; self.frame = newFrame; } - (void)alignBottom { CGSize boundingRectSize = CGSizeMake(self.frame.size.width, CGFLOAT_MAX); NSDictionary *attributes = #{NSFontAttributeName : self.font}; CGRect labelSize = [self.text boundingRectWithSize:boundingRectSize options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading attributes:attributes context:nil]; int numberOfLines= ceil(labelSize.size.height / self.font.lineHeight); int numberOfNewLined = (self.frame.size.height/self.font.lineHeight) - numberOfLines; NSMutableString *newLines = [NSMutableString string]; for(int i=0; i< numberOfNewLined; i++){ [newLines appendString:#"\n"]; } [newLines appendString:self.text]; self.text = [newLines mutableCopy]; }
Swift 2.0: : Using UILabel Extension Make constant enum values in a empty Swift file. // AppRef.swift import UIKit import Foundation enum UILabelTextPositions : String { case VERTICAL_ALIGNMENT_TOP = "VerticalAlignmentTop" case VERTICAL_ALIGNMENT_MIDDLE = "VerticalAlignmentMiddle" case VERTICAL_ALIGNMENT_BOTTOM = "VerticalAlignmentBottom" } Using UILabel Extension: Make a empty Swift class and name it. Add the following. // AppExtensions.swift import Foundation import UIKit extension UILabel{ func makeLabelTextPosition (sampleLabel :UILabel?, positionIdentifier : String) -> UILabel { let rect = sampleLabel!.textRectForBounds(bounds, limitedToNumberOfLines: 0) switch positionIdentifier { case "VerticalAlignmentTop": sampleLabel!.frame = CGRectMake(bounds.origin.x+5, bounds.origin.y, rect.size.width, rect.size.height) break; case "VerticalAlignmentMiddle": sampleLabel!.frame = CGRectMake(bounds.origin.x+5,bounds.origin.y + (bounds.size.height - rect.size.height) / 2, rect.size.width, rect.size.height); break; case "VerticalAlignmentBottom": sampleLabel!.frame = CGRectMake(bounds.origin.x+5, bounds.origin.y + (bounds.size.height - rect.size.height),rect.size.width, rect.size.height); break; default: sampleLabel!.frame = bounds; break; } return sampleLabel! } } Usage : myMessageLabel.makeLabelTextPosition(messageLabel, positionIdentifier: UILabelTextPositions.VERTICAL_ALIGNMENT_TOP.rawValue)
Swift 5 It´s simple, the order of the properties is everything. titleLabel.frame = CGRect(x: 20, y: 20, width: 374, height: 291.2) titleLabel.backgroundColor = UIColor.clear //set a light color to see the frame titleLabel.textAlignment = .left titleLabel.lineBreakMode = .byTruncatingTail titleLabel.numberOfLines = 4 titleLabel.font = UIFont(name: "HelveticaNeue-Bold", size: 35) titleLabel.text = "Example" titleLabel.sizeToFit() self.view.addSubview(titleLabel)
I was able to fix it by embedding the tag within a view. It worked perfect!
You need to set in layoutSubviews: override func layoutSubviews() { super.layoutSubviews() yourLabel.sizeToFit() //yourLabel.center.x = someView.center.x // optionally if exists }
Pure Storyboard solution... Horizontal left alignment is easy since it can be setup as UILabel property in Interface Builder. Vertical top alignment can be setup with just two constraints: one that setups UILabel.bottom as greater or equal to some other view (maximal height) with priority like 1000 (required). This will add UILabel ability to grow up to this constraint's height other that setups UILabel.bottom like equal 0 (minimal height) with priority like 750 (non-required). This will add UILabel ability/tendency to shrink to minimal size allowed by its content This means that UIKit will try to setup UILabel height as minimal as possible in range from minimal auto-sized UILabel based on its properties/content (text, font, ...) up to 1. constraint height.
How to set top-left alignment for UILabel for iOS application? Label Set Content Mode to "Top Left" work for me, thank you very much: