views:

4784

answers:

9

Hi all!

I'm making an application which uses an UITextView. Now I want the UITextView to have a placeholder similar to the one you can set for an UITextField.

Does anyone know how to do this?

Thanks in advance.

+15  A: 

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.

Tim
+2  A: 

You could also create a new class TextViewWithPlaceholder as a subclass of UITextView.

(This code is kind of rough -- but I think it's on the right track.)

@interface TextViewWithPlaceholder : UITextView
{

    NSString *placeholderText;  // make a property
    UIColor *placeholderColor;  // make a property
    UIColor *normalTextColor;   // cache text color here whenever you switch to the placeholderColor
}

- (void) setTextColor: (UIColor*) color
{
   normalTextColor = color;
   [super setTextColor: color];
}

- (void) updateForTextChange
{
    if ([self.text length] == 0)
    { 
        normalTextColor = self.textColor;
        self.textColor = placeholderColor;
        self.text = placeholderText;
    }
    else
    {
        self.textColor = normalTextColor;
    }

}

In your delegate, add this:

- (void)textViewDidChange:(UITextView *)textView
{
    if ([textView respondsToSelector: @selector(updateForTextChange)])
    {
        [textView updateForTextChange];
    }

}
Amagrammer
rpetrich
+1  A: 

this is how I did it:

UITextView2.h

#import <UIKit/UIKit.h>

@interface UITextView2 : UITextView <UITextViewDelegate> {
 NSString *placeholder;
 UIColor *placeholderColor;
}

@property(nonatomic, retain) NSString *placeholder;
@property(nonatomic, retain) UIColor *placeholderColor;

-(void)textChanged:(NSNotification*)notif;

@end

UITextView2.m

@implementation UITextView2

@synthesize placeholder, placeholderColor;

- (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*)notif {
    if ([[self placeholder] length]==0)
     return;
    if ([[self text] length]==0) {
     [[self viewWithTag:999] setAlpha:1];
    } else {
     [[self viewWithTag:999] setAlpha:0];
    }

}

- (void)drawRect:(CGRect)rect {
    if ([[self placeholder] length]>0) {
     UILabel *l = [[UILabel alloc] initWithFrame:CGRectMake(8, 8, 0, 0)];
     [l setFont:self.font];
     [l setTextColor:self.placeholderColor];
     [l setText:self.placeholder];
     [l setAlpha:0];
     [l setTag:999];
     [self addSubview:l];
     [l sizeToFit];
     [self sendSubviewToBack:l];
     [l release];
    }
    if ([[self text] length]==0 && [[self placeholder] length]>0) {
     [[self viewWithTag:999] setAlpha:1];
    }
    [super drawRect:rect];
}

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


@end
bcd
+7  A: 
Jason George
awesome work george! really useful stuff, thanks for providing the full solution!
samiq
Thanks, great work both bcd and Jason. Works like a gem!
Raj
The only thing I would change with this is to listen for the event UITextViewTextDidBeginEditingNotification so that when they clicked on the textview, the placeholder would disappear. This also requires a small modification on textChanged (or renamed to didBeginEdit in my case) such that the placeholder alpha is set to 0 on this event.
freshfunk
A: 

This is great, and very helpful, but there are a couple other things missing from UITextView that would be very handy as well: the borderStyle property, to make it look more like a UITextField, and the clearButtonMode property, to get the little clear text button overlayed.

Does anybody have sample code to add those two features as well?

Pmatt
A: 

Jason and bcd, thanks so much for this. Worked perfectly for me. I had simply replace UITextView with your class and everything else just fit in.

This is not an answer. If you have a comment about an answer, please comment on the answer.
Sam Soffes
A: 

hi friend

you can set lable on the UITextView by [UITextView addSubView:lblPlaceHoldaer];

and hide it on TextViewdidChange method

it is the simple & easy way.....

Yakub Moriss

From Ahmedabad(Guj-India)

yakub_moriss
A: 

Very, very nice description. Thanks so much. I had exactly the same idea myself but this saved me about 30 minutes of fluffing around getting it working.

I agree. A borderStyle on a UITextView would be wonderful. The UITextView interface is somewhat light on useful functionality. For a curved border I'd suggest you just resort to photoshop or see if you can put a disabled UITextField behind the UITextView and make the textview transparent.

Wump
+2  A: 

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: SSTextView

SSTextView.h

//
//  SSTextView.h
//  SSToolkit
//
//  Created by Sam Soffes on 8/18/10.
//  Copyright 2010 Sam Soffes. All rights reserved.
//

@interface SSTextView : UITextView {

    NSString *_placeholder;
    UIColor *_placeholderColor;

    BOOL _shouldDrawPlaceholder;
}

@property (nonatomic, retain) NSString *placeholder;
@property (nonatomic, retain) UIColor *placeholderColor;

@end

SSTextView.m

//
//  SSTextView.m
//  SSToolkit
//
//  Created by Sam Soffes on 8/18/10.
//  Copyright 2010 Sam Soffes. All rights reserved.
//

#import "SSTextView.h"

@interface SSTextView (PrivateMethods)
- (void)_updateShouldDrawPlaceholder;
- (void)_textChanged:(NSNotification *)notification;
@end


@implementation SSTextView

@synthesize placeholder = _placeholder;
@synthesize placeholderColor = _placeholderColor;

#pragma mark NSObject

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self name:UITextViewTextDidChangeNotification object:self];

    [_placeholder release];
    [_placeholderColor release];
    [super dealloc];
}


#pragma mark UIView

- (id)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_textChanged:) name:UITextViewTextDidChangeNotification object:self];

        self.placeholderColor = [UIColor lightGrayColor];
        _shouldDrawPlaceholder = NO;
    }
    return self;
}


- (void)drawRect:(CGRect)rect {
    [super drawRect:rect];

    if (_shouldDrawPlaceholder) {
        [_placeholderColor set];
        [_placeholder drawInRect:CGRectMake(8.0, 8.0, self.frame.size.width - 16.0, self.frame.size.height - 16.0) withFont:self.font];
    }
}


#pragma mark Setters

- (void)setText:(NSString *)string {
    [super setText:string];
    [self _updateShouldDrawPlaceholder];
}


- (void)setPlaceholder:(NSString *)string {
    if ([string isEqual:_placeholder]) {
        return;
    }

    [_placeholder release];
    _placeholder = [string retain];

    [self _updateShouldDrawPlaceholder];
}


#pragma mark Private Methods

- (void)_updateShouldDrawPlaceholder {
    BOOL prev = _shouldDrawPlaceholder;
    _shouldDrawPlaceholder = self.placeholder && self.placeholderColor && self.text.length == 0;

    if (prev != _shouldDrawPlaceholder) {
        [self setNeedsDisplay];
    }
}


- (void)_textChanged:(NSNotification *)notificaiton {
    [self _updateShouldDrawPlaceholder];    
}

@end

It's a lot simpler than the others, as it doesn't use subviews (or have leaks). Feel free to use it.

Sam Soffes
I like your solution, and I added an override of `setText` to also update placeholder when changing text property programatically: - (void)setText:(NSString *)string { [super setText:string]; [self _updateShouldDrawPlaceholder]; }
olegueret
Thanks! I added that to my class: http://soff.me/2Jds
Sam Soffes