35
27

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

UIScreenとUIWindowの違い

Last updated at Posted at 2019-10-21

前回iOSアプリの基本構造という記事を書いたのですが、
長くなりすぎて、UIScreenとUIResponderについても入れたかったのですが、割愛しました。
今回はUIScreenについて書きます。

画面のサイズUIScreenからとるか?viewからとるか?

image.png

端末の画面サイズがコードの中で欲しいとき、ありますよね。
ViewControllerの中なら、

view.bounds

でとれますが、viewプロパティ持ってないクラスだと、この手はつかえません。
あと参照しているViewが画面全体を指定してないと、思ってたのと違うことになります。
色々やりようはあると思いますが、僕は

UIScreen.main.bounds

でとっていました。
あるいは、

view.window?.bounds

と指定する方法もありますね。
これだとUIViewが所属するUIWindowの領域がとれます。

コード的には、どれでもいいっちゃどれでもいいのですが、自分の中でもやもやしていたところなので、調べてみました。

UIScreen/UIWindow/UIView

ところで、appDelegateの中に、windowプロパティがあって、UIWindowの参照を持ってますね?
これでも画面サイズは取得することができます。

UIApplication.shared.keyWindow?.bounds

つまり端末の画面サイズをとる選択肢としては、3つあるわけです。

  1. UIScreen
  2. UIWindow
  3. UIView

3番目のUIViewからとるケースは、UIViewが端末サイズとイコールで定義されている場合限定なので、使える状況は限られます。
なので、UIScreenとUIWindow、どっちを使うのがええんや? というのが問題なわけです。

UIScreenとUIWindowの違い

端末サイズをとりたい、という目的だけで見ると、UIScreenとUIWindowは似たようなもんに見えるのですが、
ちゃんとドキュメント読んでみたら全然違うクラスでした。

Windows and Screens

UIScreenとは

UIScreenは、「ハードウェアの画面を抽象化したクラス」です。
iPhoneで言えばiPhoneの画面を表現していますし、AppleTVで言えばテレビ画面を表現しています。
iOSアプリで、最低1つ持つ必要があります。
つまりUIScreenは、iOSアプリが表示したいもの(What)を表しているのではなくて、表示する場所(Where)を表現しています。

通常のアプリであれば、UIScreen = 今アプリが稼働している端末の画面という理解でいいのですが、問題はミラーリングするようなアプリの場合です。
その場合、UIScreenはメインとサブが存在することになります。

UIWindowとは

一方、UIWindowは、iOSアプリが表示したいもの(What)を表します。
ただUIWindowそのものはユーザーの目に見えるコンテンツを直接配置する場所ではなく、
具体的なコンテンツはUIViewに乗せて、そのUIViewを乗せる場所がUIWindowです。
いわば台紙ですね。

image.png

UIWindowの取得方法ですが、「UIApplication.shared.keyWindow?」や「view.window」のwindowが実際に入るのが、
ViewDidAppear後らしいので、ViewControllerのタイミングによっては、とれないみたいです。

UIViewControllerのview.windowがロードされるタイミング

となると、appDelegate経由になるんで、下記でとらなきゃですね。

UIApplication.shared.delegate?.window

余談ですが、これでbounds取得しようとすると、

let window = UIApplication.shared.delegate?.window //UIWindow??型が返る
print(window!?.bounds) //Optional((0.0, 0.0, 375.0, 812.0))

となって、ちょっとビックリしました。

ベストプラクティスは?

というわけで、マルチスクリーンでないのであれば、UIScreenでとろうが、UIWindowでとろうが、返ってくる値としては一緒なのですが、

iPadに対応したアプリケーションを作る場合、マルチタスク機能が存在し、ScreenサイズとWindowサイズが等しくない場合があるため、アプリの表示領域のサイズを知りたい場合はWindowサイズを取得すると良い。
アプリの表示領域のサイズを取得する

という注意点もあるみたいです。

まとめると、画面サイズを取得する際にどのクラス使うかの選択基準は、こんな感じですかね。

  • UIScreenを使う→マルチスクリーンやマルチタスクを考えなくてよい
  • UIWindow(not Application Delegate)を使う→ViewControllerのライフサイクルを考えなくてよい
  • UIWindow(Application Delegate)を使う→特に考慮点が思いつかない……あれ? もしかして万能?→いやごめんなさい。UIWindow??型なので扱いがめんどくさいですねこいつ

「今アプリが稼働している端末の画面サイズが正確に欲しい」という意味合いでは、原理主義的に考えたら、AppDelegateでとるのが筋なのかな〜と思うものの、
UIScreenでとるのは楽ですし、ググったらすぐ出てくるし、ほとんどのケースで正しい値がとれるので、
マルチスクリーン対応するようなことが想定されるアプリ(動画系とか)でなければ、UIScreenでやっちゃっていい気がします。

それぞれのクラスの違いを認識して、ケースバイケースで使っていくしかなさげです。

(おまけ)Application Delegateのwindowが2重オプショナル値返す件

この件、Swift書いていてもあんまり遭遇しないケースだったので、調べてみたら、StackOverflowで質問投げてる人がいました。

Why is main window of type double optional?

UIKitフレームワークの中のイマイチなトコなので、ユーザー的にはどうしようもないですが、

let window = app.delegate?.window??.`self`()

とすることで、UIWindow?型にできます。
これはちょっとあまりエレガントではないので、

let window = app.delegate?.window ?? nil // UIWindow?

こっちにした方がいいんじゃね? という指摘が飛んでいます。
いずれにせよちょっと扱いづらいですね。

(おまけ2)UIDevice

これを書いてしばらく経って急にそういえばUIDeviceってあったなと思い出しました。

ただこのクラスは端末サイズはプロパティとして持ってませんでした。残念。

35
27
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
35
27

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?