甘いものが好きです

iOS App開発時に感じた疑問や課題、その他の雑感などを書いていきます。

iPadのModal Viewで表示したキーボードが特定条件下でコード上から非表示にできなくなる

iPad App内でModal Viewを表示し、そのModal View内でキーボードを表示したところ、そのModal View自体を消さない限りキーボードをコード上から非表示にできなくなった。この件についていろいろと調べてみたので、以下にまとめることにする。特に断りがない限り、以下の内容は次の環境で確認を行なっているものとする。

  • 開発環境: iOS SDK 4.2
  • 動作環境: 実機(初代iPadiOS 4.2.1)

問題の詳細

表示スタイル*1がUIModalPresentationFormSheetのModalViewを表示する。そのModal Viewの中ではNavigation ControllerによるViewの階層構造を表現する。ここでは最上位のViewを「RootView」と呼び、そのRootViewからpushされたViewを「SecondView」と呼ぶことにする。SecondView上にText Fieldを配置し、Text Fieldはキーボードからの入力を受け付ける。このとき、SecondViewでキーボードを表示した後、そのキーボードを非表示にするにはキーボード上のキーボード非表示ボタンを押すか、Modal View自体を非表示にするしかない。UIResponderクラスのメソッドresignFirstResponderを使うなどしてコード上からキーボードを非表示にすることはできない。

対応策

このような状況に対して、コード上からキーボードを非表示にする制御を可能とするには、次の2つの方法が考えられる。

  • プライベートAPIを使う
  • 表示スタイルがUIModalPresentationPageSheetのModal Viewを縮小して表示する
対応策1: プライベートAPIを使う

Stack Overflowにおけるトピック(How to HIDE the iPad keyboard from a MODAL view controller?)にもある通り、キーボードを非表示にしたい場面で、UIKeyboardImplクラスを利用して次のような処理を実行すると、コード上からでもキーボードを非表示にすることができる。

[aTextField resignFirstResponder];
@try
{
    Class UIKeyboardImpl = NSClassFromString(@"UIKeyboardImpl");
    id activeInstance = [UIKeyboardImpl performSelector:@selector(activeInstance)];
    [activeInstance performSelector:@selector(dismissKeyboard)];
}
@catch (NSException *exception)
{
    NSLog(@"%@", exception);
}

ただし、UIKeyboardImplはAppleが提供しているドキュメントには記載されていないため、上記コードの利用はAppleが禁止しているプライベートAPIの使用にあたると思われる。つまり、このコードを利用したAppをApp Storeに登録しようと申請してもrejectされる可能性がある。したがって、App Storeに登録するためのiPad Appを開発している場合には注意が必要だ。

対応策2: 表示スタイルがUIModalPresentationPageSheetのModal Viewを縮小して表示する

Modal Viewの2階層目以下でコード上から非表示にできないのは、Modal Viewの表示スタイルがUIModalPresentationFormSheetの場合に限られる。例えば、表示スタイルがUIModalPresentationPageSheetのModal ViewではresignFirstResponderをコールすることによりキーボードを非表示にすることができる。表示スタイルによるこの違いを利用して、表示スタイルがUIModalPresentationPageSheetのModalViewを縮小して表示することにより、UIModalPresentationFormSheetスタイルのModalViewが2階層目以下でコードによりキーボードの表示/非表示を制御できているかのように(見かけ上)見せることができる。

Stack Overflowのトピック(iPad keyboard will not dismiss if navigation controller presentation style is “form sheet”.)にあるように、次のようにしてModal Viewのサイズを変更して表示することができる。ただし、元々のModal Viewを縮小表示しているため、Modal View内の文字等の表示に違和感が残ってしまう。また、デバイスの回転への対応をするならば、この処理の中でデバイスの向きに応じたサイズの計算等にも気をつけなければならない。

viewController.modalPresentationStyle = UIModalPresentationPageSheet;
viewController.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:viewController animated:YES];
vc.view.superview.autoresizingMask = 
    UIViewAutoresizingFlexibleTopMargin | 
    UIViewAutoresizingFlexibleBottomMargin;    
viewController.view.superview.frame = CGRectMake(
    viewController.view.superview.frame.origin.x,
    viewController.view.superview.frame.origin.y,
    540.0f,
    529.0f
);
viewController.view.superview.center = self.view.center;
[viewController release];

そもそもこれはiOS SDKのバグなのか?

そもそも表示スタイルがUIModalPresentationFormSheetのModal Viewでこのようなキーボードの表示/非表示の操作に制限があるのはiOS SDKのバグなのだろうか? 上で挙げたStack Overflowのトピック内のコメント欄によると、当初のAppleの見解ではこれは仕様であった。

This has been classified as "works as intended" by Apple engineers. I filed a bug for this a while back. Their reasoning is that the user is often going to be entering data in a modal form so they are trying to be "helpful" and keep the keyboard visible where ordinarily various transitions within the modal view can cause the keyboard to show/hide repeatedly.

ところが、その後Appleはこれがバグであると判断したらしい。*2

Actually, I just got a response from Apple on this issue: "This is a follow up to Bug ID# 8400721. After further investigation it has been determined that this is a known issue, which is currently being investigated by engineering. This issue has been filed in our bug database under the original Bug ID# 7751141. The original bug number being used to track this duplicate issue can be found in the State column, in this format: Duplicate/OrigBug#." It IS a bug!

Appleの意図は?

iOS SDKのバグだということがわかったが、当初Appleがこの問題をiOS SDKの仕様であるという見解を示した背景をここで考えてみたい。

仕様であるとAppleが判断した理由は「フォーム内でユーザがデータ入力を行う際、頻繁にキーボードの表示/非表示のアニメーションが発生することを防ぐために(FirstResponderがない場合でも)キーボードを表示したままとする方が、ユーザにとって使い勝手が良いだろう」という意図によるものだった。つまり、表示スタイルがUIModalPresentationFormSheetのModal Viewはあくまでデータ入力を行うためのViewであって、そのModal Viewの中でデータ表示専門のViewとデータ入力を行うためのViewをNavigation Controllerにより行き来するというのは、Appleが想定していたFormSheetの使い方からは若干のズレがあるということなのかもしれない。

今回この問題に遭遇して改めてAppleの「iOS Human Interface Guidelines」に目を通してみたが、その中で「Alerts, Action Sheets, and Modal Views」や「For iPad: Consider Using Popovers for Some Modal Tasks」という項において、Modal ViewやPopoverがどのように使われることをAppleが想定しているかを垣間見ることができた。このガイドラインを隅から隅まで全部読み解くことは楽なことではないが、自分が実装している部分に関係がありそうな箇所だけでも時折目を通しておくとAppleの意図を感じることができて、今回のような問題に遭遇することは少なくなるかもしれない。

*1:Modal Viewの表示スタイルについては[http://developer.apple.com/library/ios/#documentation/uikit/reference/UIViewController_Class/Reference/Reference.html:title=Appleの「UIViewController Class Reference」]内の「UIModalPresentationStyle」の項を参照。

*2:Appleのバグレポートの確認方法がわからないので、現状ではStack Overflowのトピックからの二次情報でしか確認できていない。