Skip to content

UITextView をキーボードと連携させる

2011.03.27

画面上に UITextView がある場合、キーボードが競り上がったときに被ってしまう領域を考慮して UITextView を調節する必要があります。
今回は、UIEdgeInsets に関連する Tips 第4段として、UITextView をキーボードと連携させる方法を紹介します。

 


“キーボードに合わせて画面を上げ下げする”では、UIScrollView の contentInset と scrollIndicatorInsets を調節することで、キーボードに隠れてしまう領域ができる問題を回避しました。UITextView も UIScrollView のサブクラスなので同じ方法が使えそうですが、実は使えません。“UITextView:キーボード絡みの挙動・問題点”で説明した通り、UITextView の contentInset.bottom はいろいろなタイミングで変わってしまうからです。

そこで、UITextView をキーボードに合わせて調節するときは、UITextView の frame を調節することにします。UITextView のもともとの contentInset.bottom が 0 の場合は、この方法が使えます。

手順は以下の通りです:

  • キーボードが上がり下がりするタイミングを知らせてもらえるようにする
  • キーボードが競り上がるのに合わせて、UITextView の frame を調節する。UIToolbar に Done アイテムを表示する。
  • Done アイテムがタップされたらキーボードを隠す
  • キーボードが隠れるのに合わせて、UITextView の frame をリセットする。contentInset もリセットする。Done アイテムを消す。

キーボードが上がり下がりするタイミングを知らせてもらえるようにする

キーボードが上がり下がりするタイミングは、UIKeyboardWillShowNotification と UIKeyboardWillHideNotification をオブザーブすることで知ることができます。ビューコントローラの viwWillAppear: でオブザーブ開始、viewWillDisappear: でオブザーブ終了するとよいでしょう。

- (void)viewWillAppear:(BOOL)animated
{
    // super
    [super viewWillAppear:animated];
    
    // Register for notifiactions
    if (!_registered) {
        NSNotificationCenter *center;
        center = [NSNotificationCenter defaultCenter];
        [center
            addObserver:self
            selector:@selector(keyboardWillShow:)
            name:UIKeyboardWillShowNotification
            object:nil];
        [center
            addObserver:self
            selector:@selector(keybaordWillHide:)
            name:UIKeyboardWillHideNotification
            object:nil];
        
        _registered = YES;
    }
}

- (void)viewWillDisappear:(BOOL)animated
{
    // super
    [super viewWillDisappear:animated];
    
    // Unregister from notification center
    if (_registered) {
        NSNotificationCenter *center;
        center = [NSNotificationCenter defaultCenter];
        [center
            removeObserver:self
            name:UIKeyboardWillShowNotification
            object:nil];
        [center
            removeObserver:self
            name:UIKeyboardWillHideNotification
            object:nil];
        
        _registered = NO;
    }
}

これで、キーボードが競り上がり始める時に keyboardWillShow:、キーボードが隠れ始める時に keyboardWillHide: が呼ばれるようになりました。

キーボードが競り上がるのに合わせて、UITextView の frame を調節する。UIToolbar に Done アイテムを表示する。

keyboardWillShow: を実装します。

- (void)keyboardWillShow:(NSNotification*)notification
{
    // Get userInfo
    NSDictionary *userInfo;
    userInfo = [notification userInfo];
    
    // Calc overlap of keyboardFrame and textViewFrame
    CGFloat overlap;
    CGRect keyboardFrame;
    CGRect textViewFrame;
    keyboardFrame = [[userInfo
        objectForKey:UIKeyboardFrameEndUserInfoKey]
        CGRectValue];
    keyboardFrame = [_textView.superview
        convertRect:keyboardFrame
        fromView:nil];
    textViewFrame = _textView.frame;
    overlap = MAX(0.0, CGRectGetMaxY(textViewFrame) - CGRectGetMinY(keyboardFrame));
    
    // Calc textViewFrameEnd
    CGRect textViewFrameEnd;
    textViewFrameEnd = _textView.frame;
    textViewFrameEnd.size.height -= overlap;
    
    // Animate frame of _textView
    NSTimeInterval duration;
    UIViewAnimationCurve animationCurve;
    void (^animations)(void);
    duration = [[userInfo
        objectForKey:UIKeyboardAnimationDurationUserInfoKey]
        doubleValue];
    animationCurve = [[userInfo
        objectForKey:UIKeyboardAnimationCurveUserInfoKey]
        integerValue];
    animations = ^(void) {
        _textView.frame = textViewFrameEnd;
    };
    [UIView
        animateWithDuration:duration
        delay:0.0
        options:(animationCurve << 16)
        animations:animations
        completion:nil];
    
    // Show done item
    UIBarButtonItem *doneItem;
    doneItem = [[[UIBarButtonItem alloc]
        initWithBarButtonSystemItem:UIBarButtonSystemItemDone
        target:self
        action:@selector(doneAction:)]
        autorelease];
    [self.navigationItem
        setRightBarButtonItem:doneItem
        animated:YES];
}

