UIWindowを使用したUIを作成する方法と注意点
ここに記載されている内容を使用したことによるいかなる損害、および問題が出ても一切補償はしません。自己責任で使用してください。
通常、iOSの独自のUIコンポーネントは、UIViewを継承すれば作成できます。
しかし、UIWindowを使ってUIを作る事も可能です。
わざわざUIWindowを使う位なので、当然利点はあります(笑)
UIWindowを使う利点は以下の通りです。
- ステータスバー、ソフトウェアキーボードの上にUIを表示できる。
- 独自のダイアログを作れる。
- 操作ができないように全画面を覆う表示ができる。
上記の様な使い方をする機会はそうは多くないと思いますが、ソフトウェアキーボードの上に表示できると嬉しい人は多いかも知れないですね。
例えばAndroidのToastの様な画面の最前面に出るメッセージUIを作る場合です。
勿論、Toastの表示をする位であれば通常のUIViewを継承したUIでも簡単に作成できるかと思います。
しかし、表示する位置によってはソフトウェアキーボードの下に隠れてしまう事もあるでしょう。
せっかく表示したメッセージがソフトウェアキーボードの下に隠れてしまっては嬉しくありませんね。
ソフトウェアキーボードを避けて画面の上の方に出すことも考えられますが、「どうしても画面の中央や下の方に表示したい!」という場合にはソフトウェアキーボードの上に重ねて表示させる必要が出てくるかも知れません。
「表示の度にソフトウェアキーボードを隠せば良いのでは?」ということも考えられますが、あまり現実的ではない、または使い勝手を考慮すると問題ありなので、ここでは考えないようにします(笑)
iPadのUIPopoverControllerに関して言うと、ソフトウェアキーボードとは重ならない様に自動でリサイズされてしまい、使いにくいと感じる事があります。
しかし、上に重ねて表示できるUIであればこの問題も解決するという事になります。
この様に「意外に良いかも!」と思えるUIWindowを使用したUIですが、使用時には幾つかの注意点があります。
これらの注意点を知った上できちんと使用する場所を考慮し、場合によっては使わない選択肢も考慮した方が良いかも知れません。
意外にはまりますので(笑)
UIWindowを使用したUI使用時の注意点は以下の通りです。
- 画面の向き
- 重ね順
- スケール指定
- ステータスバーのスタイル
- タイミングによっては表示したUIViewControllerの表示位置が乱れる
- メモリ解放タイミング
- 選択範囲の編集ができない時がある
- InCallなどでステータスバーの高さが変更された時の表示がおかしくなる
- setFrameメソッドで渡される値はインチキ
これらの注意点を細かく説明していきたいと思います。
なお、先日リリースした多機能テキストエディタアプリ「Wrix」(ライクス) のランチャーボタンパネルやPopupメニュー、ドラッグ&ドロップ用のZoom Popup等は、UIWindowを使用したUIで実現しています。(画像こちら)
そのため、ソフトウェアキーボードの上にランチャーボタンパネルのUIを表示させる事ができています。
※ランチャーボタンパネルやPopupメニューは多機能電卓アプリの「NeoCalcs」などの 他のアプリでも使用しています。
「Wrix」は、快適にテキスト編集を行う事を目標に一から開発した多機能テキストエディタアプリです。
余りにもこだわりすぎて開発に2年位かかりましたが(笑)、基本無料でほとんどの機能を継続して使用する事ができます。広告もありません。
ほとんど人はお金を払わなくても十分かも知れません(笑)
オリジナルUIのランチャーボタンパネルは一度使うと癖になる爽快な操作感ですので、是非試してみてください。
文章作成、メモ、メールの本文等、テキスト編集をするすべての方にお勧めです。
(Appstoreからダウンロードする)
ちなみに、この文章のほとんどは、「Wrix」とiPad mini Retinaを使用して記載しました。
– 画面の向き –
UIWindowもUIViewのサブクラスではありますが、UIViewController#viewなどに貼り付けなくても表示する事ができます。
名前の通り独立したWindowを表示できるのです。
各アプリも基本的にUIWindowの上にUIViewControllerを乗せて表示させていますね。
当然、UIWindowもUIViewController#viewなどに貼り付ける事もできますが、それではUIViewの時と同じなので貼り付けずに使用します。
基本的にサイズや位置の設定するだけで表示されます。
通常、UIViewController#viewに貼り付けたUIViewは、画面の回転や向きなどはほとんど気にすることなく使えますが、独立させて表示をさせるUIWindowの場合は以下の点が違います。
- 画面の向きに合わせて自分で回転させる必要がある
UIWindowを独立させて表示するUIは、画面の向きに合わせて自動で表示を回転してくれません。
そのため、回転イベントを自分で取得して回転させる必要があります。
画面回転時に何もしないで表示していると、画面の同じ位置にそのまま表示され続けることになります。表示の回転さえもしてくれません。
画面の回転イベントはNSNotificationCenterにUIApplicationDidChangeStatusBarOrientationNotification通知を受け取る様に登録すれば取得できます。
画面の向きに関しては、UIDeviceで取得可能ですが、単純にPortrait、Landscapeの上下左右だけではなく平面に置いている向きなどもあるので、ステータスバーの向きに合わせると良いかと思います。
画面の回転時には向きの変更を行うだけではなく、表示場所を変更しなくてはいけません。
座標の基本はPortraitの時と同じ座標系を使用するため、単純に同じ場所に表示するだけでも少し手間がかかるかと思います。
※なお、上記の回転の問題はiOS7までで、iOS8では解消されている様です。
– 重ね順 –
UIWindowには3種類のレベル(windowLevel)があります。
- UIWindowLevelNormal
- UIWindowLevelAlert
- UIWindowLevelStatusBar
同じレベルの場合は通常のUIView同様に表示した順番に重ねて表示されますが、レベルを分けることで重ね順を変更する事ができます。
加えてレベルによってはソフトウェアキーボードやステータスバーの下に表示される事もあります。
例えばToast表示は最前面に表示するなど、きちんと用途に応じて指定しておく必要があります。
– スケール指定 –
UIWindow#drawRectメソッドを使用して描画する場合、CALayer使用時と同様に画面解像度に合わせたスケールを設定しておく必要があります。
– ステータスバーのスタイル –
iOS7からはデフォルトでステータスバーがUIViewControllerの上に重なるようになりました。
そのため、ステータスバーを表示する時にはスタイルを指定する必要があるかと思います。
ここで問題になるのは、以下の点です。
- UIWindowのUIを表示している間はデフォルトスタイルのステータスバーが表示される。
iOS6までは気にする必要も無かったのですが、ステータスバーがUIViewControllerの上に重なるため、UIWindowを表示している間にステータスバーのスタイルが変化するのは好ましくないかも知れません。
勿論、「デフォルトスタイル(UIStatusBarStyleDefault)しか使わないから問題ない!」という考え方もできますが、個人的に黒背景が好きなのでどうしてもUIStatusBarStyleLightContent が良いのです(笑)
違う問題としては以下のことがあります。
- 通常はステータスバーを消しているのに、UIWindowのUIを表示している間ステータスバーが表示されてしまう
スタイルの問題を含めたステータスバーの表示の問題は、rootViewControllerプロパティを使うことで解決します。
rootViewControllerプロパティに現在のUIViewControllerを設定する事で現在表示中のUIViewControllerのステータスバー設定を使うことができます。
勿論、以下のメソッドをオーバーライドして適切な値を返す様にしておく必要はあります。
- – (UIStatusBarStyle)preferredStatusBarStyle
- – (BOOL)prefersStatusBarHidden
UIWindowのUIのrootViewControllerプロパティに現在のUIViewControllerを設定すればステータスバーの表示/非表示の切り替えやスタイルを変更できる事は説明しました。
しかし、表示するタイミングによってはまた別の問題が出ることがあります。
例えば、別のUIViewControllerを表示した後、そのUIViewControllerを非表示にして元の画面に戻って来た時にUIWindowのUIを表示する場合です。
下手をすると画面のあらぬ場所にUIViewControllerが表示されたり、おかしな向きでUIViewControllerが表示される事があります。
つまり、別のUIViewController表示中にまだ表示されていない元のUIViewControllerでUIWindowのUIを表示すると元のUIViewControllerの位置が確定していないのか、元のUIViewControllerが画面の中途半端な所やおかしな向きで表示されることがあります。
rootViewControllerプロパティを設定しなければこの様な問題は発生しないのですが、ステータスバーの問題が出てしまうので、ステータスバーのスタイルや非表示設定を変更している場合には使わざる終えないかと思います。
なお、この問題はUIViewControllerをモーダル表示した時とUINavigationControllerにpushした時では動作が異なりますので注意してください。
「非表示のUIWindow専用のUIViewControllerを用意すればいいのでは!」という考えもあるかと思いますが、上記のスタイル変更のメソッドが呼び出されても効果がないかと思います。
rootViewControllerプロパティに UIStoryboard#instantiateInitialViewControllerを設定すると上記問題が出ない様ですが、StoryBoardを使わない派なのです(笑)
参照: iOS開発におけるウィンドウ「UIWindow」の知られざる活用方法とは?
※ちなみに、この記事が出る前にこのエントリの文章は完成していました。内容を真似した訳ではありません(笑)
※なお、上記のUIViewControllerの位置がおかしくなる問題はiOS7までで、iOS8では解消されている様です。
– メモリ解放タイミング –
UIWindowのUIのrootViewControllerプロパティに現在のUIViewControllerを設定する事を説明しましたが、このrootViewControllerプロパティは、weakではなく、retainなのでretainカウントが増えます。
つまり、きちんと解放しておかないと現在のUIViewControllerのdeallocメソッドが呼び出されないことになります。
そのため、現在のUIViewControllerを非表示にするタイミングでrootViewControllerプロパティにnil代入をするなどして現在のUIViewControllerのretainカウントを減らしておく必要があります。
– 選択範囲の編集ができない時がある –
UIWindowのUIにUITextViewなどを貼り付けた場合、範囲選択はできてもコピーなどのUITextViewにデフォルトで提供されている編集メニューが出ない場合があります。
つまり、範囲選択してもコピーする事が出来ないという状態です。
UITextViewは編集メニューが出る様ですが、UIWebViewのように選択はできても編集メニューが出ないUIも存在します。
加えて、選択カーソルの表示が少し変な時もあります。例えば左側の選択カーソルの上の丸い部分が表示されないなどです。
更に編集メニューが表示させたUIPopoverは、画面の回転に合わせて位置が変更されないなどの問題も出ます。
これらの問題を理解しておくことでUIWindowのUI使用時の限界がわかるかと思います。
– InCallなどでステータスバーの高さが変更された時の表示がおかしくなる –
ステータスバーのサイズが変更されるとそれに釣られて大きさが変わることがあります。
X、Y座標はそのままでステータスバーのサイズ変更に合わせて高さが変わるイメージです。
ステータスバーのサイズが変更される機会はそうは多くないかと思いますが、対策をしておく必要があります。
– setFrameメソッドで渡される値はインチキ –
setFrameメソッドで渡される値がおかしいため、自分でサイズを変更した際にそのままframe値を使用すると、ある時は0座標に表示、そしてその後に指定位置に表示されるなどの怪現象が起こります。
setFrameメソッドで渡される値はインチキのことがあることを考慮し、setFrameメソッドをオーバーライドするなどの対策をして置くと良いでしょう。
– まとめ –
この様に問題の多いUIWindowを使用したUIですが、最前面に表示できるのはかなりの利点です。
幾つかの注意点、及び使い方を理解していれば使いやすいUIを作り出すことができると思います。
なお、これらの注意点はiOS7での話であり、今後どうなるのかはApple以外知りません。
その点を理解した上でこれらの情報を利用してください。
余りにも癖が強くて問題が山積みなUIなので、開発の相談に乗る事も可能です。
ご相談事がある場合はメールまたはTwitter等でご連絡ください。
ちなみに、iOS8では画面の向き関連の座標の扱いが変更されているため、UIWindowのUIを使用したアプリは全部アップデートをしないといけない状態になっています(苦)
まあ、それ以上に使い勝手の良いUIが作れるので、それでも使い続けようとは思います。
SkyArtsのiOSアプリ一覧
2015年6月6日追記
iOS8.1より、UIWindowのUIに貼り付けたUIViewのタッチイベントがおかしくなる問題が出始めました。そして、8.3では使い物にならなく問題が出る様になりました。
しかし、別の表示方法を使用する事で回避できる事がわかりました。この別の表示方法については、後日まとめて公開する予定です。