80

My question is as simple as the title, but here's a little more:

I have a UITextField, on which I've set the background image. The problem is that the text hugs so closely to it's edge, which is also the edge of the image. I want my text field to look like Apple's, with a bit of horizontal space between the background image and where the text starts.

Mark Amery
  • 143,130
  • 81
  • 406
  • 459
Dyldo42
  • 2,358
  • 2
  • 22
  • 26
  • 1
    Do you mean to set the text's insets? Then try this question: http://stackoverflow.com/questions/2694411/text-inset-for-uitextfield – phi Sep 27 '11 at 07:40
  • you can add a UIImageView with the image, add the text field on top of it and set the textfield's width accordingly... thats the easiest solution... – Swapnil Luktuke Sep 27 '11 at 07:43
  • @lukya Unless he wants the tappable area of the text field to include the section with the image, in which case your approach will require an additional gesture recogniser and some code in order to hack it into working. – Mark Amery Sep 09 '13 at 10:18

8 Answers8

235

This is 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

Swift 5:

let spacerView = UIView(frame:CGRect(x:0, y:0, width:10, height:10))
textField.leftViewMode = .always
textField.leftView = spacerView
clearlight
  • 12,255
  • 11
  • 57
  • 75
adjwilli
  • 9,658
  • 4
  • 35
  • 29
  • 19
    This is cleaner than subclassing, AND CORRECTLY HANDLES THE CLEAR BUTTON. Wish I could give +2. – Rembrandt Q. Einstein Jul 03 '13 at 21:19
  • 8
    i agree, +2 if I could. as soon as i read 'subclass' in the much-voted-up answer i groaned inwardly because it's always harder and more error prone to start fooling with drawing methods – mblackwell8 Jul 09 '13 at 02:58
  • 6
    Great solution, just translated this to Swift: `var spacerView = UIView(frame:CGRect(x:0, y:0, width:10, height:10)); textfield.leftViewMode = UITextFieldViewMode.Always textfield.leftView = spacerView` – juliensaad Jan 13 '15 at 16:36
  • great stuff, especially because it doesn't involve hacking with subclasses! translation to C# (Xamarin): `var spacerView = new UIView(new RectangleF(0, 0, 7, 10)); myTextfield.LeftViewMode = UITextFieldViewMode.Always; myTextfield.LeftView = spacerView;` – Gabor Furedi Jan 21 '15 at 11:42
  • 2
    @jsiodtb I've tried to use this in Swift, but it prevents my segue from being performed. Any idea why? I _am_ calling it in `viewDidLoad()`. It doesn't work in `viewWillAppear()`, either. – IIllIIll Feb 17 '15 at 22:08
  • @arcrammer I've having the same problem i can get the segue to work by putting the call in viewDidAppear however no effect, very strange! – Maximilian Apr 14 '15 at 17:07
  • 1
    @Arcrammer You are not allowed to use the same view as padding twice ( which causes the error you described ) – Norman Dec 02 '15 at 12:45
  • can't use negative text Indent :( – Yaro Jun 13 '16 at 20:19
41

You have to subclass and override textRectForBounds: and editingRectForBounds:. Here is a UITextfield subclass with custom background and vertical and horizontal padding:

@interface MyUITextField : UITextField 
@property (nonatomic, assign) float verticalPadding;
@property (nonatomic, assign) float horizontalPadding;
@end

#import "MyUITextField.h"
@implementation MyUITextField
@synthesize horizontalPadding, verticalPadding;
- (CGRect)textRectForBounds:(CGRect)bounds {
    return CGRectMake(bounds.origin.x + horizontalPadding, bounds.origin.y + verticalPadding, bounds.size.width - horizontalPadding*2, bounds.size.height - verticalPadding*2);
}
- (CGRect)editingRectForBounds:(CGRect)bounds {
    return [self textRectForBounds:bounds];
}
@end

Usage:

UIImage *bg = [UIImage imageNamed:@"textfield.png"];
CGRect rect = CGRectMake(0, 0, bg.size.width, bg.size.height);
MyUITextField *textfield = [[[MyUITextField alloc] initWithFrame:rect] autorelease];
textfield.verticalPadding = 10; 
textfield.horizontalPadding = 10;
[textfield setBackground:bg];
[textfield setBorderStyle:UITextBorderStyleNone];
[self.view addSubview:textfield];
Jano
  • 62,815
  • 21
  • 164
  • 192
  • 1
    That's a good solution, but it does not account for the Clear Button when it's visible. +1 nevertheless. – jweyrich Feb 07 '13 at 19:20
  • Awesome solution ! Cant believe Apple doesnt allow you to set insets for UITextField ! – RPM Mar 19 '13 at 22:41
  • @RPM Since iOS 6.0 UITextView has a attributedText property,so you could set NSAttributedString with attributes NSMutableParagraphStyle + firstLineHeadIndent (and tailIndent for the clear button). But yes, it takes iOS 6. – Jano Apr 28 '13 at 09:43
  • For whatever reason, firstLineHeadIndent within NSParagraphStyle appears to have no effect on the way text is displayed in a UITextField in iOS 6. – William Jockusch Jun 28 '13 at 14:46
