はじめに
独自のUIViewを作ったり、UIViewControllerなどからユーザーに見えている画面の部品を更新したいときによく呼ぶこれらのメソッド。
これら4つの描画周りメソッドが、どのように違うのかをきちんと把握できていない、もしくはこれらがどういうものなのかよくわからないという方は意外と多いのではないでしょうか。
(私もつい3日ほど前までそうでした😓)
今回は、仕事でUIViewをカスタマイズした独自のViewを作った際に調べたことや、コードレビューの際に指摘されたことなどの知見を共有したいと思います。
なお、このページでの記述は上から順番に「 動作が軽量 」で、「 計算量が低い 」順になっています。
(下のメソッドほど動作が重く、計算量が多いです。なお一番下のメソッドは我々プログラマーが直接呼んではいけません。)
内容について意見や間違いがあった場合は、 @h1d3mun3 までご連絡をいただくか、コメント欄への記入をいただけると幸いです。
(間違っている内容を公開しておくのは大変お恥ずかしいので、もしも見つけていただいたら遠慮なくご連絡いただければ幸いです。)
setNeedsDisplay
この処理では、UIViewに「再描画が必要」というフラグを設定します。
(処理が呼ばれたUIViewに存在するサブビューには設定しません)
フラグを設定するのみで、実際に再描画の処理を行うのは 別の処理 となります。
その為、この処理を1万回呼んだためにiOSアプリが重たくなるということはありません
(実際の再描画の処理で重たくなる可能性はあります。というか実際に1万回も呼んだらそれのせいでほぼ重たくなります。多分、きっと、めいびー😢)。
setNeedsLayout
この処理は、UIViewをルートとしたサブビュー全てに「再描画が必要」というフラグを設定します。
setNeedsLayoutではサブビューも含めて自身を起点とした配下のViewすべてを再描画するので、お手軽に使いやすいのではないでしょうか。
(setNeedsDisplayと比べて、setNeedsLayoutのほうが計算量も増えますが、2017年現在流通しているiOS端末のリソースから考えると微々たるものだと思います)
layoutIfNeeded
この処理を呼ばれたView、及びその配下のViewに 画面更新の必要があった場合 にすべて 即座に 配置します。
その為、setNeedsDisplayと、setNeedsLayoutの2つより処理が重いです。
また次の2つの特徴があります。
・iOSでは UI周りの更新はMain Threadで行わないといけない というルールがあるので、この処理を呼ぶスレッドはMain Threadである必要があります。(Main Thread以外で呼ぶとアプリが落ちます😢)
・この処理を呼んだ場合再描画処理が同期実行されます。その為、再描画が完了するまで呼び出し側の実行が止まります。再描画がとてもとても大変な場合はこの処理を安易に呼ぶのではなく、更新が必要な箇所のみに対して呼ぶなどして、計算量を減らしてみるというのも良いでしょう : )
layoutSubviews
自身が持つSubViewを、制約に基づきリサイズし、位置を決定します。
(このメソッド経由で、UIViewのdrawRectなどが呼ばれているのだと思いますが、私もあまりよくわかっておりません・・・知見をお持ちの方は教えていただけるとうれしいです。)
余談
UIImageViewではdrawRectが 呼ばれません !
その為、UIImageViewでdrawRectをカスタムしたいような場合は、UIViewを継承したサブクラスのdrawRect内でUIImageを直接drawRectするほうが、お手軽です。
この仕様については後日調べて記事にしたいと思います。
参考資料
私がこの記事を書けたのはこれらの素晴らしい参考資料のおかげです。
感謝します。
・What is the relationship between UIView's setNeedsLayout, layoutIfNeeded and layoutSubviews?
・UIView が持つ描画・レイアウト更新系のメソッドメモ