My application uses an UITextView
. Now I want the UITextView
to have a placeholder similar to the one you can set for an UITextField
.
How to do this?
My application uses an UITextView
. Now I want the UITextView
to have a placeholder similar to the one you can set for an UITextField
.
How to do this?
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 UIPlaceHolderTextView
CGFloat 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.
What you can do is set up the text view with some initial value in the text
property, and change the textColor
to [UIColor grayColor]
or something similar. Then, whenever the text view becomes editable, clear the text and present a cursor, and if the text field is ever empty again, put your placeholder text back. Change the color to [UIColor blackColor]
as appropriate.
It\'s not exactly the same as the placeholder functionality in a UITextField, but it\'s close.
I found myself a verry easy way to imitate a place-holder
Edit:
Changed if statements to compare tags rather than text. If the user deleted their text it was possible to also accidentally delete a portion of the place holder @\"Foobar placeholder\"
.This meant if the user re-entered the textView the following delegate method, -(BOOL) textViewShouldBeginEditing:(UITextView *) textView
, it would not work as expected. I tried comparing by colour of text in the if statement but found that light grey color set in interface builder is not the same as light grey colour set in code with [UIColor lightGreyColor]
- (BOOL) textViewShouldBeginEditing:(UITextView *)textView
{
if(textView.tag == 0) {
textView.text = @\"\";
textView.textColor = [UIColor blackColor];
textView.tag = 1;
}
return YES;
}
It is also possible to reset the placeholder text when the keyboard returns and the [textView length] == 0
EDIT:
Just to make the last part clearer - here\'s is how you can set the placeholder text back:
- (void)textViewDidChange:(UITextView *)textView
{
if([textView.text length] == 0)
{
textView.text = @\"Foobar placeholder\";
textView.textColor = [UIColor lightGrayColor];
textView.tag = 0;
}
}
You can set the label on the UITextView
by
[UITextView addSubView:lblPlaceHoldaer];
and hide it on TextViewdidChange
method.
This is the simple & easy way.
If someone needs a Solution for Swift:
Add UITextViewDelegate to your class
var placeHolderText = \"Placeholder Text...\"
override func viewDidLoad() {
super.viewDidLoad()
textView.delegate = self
}
func textViewShouldBeginEditing(textView: UITextView) -> Bool {
self.textView.textColor = .black
if(self.textView.text == placeHolderText) {
self.textView.text = \"\"
}
return true
}
func textViewDidEndEditing(textView: UITextView) {
if(textView.text == \"\") {
self.textView.text = placeHolderText
self.textView.textColor = .lightGray
}
}
override func viewWillAppear(animated: Bool) {
if(currentQuestion.answerDisplayValue == \"\") {
self.textView.text = placeHolderText
self.textView.textColor = .lightGray
} else {
self.textView.text = \"xxx\" // load default text / or stored
self.textView.textColor = .black
}
}
Simple Swift 3 solution
Add UITextViewDelegate
to your class
Set yourTextView.delegate = self
Create placeholderLabel
and position it inside yourTextView
Now just animate placeholderLabel.alpha
on textViewDidChange
:
func textViewDidChange(_ textView: UITextView) {
let newAlpha: CGFloat = textView.text.isEmpty ? 1 : 0
if placeholderLabel.alpha != newAlpha {
UIView.animate(withDuration: 0.3) {
self.placeholderLabel.alpha = newAlpha
}
}
}
you might have to play with placeholderLabel
position to set it up right, but that shouldn\'t be too hard
I extended KmKndy\'s answer, so that the placeholder remains visible until the user starts editing the UITextView
rather than just taps on it. This mirrors the functionality in the Twitter and Facebook apps. My solution doesn\'t require you to subclass and works if the user types directly or pastes text!
- (void)textViewDidChangeSelection:(UITextView *)textView{
if ([textView.text isEqualToString:@\"What\'s happening?\"] && [textView.textColor isEqual:[UIColor lightGrayColor]])[textView setSelectedRange:NSMakeRange(0, 0)];
}
- (void)textViewDidBeginEditing:(UITextView *)textView{
[textView setSelectedRange:NSMakeRange(0, 0)];
}
- (void)textViewDidChange:(UITextView *)textView
{
if (textView.text.length != 0 && [[textView.text substringFromIndex:1] isEqualToString:@\"What\'s happening?\"] && [textView.textColor isEqual:[UIColor lightGrayColor]]){
textView.text = [textView.text substringToIndex:1];
textView.textColor = [UIColor blackColor]; //optional
}
else if(textView.text.length == 0){
textView.text = @\"What\'s happening?\";
textView.textColor = [UIColor lightGrayColor];
[textView setSelectedRange:NSMakeRange(0, 0)];
}
}
- (void)textViewDidEndEditing:(UITextView *)textView
{
if ([textView.text isEqualToString:@\"\"]) {
textView.text = @\"What\'s happening?\";
textView.textColor = [UIColor lightGrayColor]; //optional
}
[textView resignFirstResponder];
}
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text{
if (textView.text.length > 1 && [textView.text isEqualToString:@\"What\'s happening?\"]) {
textView.text = @\"\";
textView.textColor = [UIColor blackColor];
}
return YES;
}
just remember to set myUITextView with the exact text on creation e.g.
UITextView *myUITextView = [[UITextView alloc] init];
myUITextView.delegate = self;
myUITextView.text = @\"What\'s happening?\";
myUITextView.textColor = [UIColor lightGrayColor]; //optional
and make the parent class a UITextView delegate before including these methods e.g.
@interface MyClass () <UITextViewDelegate>
@end
I recommend to use SZTextView
.
https://github.com/glaszig/SZTextView
Add your default UITextView
from storyboard
and then change its custom class to SZTextView
like below