Placeholder text in UITextView

For a recent iPhone app I wanted a multi-line editable text field, in which I could display placeholder text so my user would know what to do with the field.

Placeholder text is a common affordance provided by several UI classes such as UITextField. But UITextView in iOS 4 does not support placeholder text.

A web search turned up several implementations. Some of these seemed too complex, and some failed to provide the behavior I wanted:

  • If the view is empty, show the placeholder.
  • Whenever the view is the first responder, hide the placeholder.

Here's my implementation. It's very basic. For example it doesn't let you customize the color of the placeholder text; and the placeholder is always centered vertically within the bounds of the text view. But it does provide the behaviors listed above.

PlaceholderTextView.h

#import <UIKit/UIKit.h>

@interface PlaceholderTextView : UITextView {
    UILabel *placeholderLabel;
    NSString *placeholderText;
}

@property (nonatomic, retain) NSString *placeholderText;
@end

PlaceholderTextView.m

#import "PlaceholderTextView.h"

@implementation PlaceholderTextView
@synthesize placeholderText;

- (void)hidePlaceholder {
    if ([placeholderLabel superview]) {
        [placeholderLabel removeFromSuperview];
    }
}

- (void)maybeShowPlaceholder {
    if ([placeholderText length] > 0) {
        if (![self hasText] && ![placeholderLabel superview]) {
            CGRect frame = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height);
            placeholderLabel.frame = frame;
            placeholderLabel.text = placeholderText;
            [self addSubview:placeholderLabel];
        }
    }
}

- (void)initCommon {
    if (!placeholderLabel) {
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(beganEditing:) name:UITextViewTextDidBeginEditingNotification object:nil];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(endedEditing:) name:UITextViewTextDidEndEditingNotification object:nil];

        placeholderLabel = [[UILabel alloc] init];
        placeholderLabel.numberOfLines = 0;
        placeholderLabel.backgroundColor = [UIColor clearColor];
        placeholderLabel.textColor = [UIColor lightGrayColor];
        placeholderLabel.font = self.font;
        placeholderLabel.lineBreakMode = UILineBreakModeWordWrap;
        placeholderLabel.textAlignment = UITextAlignmentCenter;
        [self maybeShowPlaceholder];
    }
}

- (void)awakeFromNib {
    [super awakeFromNib];
    [self initCommon];
}

- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        [self initCommon];
    }
    return self;
}

- (id)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
    if (self) {
        [self initCommon];
    }
    return self;
}

- (id)init {
    self = [super init];
    if (self) {
        [self initCommon];
    }
    return self;
}

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [placeholderLabel release];
    [placeholderText release];
    [super dealloc];
}

- (void)setPlaceholderText:(NSString *)newValue {
    [newValue retain];
    NSString *oldValue = placeholderText;
    placeholderText = newValue;
    [oldValue release];

    [self maybeShowPlaceholder];
}

- (void)beganEditing:(NSNotification *)notification {
    [self hidePlaceholder];
}

- (void)endedEditing:(NSNotification *)notification {
    [self maybeShowPlaceholder];
}
@end