UITextView で Placeholder を使う

環境:iOS SDK 7.1

UITextField には Placeholder が用意されているけど、UITextView にはないみたい。
以下の stackoverflow にそのものズバリな回答があったのでこれをそのまま使った。

ios - Placeholder in UITextView - Stack Overflow
http://stackoverflow.com/questions/1328638/placeholder-in-uitextview

UITextView クラスを継承して Placeholder 機能を追加した UIPlaceHolderTextView クラスを作成する。使う場合には以下のようにplaceholderプロパティに文字列を設定してあげればよい。文字色を変えたければplaceholderColorプロパティに UIColor を設定する。

placeHolderTextView.placeholder = @"タップして内容を入力してください";
placeHolderTextView.placeholderColor = [UIColor lightGrayColor]; // デフォルトは lightGrayColor

こんなかんじになる。

f:id:xyk:20140911175856g:plain

UIPlaceHolderTextView.h

#import <UIKit/UIKit.h>

@interface UIPlaceHolderTextView : UITextView

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

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

@end

UIPlaceHolderTextView.m

#import "UIPlaceHolderTextView.h"

@interface UIPlaceHolderTextView ()
@property(nonatomic, strong) UILabel *placeHolderLabel;
@end

@implementation UIPlaceHolderTextView

CGFloat const UI_PLACEHOLDER_TEXT_CHANGED_ANIMATION_DURATION = 0.25;

- (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
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
        [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

やってることは

  • Placeholder を UILabel で作成し、UITextView の subview に追加
  • NSNotificationCenter に UITextView のUITextViewTextDidBeginEditingNotificationを登録
  • UITextView のテキストに変更があったら Placeholder の alpha 値を変更して表示、非表示を切り替え

という流れ。

以下は、UIPlaceHolderTextView を使う例。nb や sb は使わずコードだけで実装。
ついでに TextView で入力後にキーボードが隠れるように AccessoryView に「完了」ボタンを追加している。

#import "SampleTextViewController.h"
#import "UIPlaceHolderTextView.h"

@interface SampleTextViewController ()
@property(nonatomic, strong) UIPlaceHolderTextView *myPlaceHolderTextView;
@end

@implementation SampleTextViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor grayColor];

    // ツールバー作成
    UIToolbar *toolBar = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, 44)];
    UIBarButtonItem *btn = [[UIBarButtonItem alloc] initWithTitle:@"完了"
                                                            style:UIBarButtonItemStylePlain
                                                           target:self
                                                           action:@selector(performDoneButtonAction)];

    // 「完了」ボタンを右寄せにしたいので左側に可変間隔スペーサーを挟む
    UIBarButtonItem *flexibleSpacer = [[UIBarButtonItem alloc]
                                       initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
                                       target:nil
                                       action:nil];
    toolBar.items = @[flexibleSpacer, btn];

    // PlaceHolder を拡張した TextView 作成
    const float TEXT_VIEW_WIDTH_SPACE = 40;
    self.myPlaceHolderTextView = [[UIPlaceHolderTextView alloc] init];
    self.myPlaceHolderTextView.placeholder = @"タップして内容を入力してください";
    self.myPlaceHolderTextView.frame = CGRectMake(TEXT_VIEW_WIDTH_SPACE / 2, 100,
                                       self.view.bounds.size.width - TEXT_VIEW_WIDTH_SPACE, 200);
    // AccessoryView にツールバーを追加する
    self.myPlaceHolderTextView.inputAccessoryView = toolBar;

    [self.view addSubview:self.myPlaceHolderTextView];
}

- (void)performDoneButtonAction
{
    // キーボードを隠す
    [self.myPlaceHolderTextView resignFirstResponder];
}

@end