今回は、はじめに前回の最後で話題にのぼった CGRect
まわりの補足をしつつ、引き続いて UIKit
を用いて iOS 開発を行うときのチップス集的な技術ブログ「同じような処理だけどこっちの方がいいよってやつ」を読み進めていく感じにしますね。どうぞよろしくお願いします。
——————————————————————————————————————— 熊谷さんのやさしい Swift 勉強会 #290
00:00 開始 00:35 CGRect.null の是非 02:36 CGRect.null は何のために存在するか 04:27 CGRect.infinite や矩形の演算 07:01 UIView でも矩形演算の価値観は有効? 07:55 UIView での既定値は .zero 08:28 特殊な値は扱いに注意 13:24 意見というものは偏りがち 15:17 実際に UIView で CGRect.null を使ってみる 18:21 CGRect.zero とも比べてみる 18:53 CGRect.zero の UIView は無いものとされる? 20:55 CGRect.null な UIView は得体の知れない感じ 21:57 CGRect.null な UIVew の存在を確認 25:24 CGRect.null な UIView 操作で強制終了することも 26:26 CGRect.null な UIView は使わないのが無難 27:21 クロージングと次回の展望 ———————————————————————————————————————
Transcription & Summarize : 熊谷さんのやさしい Swift 勉強会 #290
では、始めていきますね。今日も引き続き、このテーマのブログを見ていきます。今回は、特にiOSアプリの開発に役立つUIキットについて触れることが主な内容です。前回、少しだけ触れた部分があったので、それについてさらに調べてみました。その結果を共有したいと思います。
次に進むと、CGRectに関する話題ですね。UIビューのフレームに対して、とりあえずCGRect.zero
を設定することがありますが、「それよりも無効な値を設定しておいたほうが明瞭だ」という提案がありますね。この辺りの価値観もブログに書いてもらえると嬉しいですね。
CGRect.zero
が原点とサイズ0を指す一方、nil
は座標上に存在しないことを示します。これは微妙な違いですが、大切なポイントです。0を設定するときの多くは、オートレイアウトなどによって座標を示したいケースが多く、座標が存在しないことを示したい場合にはnil
のほうが正しいとされています。本当に正しいのか、調べた人はいるのでしょうか。
個人的な見解としては、確かに少し過剰かもしれないと思います。ただ、私自身の中ではその辺りの考えを深めて見ていきたいですね。まず、nil
とは何であり、何のために存在しているのかを見ていきたいと思います。
前回、コメントでも教えてもらったのですが、やはり公式ドキュメントを調べるのが一番良い方法ですね。CGRect.nil
を調べていきます。この表現方法からも分かるように、オブジェクティブCの頃からあったようです。iOS 2.0でnil
が導入され、nil
レクトとzero
レクトは異なるものであることが明記されています。これらは、二つのレクトの共通部分を取ったときに共通部分がなかったりした場合を示すために用意されています。
簡単に言うと、矩形の衝突判定などで使われます。例えば、昔はキャラクターが矩形で捉えられ、その接触判定に使われていました。最近のゲームはもっと賢い方法がありますが、こういった用途で使われることが多いですね。
さらに、infinite
という用語も気になって調べてみましたが、これは無限を示す場合などに使われます。具体的な使用例が少ないですが、抽象的な平面を作るときに使えるかもしれません。CGRect
には意外と多くの演算機能が用意されているので、これらの機能も合わせて使うと便利ですね。
次に、CGRectDivide
というメソッドもあり、矩形の分割が可能です。これにより、CGRect
内にポイントが含まれているかなどのメソッドにも使われることがあります。これらの機能を駆使することで、非常に便利な操作が可能となります。
このように、CGRect
の様々な機能を理解し、適切に使うことで、UI開発がさらに効率的になるでしょう。次回もこれらのテーマに沿って進めていきたいと思います。 仮にこれがちゃんと想定されているなら、ブログの書き込みもありかもしれません。しかし、インフィニットとしてヌルを設定しておくことと、ヌルと0は確かに意味が全然違います。たとえば、0.0
に質量ゼロのビューが置いてあるというイメージと、ヌルの場合のUIビューのフレーム処理がどういうものか次第で、そのフレームが無効な状態になっていることを表わせるのです。
UIビューの検証で言えば、ディフォルトイニシャライザーでUIビューをインスタンス化するとフレームはゼロに設定されます。これはヌルの設定であれば、もしかするとより有力になってくるかもしれません。ただし、「ゼロ」という設定は正しいことが多いですが、それでも完全な安心は得られないかもしれません。ヌルが本当に正しいのかどうかが問題です。
こういう特殊な値は、状況が想定されていないと危険です。間違いとまでは言いませんが、想定外のヌルが未定義なのか、定義域に入っているのか、ここが非常に重要なポイントです。これがわからない限りは、安全な選択をしておいたほうがよいでしょう。
このような疑問を解決するために一番ありがたいのは、公式ドキュメントです。UIビューのフレームがヌルを想定しているかどうか調べてみましたが、特に触れられていません。フレームに関する情報はiOS2.0からあり、CGXのヌルも2.0からですので、想定されている可能性も考えられますが、具体的な記述はないのです。0は問題がないと考えられるとしても、サイズが0のビューについても議論が発展してきます。
エンプティビュー(サイズが0のビュー)は不自然ではありません。そういうビューが存在しても、危険性は感じられません。しかし、ヌルは完全に無効な感じがして怖いですよね。ドキュメントにすら触れられていないとなると、敬遠したくなるのが普通です。
ここまでドキュメントでは不明だったので、ChatGPTにも聞いてみたところ、やはり「避けたほうがいい」という回答が得られました。これは私個人の所感でもありますが、一般的な提案でも同様に「それはよくない」と断言されることがあります。具体的には「予期しない問題が生じる可能性が考えられるなら、使わないほうがいいですよ」といった感じでした。
こういった不安要素は避けたほうが無難です。ヌルの代わりにゼロを使えばいいのか、ヌルを避けるべきなのかについては、根拠があればそれに従うべきです。公式ドキュメントや確立された知識には触れられていないので、個々のケースに対する判断が重要になります。
また、例えばlet value: Int
型の変数を用意して、初期化を遅らせるケースもあります。スイフトの言語仕様的にはこれは正しい書き方とされており、特に気にせず使っても問題ありません。計算型プロパティのフレームにヌルを入れるべきかどうかも同様の議論です。根拠がしっかりしていれば、その選択は正しいと言えます。 そんな感じで情報を揃えていても、何が得られるか分かりませんが、一応実際の動きを見てみたんですよ。しかし、それでもやはり決定的なことは分からなくて、先ほど言ったように、なるべく避けたほうがいいんじゃないかなという結論に至ったんです。
具体的に何を試したかというと、iOSアプリを作って、ビューコントローラーのところで試してみました。例えば、ビューディッドロードで、フレームが nil
のサブビューを配置したらどうなるかを試してみたんです。
まず、インスタンスを外に配置しておきます。例えば、let subview = UIView(frame: nil)
のようにして。それを viewDidLoad
の後に view.addSubview(subview)
で追加します。通常であれば、オートレイアウトなどの設定をする前に addSubview
するかと思いますが、nil
設定されている UIView
を追加するべきかどうか悩みます。追加しないという選択肢もありますが、今回はあえて追加してみました。
これでビルドを実行すると、見た目には特に何も表示されません。背景色をアレンジして試し、更にビューヒエラルキーを見てみると特に問題はないように思えますが、実際にフレームが nil
の状態のビューは見当たりません。当たり前のことかもしれませんが、フレームがゼロの場合も同じように表示されません。ゼロの方が怖くないような気もします。
次に、subview
の中にさらに UIView
を subSubView
として追加してみました。例えば subSubView = UIView(frame: CGRect(x: 0, y: 0, width: 500, height: 500))
で、その背景色を黄色にして、subview.addSubview(subSubView)
としてみました。その結果、subSubView
は表示されましたが、親ビューである subview
は存在していないように見えました。
フレームがゼロの UIView
はどうやら描画上最適化されて無視されているようです。質量を最低でも1以上にすると普通に表示されるという結果でした。つまり、UIView(frame: nil)
または UIView(frame: CGRect.zero)
のような設定は、実際の表示には影響しないということがわかりました。
以上の実験からわかるのは、nil
やゼロのフレームを持つ UIView
は、存在していないかのように無視されることが多いということです。例えば、ビューがすごく遠くに配置されている可能性もあるかもしれませんが、そこまでの検証は行わなかったので分かりません。
そういうわけで、完全に動かないわけではないが、やはり安心感はありません。ですから、nil
のフレームを持つビューは基本的に使用しない方が良いと思います。 なので、それがあくまでも自分の期待が存在していないと嬉しいということです。実際、黄色のビューが見えなくなっているのを見ると、ない方が有力という希望や願望があるわけです。ここで、UILabel
をちょっと用意して...あ、ラベルじゃないですね、UITextView
かUITextField
を用意します。そして、そのフレームを適当に設定します。たとえば、50x50
から100x100
くらいの大きさにしておいて、このラベルをサブビューに追加します。
次に、このサブビューに対してラベルを設定し、その存在を確認したいと思います。ラベルのデリゲートに自分自身を設定し、extension ViewController
でUITextFieldDelegate
を実装していきます。textFieldShouldReturn
メソッドを使って、リターンキーが押されたときにテキストフィールドのテキストをNSLog
で表示させます。また、テキストフィールドのエディティングが終了するようにtextField.resignFirstResponder()
を呼び出して、エディティングを終了させます。
このようにしてあげると、シミュレーターを使って実行できます。まず、テキストフィールドが見えるかどうかをチェックして、リターンキーを押して入力を終了させると、NSLog
にテキストが表示されるはずです。しかし、場合によってはアプリがクラッシュする可能性もあります。クラッシュする原因は、たとえばCALayer
のポジション計算ミスなどが考えられます。これを回避するために少し控えめな入力を試してみても、再現することがあります。
要は、見えないけれども存在しているビューがあるということが確認できれば、例外処理やオプショナルの取り扱いにも慎重になるべきだという結論になります。このようなランタイムエラーが多発する場合は、強制アンラップオプションや安易な使い方は避けるべきだという教訓になります。
今日はこの辺で終わりにしましょう。また次回、ブログの続きをじっくり見ていきたいと思います。お疲れ様でした。ありがとうございました。