Placeholder in UITextView
I made a few minor modifications to bcd's solution to allow for initialization from a Xib
file, text wrapping, and to maintain background color. Hopefully it will save others the trouble.
UIPlaceHolderTextView.h:
#import <Foundation/Foundation.h>IB_DESIGNABLE@interface UIPlaceHolderTextView : UITextView@property (nonatomic, retain) IBInspectable NSString *placeholder;@property (nonatomic, retain) IBInspectable UIColor *placeholderColor;-(void)textChanged:(NSNotification*)notification;@end
UIPlaceHolderTextView.m:
#import "UIPlaceHolderTextView.h"@interface UIPlaceHolderTextView ()@property (nonatomic, retain) UILabel *placeHolderLabel;@end@implementation UIPlaceHolderTextViewCGFloat const UI_PLACEHOLDER_TEXT_CHANGED_ANIMATION_DURATION = 0.25;- (void)dealloc{ [[NSNotificationCenter defaultCenter] removeObserver:self];#if __has_feature(objc_arc)#else [_placeHolderLabel release]; _placeHolderLabel = nil; [_placeholderColor release]; _placeholderColor = nil; [_placeholder release]; _placeholder = nil; [super dealloc];#endif}- (void)awakeFromNib{ [super awakeFromNib]; // Use Interface Builder User Defined Runtime Attributes to set // placeholder and placeholderColor in Interface Builder. if (!self.placeholder) { [self setPlaceholder:@""]; } if (!self.placeholderColor) { [self setPlaceholderColor:[UIColor lightGrayColor]]; } [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textChanged:) name:UITextViewTextDidChangeNotification object:nil];}- (id)initWithFrame:(CGRect)frame{ if( (self = [super initWithFrame:frame]) ) { [self setPlaceholder:@""]; [self setPlaceholderColor:[UIColor lightGrayColor]]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textChanged:) name:UITextViewTextDidChangeNotification object:nil]; } return self;}- (void)textChanged:(NSNotification *)notification{ if([[self placeholder] length] == 0) { return; } [UIView animateWithDuration:UI_PLACEHOLDER_TEXT_CHANGED_ANIMATION_DURATION animations:^{ if([[self text] length] == 0) { [[self viewWithTag:999] setAlpha:1]; } else { [[self viewWithTag:999] setAlpha:0]; } }];}- (void)setText:(NSString *)text { [super setText:text]; [self textChanged:nil];}- (void)drawRect:(CGRect)rect{ if( [[self placeholder] length] > 0 ) { if (_placeHolderLabel == nil ) { _placeHolderLabel = [[UILabel alloc] initWithFrame:CGRectMake(8,8,self.bounds.size.width - 16,0)]; _placeHolderLabel.lineBreakMode = NSLineBreakByWordWrapping; _placeHolderLabel.numberOfLines = 0; _placeHolderLabel.font = self.font; _placeHolderLabel.backgroundColor = [UIColor clearColor]; _placeHolderLabel.textColor = self.placeholderColor; _placeHolderLabel.alpha = 0; _placeHolderLabel.tag = 999; [self addSubview:_placeHolderLabel]; } _placeHolderLabel.text = self.placeholder; [_placeHolderLabel sizeToFit]; [self sendSubviewToBack:_placeHolderLabel]; } if( [[self text] length] == 0 && [[self placeholder] length] > 0 ) { [[self viewWithTag:999] setAlpha:1]; } [super drawRect:rect];}@end
Easy way, just create placeholder text in UITextView
by using the following UITextViewDelegate
methods:
- (void)textViewDidBeginEditing:(UITextView *)textView{ if ([textView.text isEqualToString:@"placeholder text here..."]) { textView.text = @""; textView.textColor = [UIColor blackColor]; //optional } [textView becomeFirstResponder];}- (void)textViewDidEndEditing:(UITextView *)textView{ if ([textView.text isEqualToString:@""]) { textView.text = @"placeholder text here..."; textView.textColor = [UIColor lightGrayColor]; //optional } [textView resignFirstResponder];}
just remember to set myUITextView
with the exact text on creation e.g.
UITextView *myUITextView = [[UITextView alloc] init];myUITextView.delegate = self;myUITextView.text = @"placeholder text here...";myUITextView.textColor = [UIColor lightGrayColor]; //optional
and make the parent class a UITextViewDelegate
before including these methods e.g.
@interface MyClass () <UITextViewDelegate>@end
Code for Swift 3.1
func textViewDidBeginEditing(_ textView: UITextView) { if (textView.text == "placeholder text here..." && textView.textColor == .lightGray) { textView.text = "" textView.textColor = .black } textView.becomeFirstResponder() //Optional}func textViewDidEndEditing(_ textView: UITextView){ if (textView.text == "") { textView.text = "placeholder text here..." textView.textColor = .lightGray } textView.resignFirstResponder()}
just remember to set myUITextView
with the exact text on creation e.g.
let myUITextView = UITextView.init() myUITextView.delegate = self myUITextView.text = "placeholder text here..." myUITextView.textColor = .lightGray
and make the parent class a UITextViewDelegate
before including these methods e.g.
class MyClass: UITextViewDelegate{}
I wasn't too happy with any of the solutions posted as they were a bit heavy. Adding views to the view isn't really ideal (especially in drawRect:
). They both had leaks, which isn't acceptable either.
Here is my solution: SAMTextView
SAMTextView.h
//// SAMTextView.h// SAMTextView//// Created by Sam Soffes on 8/18/10.// Copyright 2010-2013 Sam Soffes. All rights reserved.//#import <UIKit/UIKit.h>/** UITextView subclass that adds placeholder support like UITextField has. */@interface SAMTextView : UITextView/** The string that is displayed when there is no other text in the text view. The default value is `nil`. */@property (nonatomic, strong) NSString *placeholder;/** The color of the placeholder. The default is `[UIColor lightGrayColor]`. */@property (nonatomic, strong) UIColor *placeholderTextColor;/** Returns the drawing rectangle for the text views’s placeholder text. @param bounds The bounding rectangle of the receiver. @return The computed drawing rectangle for the placeholder text. */- (CGRect)placeholderRectForBounds:(CGRect)bounds;@end
SAMTextView.m
//// SAMTextView.m// SAMTextView//// Created by Sam Soffes on 8/18/10.// Copyright 2010-2013 Sam Soffes. All rights reserved.//#import "SAMTextView.h"@implementation SAMTextView#pragma mark - Accessors@synthesize placeholder = _placeholder;@synthesize placeholderTextColor = _placeholderTextColor;- (void)setText:(NSString *)string { [super setText:string]; [self setNeedsDisplay];}- (void)insertText:(NSString *)string { [super insertText:string]; [self setNeedsDisplay];}- (void)setAttributedText:(NSAttributedString *)attributedText { [super setAttributedText:attributedText]; [self setNeedsDisplay];}- (void)setPlaceholder:(NSString *)string { if ([string isEqual:_placeholder]) { return; } _placeholder = string; [self setNeedsDisplay];}- (void)setContentInset:(UIEdgeInsets)contentInset { [super setContentInset:contentInset]; [self setNeedsDisplay];}- (void)setFont:(UIFont *)font { [super setFont:font]; [self setNeedsDisplay];}- (void)setTextAlignment:(NSTextAlignment)textAlignment { [super setTextAlignment:textAlignment]; [self setNeedsDisplay];}#pragma mark - NSObject- (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self name:UITextViewTextDidChangeNotification object:self];}#pragma mark - UIView- (id)initWithCoder:(NSCoder *)aDecoder { if ((self = [super initWithCoder:aDecoder])) { [self initialize]; } return self;}- (id)initWithFrame:(CGRect)frame { if ((self = [super initWithFrame:frame])) { [self initialize]; } return self;}- (void)drawRect:(CGRect)rect { [super drawRect:rect]; if (self.text.length == 0 && self.placeholder) { rect = [self placeholderRectForBounds:self.bounds]; UIFont *font = self.font ? self.font : self.typingAttributes[NSFontAttributeName]; // Draw the text [self.placeholderTextColor set]; [self.placeholder drawInRect:rect withFont:font lineBreakMode:NSLineBreakByTruncatingTail alignment:self.textAlignment]; }}#pragma mark - Placeholder- (CGRect)placeholderRectForBounds:(CGRect)bounds { // Inset the rect CGRect rect = UIEdgeInsetsInsetRect(bounds, self.contentInset); if (self.typingAttributes) { NSParagraphStyle *style = self.typingAttributes[NSParagraphStyleAttributeName]; if (style) { rect.origin.x += style.headIndent; rect.origin.y += style.firstLineHeadIndent; } } return rect;}#pragma mark - Private- (void)initialize { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textChanged:) name:UITextViewTextDidChangeNotification object:self]; self.placeholderTextColor = [UIColor colorWithWhite:0.702f alpha:1.0f];}- (void)textChanged:(NSNotification *)notification { [self setNeedsDisplay];}@end
It's a lot simpler than the others, as it doesn't use subviews (or have leaks). Feel free to use it.
Update 11/10/11: It is now documented and supports use in Interface Builder.
Update 11/24/13: Point to new repo.