Done アイテムがタップされたらキーボードを隠す

doneAction: メソッドを実装します。

- (void)doneAction:(id)sender
{
    // End editing
    [self.view endEditing:NO];
}

キーボードが隠れるのに合わせて、UITextView の frame をリセットする。contentInset もリセットする。Done アイテムを消す。

keyboardWillHide: メソッドを実装します。

- (void)keybaordWillHide:(NSNotification*)notification
{
    // Get userInfo
    NSDictionary *userInfo;
    userInfo = [notification userInfo];
    
    // Record contentOffset
    CGPoint contentOffset;
    contentOffset = _textView.contentOffset;
    
    // Reset frame and insets keeping appearance
    _textView.frame = self.view.bounds;
    _textView.contentInset = UIEdgeInsetsZero;
    _textView.scrollIndicatorInsets = UIEdgeInsetsZero;
    _textView.contentOffset = contentOffset;
    
    // Animate contentOffset of _textView if needed
    CGFloat contentOffsetDeltaY;
    contentOffsetDeltaY = _textView.contentSize.height - (contentOffset.y + CGRectGetHeight(_textView.frame));
    if (contentOffsetDeltaY < 0.0) {
        // Animate contentOffset of _textView
        NSTimeInterval duration;
        UIViewAnimationCurve animationCurve;
        void (^animations)(void);
        duration = [[userInfo
            objectForKey:UIKeyboardAnimationDurationUserInfoKey]
            doubleValue];
        animationCurve = [[userInfo
            objectForKey:UIKeyboardAnimationCurveUserInfoKey]
            integerValue];
        animations = ^(void) {
            _textView.contentOffset = CGPointMake(contentOffset.x, contentOffset.y + contentOffsetDeltaY);
        };
        [UIView
            animateWithDuration:duration
            delay:0.0
            options:(animationCurve << 16)
            animations:animations
            completion:nil];
    }
    
    // Hide done item
    [self.navigationItem setRightBarButtonItem:nil animated:YES];
}

* frame と contentInset, scrollIndicatorInsets はアニメーションなしでリセットし contentOffset をアニメートするという、少々回りくどい方法を取っていますが、以下のようにするとアニメーションがスムースに行われない問題があったのでこうしました。

- (void)keybaordWillHide:(NSNotification*)notification
{
    // Get userInfo
    NSDictionary *userInfo;
    userInfo = [notification userInfo];
    
    // Animate frame and insets of _textView
    NSTimeInterval duration;
    UIViewAnimationCurve animationCurve;
    void (^animations)(void);
    duration = [[userInfo
        objectForKey:UIKeyboardAnimationDurationUserInfoKey]
        doubleValue];
    animationCurve = [[userInfo
        objectForKey:UIKeyboardAnimationCurveUserInfoKey]
        integerValue];
    animations = ^(void) {
        _textView.frame = self.view.bounds;
        _textView.contentInset = UIEdgeInsetsZero;
        _textView.scrollIndicatorInsets = UIEdgeInsetsZero;
    };
    [UIView
        animateWithDuration:duration
        delay:0.0
        options:(animationCurve << 16)
        animations:animations
        completion:nil];
    
    // Hide done item
    [self.navigationItem setRightBarButtonItem:nil animated:YES];
}

残る問題点

冒頭で断った通り、今回紹介した方法は UITextView のもともとの contentInset.bottom が 0 のときのみに使用できる方法です。UITextView の contentInset.bottom は多くの場合 0 でしょうから、この方法で事足りると思います。
しかし、もともとの contentInset.bottom が 0 ではない場合はどうすればよいのか。次回は、ここに踏み込んでいきたいと思います。

Advertisements

From → Develop

Leave a Comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s