27

A good approach to add padding to UITextField is to subclass UITextField , overriding the rectangle methods and adding an edgeInsets property. You can 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
Brody Robertson
  • 8,506
  • 2
  • 47
  • 42
  • Good and clean approach. Just to note for others reading this comment: There's a temptation to go with spaceView and assign leftView for text view, but when you have many text fields, will you duplicate the code all around by adding spaceViews to all your textField? Probably no, so go this sublassing if you have many text fields. – Centurion Oct 04 '13 at 09:23
  • 3
    Neat solution. +1 for using `UIEdgeInsets` that is definitely the appropriate tool for the task at hand! – t6d Jun 14 '14 at 13:21
  • This works, I found the accepted answer to crash if those UITextFields were using autolayout. – elliotrock Dec 30 '14 at 12:58
  • Much cleaner than the most voted answer, IMO. Nice job. – Brian Sachetta Jun 01 '15 at 17:20
  • I've tried the other solutions, they also worked but textRectForBounds is the correct way. Thanks Brody. – seymatanoglu Feb 18 '16 at 12:37
  • 1
    Excellent approach to even the general concept of creating a custom control, based on an existing one. I used this as a basis for porting over all my custom Swift controls to Objective-C and now the IB is no longer crashing when loading Designables. (I should add that my controls have IB_DESIGNABLE set) – Daniel Randall Feb 16 '21 at 19:46
14

I turned this into a nice little extension for use inside Storyboards:

public extension UITextField {
    @IBInspectable public var leftSpacer:CGFloat {
        get {
            return leftView?.frame.size.width ?? 0
        } set {
            leftViewMode = .Always
            leftView = UIView(frame: CGRect(x: 0, y: 0, width: newValue, height: frame.size.height))
        }
    }
}
Vitalii
  • 4,267
  • 1
  • 40
  • 45
Oxcug
  • 6,524
  • 2
  • 31
  • 46
4

My 2 cents:

class PaddedTextField : UITextField {
    var insets = UIEdgeInsets.zero
    var verticalPadding:CGFloat = 0
    var horizontalPadding:CGFloat = 0

    override func textRect(forBounds bounds: CGRect) -> CGRect {
        return CGRect(x: bounds.origin.x + insets.left, y: bounds.origin.y + insets.top, width: bounds.size.width - (insets.left + insets.right), height: bounds.size.height - (insets.top + insets.bottom));
    }

    override func editingRect(forBounds bounds: CGRect) -> CGRect {
        return textRect(forBounds: bounds)
    }
}
Chris Prince
  • 7,288
  • 2
  • 48
  • 66
1

I suggest converting your UITextField to a UITextView and setting the contentInset. For example to indent by 10 spaces:

textview.contentInset=UIEdgeInsetsMake(0, 10, 0, 0);
DaveShaw
  • 52,123
  • 16
  • 112
  • 141
user790072
  • 35
  • 3
  • this is actually a good solution, not sure why it was downvoted. – Jeremie D May 04 '17 at 17:40
  • 2
    @Jeremie it's because this doesn't answer the question. UITextField and UITextView behaves differently and you would have to modify the text view significantly to get it to behave like a text field – Chris Jul 26 '17 at 18:56
0

Sometimes the leftView of the textField is a Apple's magnifying glass image. If so, you can set an indent like this:

    UIView *leftView = textField.leftView;
    if (leftView && [leftView isKindOfClass:[UIImageView class]]) {
        leftView.contentMode = UIViewContentModeCenter;
        leftView.width = 20.0f;  //the space you want indented.
    }
Lirik
  • 3,167
  • 1
  • 30
  • 31
0

If you don't want to create @IBOutlet's could simply subclass (answer in Swift):

class PaddedTextField: UITextField {

  override func awakeFromNib() {
      super.awakeFromNib()

      let spacerView = UIView(frame:CGRect(x: 0, y: 0, width: 10, height: 10))
      leftViewMode = .always
      leftView = spacerView
   }
}
Vrutin Rathod
  • 900
  • 1
  • 12
  • 16