どうも。「いらないって言ってたのに開発機をいじってたら欲しくなっちゃいましたの法則」が発動してiPadも先週結局買っちゃった僕です。

iPad買ったら当然JailBreakしてターミナルでコード書きまくってやる、と思ってたんだけど、いきなりmobileterminalが動かなくて躓く。ふぁっきん。とは言え自分で修正するとか新しく作るとかまでする気も起きず、あーそうだJSとかHTMLくらいだったらiPad上で書いたりデバッグしたりできるエディタあったよねーと思って、AppStoreで探してみたらちらほら見付かるも、コレ、というのはまだ無い様子。当たり前だけど、Syntax Highlightができるメモ帳程度なんだよなー。

エディタのViewを作る

ここで「タッチインターフェースを有効活用したコードエディタ」ってのを思い付いたら一時代築けそうなんだけど、別に今のところなんかアイディアがあるわけでもないので、誰かが作ればいいなー、もしくはアイディアくれるといいなーとか言うだけ言っておいて、とりあえずごく普通のテキスト入力画面の話をする。

例えば

  • 画面いっぱいにTextViewが表示されてて
  • Viewが表示されたらTextViewにフォーカスが当たるようにして
  • キーボードが表示されたらそれに合わせてViewをリサイズもしくはスライドさせて
  • キーボードが非表示になったらまたViewをリサイズもしくはスライドさせる

ようなごく普通のテキスト入力画面を作りたいとする。

TextViewにフォーカスを当てるのは簡単で、例えばViewControllerのviewDidAppearとかの中でTextViewのresignFirstResponderメソッドを呼んでやればいい。そうするとTextViewにフォーカスが当たってキーボードがせりあがってくる。キーボードのサイズは、UIKeyboard(Will|Did)(Show|Hide)Notificationていう名前でキーボードの表示、非表示の際に通知が飛ぶので、それをViewControllerで受けてViewをいじってやればいい(この記事とかが参考になった: 画面いっぱいのUITextViewがキーボードに隠れないようにする ? LANCARD.LAB|ランカードコムのスタッフブログ)。リンクの記事だとアニメーションさせてるけど、UIKeyboardDidShowNotificationを受けて単にself.view.frameの値を変えてやるだけでもそれっぽくなる。

あれ、Viewが消えた

iPhone/iPod Touchだとこれで上手く行くんだけど、実はiPadだとこのままだと上手く行かない。どうなるかというと、「日本語キーボードと英語キーボードを切り換えるとViewがどんどん小さくなる、消える」みたいなことになる。これ何でなのかなと思ったら、iPadだと日本語キーボードのときはキーボードの上に変換候補を表示するバーが出るのでその分だけ英語キーボードと表示領域のサイズが違うんだけど、表示領域のサイズが変わる度にUIKeyboard(Will|Did)ShowNotificationが通知される。前述のコードだとkeyboardWillShow:が呼ばれる度にキーボードの高さの分だけViewの高さを縮めてるので、連続でkeyboardWillShow:が呼ばれちゃうとどんどん小さくなってしまうという話。

これを防ぐには、単にself.view.frame.size.heightからキーボードの高さ分丸ごと引くんじゃなくて、UIKeyboardFrameBeginUserInfoKeyでキーボードの表示領域変更前の、UIKeyboardFrameEndUserInfoKeyで変更後のサイズが取れるので、その差分を取ってViewのサイズを調整してやる必要がある。例えばこんな感じ。

- (void)keyboardWillShow:(NSNotification *)notification {
  NSDictionary *userInfo = [notification userInfo];
  UIView *superview      = self.view.superview;
  CGRect beginRect       = [superview convertRect:[[userInfo objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue] fromView:nil];
  CGRect endRect         = [superview convertRect:[[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue] fromView:nil];
  CGRect superviewFrame  = superview.frame;
  CGRect viewFrame       = self.view.frame;

  viewFrame.size.height -=
    (beginRect.origin.y > superviewFrame.size.height) // キーボードが表示されてないときは origin.y が全体の表示領域より下になってる
    ? endRect.size.height
    : endRect.size.height - beginRect.size.height;

  // 他にもやることがあればごにょごにょ

  self.view.frame = viewFrame;
}

hideの方は連続で呼ばれることはないのでそのままでも使えるけど、これも「既に非表示になってたら処理をスキップする」とかちゃんと入れといた方がいいです。あと、iPhoneSDK 3.2からUITextFieldやUITextViewにカスタムキーボードを付けてあげることができるようになったけど、iPhoneではまずやらないと思うけどiPadでは「それぞれの入力欄にそれぞれのカスタムキーボードを設定してあり、しかも全部形が違う」みたいなこともやろうと思えばできる。そうなるとコードのカオス度が格段に上がるし、上記のコードだとまた残念なことになるのでご注意を。