Skip to content

キーボードに合わせて画面を上げ下げする

2011.03.21

画面の下の方に TextField がある場合、キーボードが競り上がっても隠れないよう、キーボードが競り上がるのに合わせて画面をにょいっと上げてやる処理が必要になります。

今回は、UIEdgeInsets に関連する Tips 第2段として、キーボードに合わせて画面を上げ下げする方法を紹介します。

 


まずは、”Lorem ipsum…” UITextView、”Name:” UILabel、UITextField を、UIScrollView に入れます。このビュー構成で次のことをします:

  • キーボードが上がり下がりするタイミングを知らせてもらえるようにする
  • キーボードが競り上がるのに合わせて、画面を上げる
  • キーボードの Done ボタンが押されたら、キーボードを隠す
  • キーボードが隠れるのに合わせて、画面を下げる

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

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

- (void)viewWillAppear:(BOOL)animated
{
    // super
    [super viewWillAppear:animated];
    
    // Start observing
    if (!_observing) {
        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];
        
        _observing = YES;
    }
}

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

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

キーボードが競り上がるのに合わせて、画面を上げる

keyboardWillShow: メソッドで、次のアニメーションを行います:

  • キーボードに被る分、scrollView の中身に余白をつける
  • textField が常にキーボードのすぐ上に表示されるように、scrollView を一番下にスクロールさせる
    • 「キーボードに被る分、scrollView の中身に余白をつける」の考え方は、前回の「UIScrollView を半透明なバーに適合させる」と同じ。今回は、それをアニメーション付きで行っているだけです。

      - (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 = [_scrollView.superview
              convertRect:keyboardFrame
              fromView:nil];
          textViewFrame = _scrollView.frame;
          overlap = MAX(0.0f,
              CGRectGetMaxY(textViewFrame) - CGRectGetMinY(keyboardFrame));
          
          // Calc insets of _scrollView
          UIEdgeInsets insets;
          insets = UIEdgeInsetsMake(0.0f, 0.0f, overlap, 0.0f);
          
          // Animate insets of _scrollView
          NSTimeInterval duration;
          UIViewAnimationCurve animationCurve;
          void (^animations)(void);
          duration = [[userInfo
              objectForKey:UIKeyboardAnimationDurationUserInfoKey]
              doubleValue];
          animationCurve = [[userInfo
              objectForKey:UIKeyboardAnimationCurveUserInfoKey]
              integerValue];
          animations = ^(void) {
              // Set insets of _scrollView
              _scrollView.contentInset = insets;
              _scrollView.scrollIndicatorInsets = insets;
          };
          [UIView
              animateWithDuration:duration
              delay:0.0
              options:(animationCurve << 16)
              animations:animations
              completion:nil];
          
          // Scroll to bottom
          CGRect rect;
          rect.origin.x = 0.0f;
          rect.origin.y = _scrollView.contentSize.height - 1.0f;
          rect.size.width = CGRectGetWidth(_scrollView.frame);
          rect.size.height = 1.0f;
          [_scrollView scrollRectToVisible:rect animated:YES];
      }
      

      キーボードの Done ボタンが押されたら、キーボードを隠す

      Done ボタンが押されたことを知るには、textField の delegate になり -textFieldShouldReturn: メソッドを実装します。ここで、textField を resignFirstResponder させれば、キーボードは隠れることになります。

      - (BOOL)textFieldShouldReturn:(UITextField *)textField
      {
          // Hide keyboard
          [textField resignFirstResponder];
          
          return YES;
      }
      

      キーボードが隠れるのに合わせて、画面を下げる

      keyboardWillHide: メソッドで、scrollView の contentInset と scrollIndicatorInsets を元に戻します。

      - (void)keybaordWillHide:(NSNotification*)notification
      {
          // Get userInfo
          NSDictionary *userInfo;
          userInfo = [notification userInfo];
          
          // Animate insets of _scrollView
          NSTimeInterval duration;
          UIViewAnimationCurve animationCurve;
          void (^animations)(void);
          duration = [[userInfo
              objectForKey:UIKeyboardAnimationDurationUserInfoKey]
              doubleValue];
          animationCurve = [[userInfo
              objectForKey:UIKeyboardAnimationCurveUserInfoKey]
              integerValue];
          animations = ^(void) {
              // Set insets of _scrollView
              _scrollView.contentInset = UIEdgeInsetsZero;
              _scrollView.scrollIndicatorInsets = UIEdgeInsetsZero;
          };
          [UIView
              animateWithDuration:duration
              delay:0.0
              options:(animationCurve << 16)
              animations:animations
              completion:nil];
      }
      
      Advertisements

From → Develop

5 Comments
  1. PrototypeNexus6 permalink

    う〜ん、メソッドはタイミングよく呼ばれているのですが、スクロールしてくれません(泣)どうしてかなー、ちなみに、insets = UIEdgeInsetsMake(-overlap, 0.0f, 0.0f, 0.0f);とすると上には動いてくれましたが、隠れた部分をスクロールで見ることができませんでした。何が悪いのかなあ…

    • * お返事が相当遅くなってしまいすみません。もう解決済みかもしれませんが‥‥

      scrollView の contentSize が適切に設定されていない可能性が考えられます。
      この ViewController の viewDidLoad メソッドで、scrollView.contentSize = scrollView.frame.size などとして、もう一度挙動を確認してみてください。

  2. komi permalink

    _observing
    とは何を表していますか?

    • _observing はノーティフィケーションの二重登録を防ぐためのフラグです。二重登録をすると、1回のノーティフィケーションにおいて、登録したセレクタが2度呼ばれます。往々にしてそうだと思うのですが、今回もそれは求めた挙動ではないので、_observing フラグによってそれを防いでいます。

      今回、ノーティフィケーションの登録を viewWillAppear: で、解除を viewWillDisappear: で行っています。これらが絶対に対で呼ばれる保証があれば _observing フラグは必要ありません。ただ、そんな保証はないとした方が堅牢な作りになりますし、実際に iOS のバージョンが上がって挙動が変わって対で呼ばれなくなったなんてこともありました。或は同僚がこのクラスの viewWillAppear: を何度も呼ぶかもしれません。少し気にし過ぎと思われるかもしれませんが、二重登録によって、登録したセレクタが2度呼ばれるというのは、フレームワークからしたら正常な動作で、見つけにくいバグを生み出す危険性を孕んでいます。何らかの方法でノーティフィケーションの二重登録を防ぐ癖をつけることをお勧めします。

Trackbacks & Pingbacks

  1. iOSでGoogleAutoCompleteするサンプルコードを公開しました - Web学び

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