Swift

【Swift】ViewDidLayoutSubviewsで初回だけ処理を実行する方法

AutoLayoutframeを決定しているViewに対してコードで動的に制御しようとしたら、最初の1回だけで良いのに何回も呼ばれて困った話。

ViewDidLayoutSubviewsは、ViewWillAppearViewDidAppear同様、Viewが表示されるたびに呼ばれるライフサイクルなので、

mani
mani
Viewを読み込んだ後に1回だけ処理を実行したいけど、ViewDidLoadだとViewframeが決定してないから不適切どうしよう

と言う時には一工夫必要でした。

色々調べた結果解決方法としては、1回だけ行いたい処理をlazy変数としてクロージャに書き、初回のViewDidLayoutSubviewsで実行させた直後にnilを返すという手法が一番綺麗だったのでまとめておきます。

開発環境
  • Swift 5.0.1
  • Xcode 10.1

ViewControllerのライフサイクルおさらい

簡単にViewControllerのライフサイクルをおさらいしておきましょう。(不要な方は飛ばしてください。)

ViewControllerが呼び出される時は以下の順番でライフサイクルのメソッドが実行されます。

AutoLayoutなどで動的に位置やサイズを決めているViewは、viewWillLayoutSubviewsより後に初めてframeが決定されるので、それより前に処理を実行してしまうと思い通りのレイアウトにならないというわけですね。

なので今回の場合はviewDidLayoutSubviewsを使いましょう。

1回だけ行いたい処理を「lazy変数」としてクロージャに書く

さて本題の解決法ですが、lazy変数のクロージャを作り、その中に処理を書いて、viewDidLayoutSubviewsでクロージャを実行する」という方法です。

全体のコードを示しておきます。

lazyプロパティとは

lazyは、変数が初期化されるタイミングを指定することができるプロパティです。

本来であれば、インスタンスが生成された時点で全ての変数は初期値が設定されますが、このlazyを使うと初期化されるタイミングを「最初にその変数が参照された時」に変えることができます。

上記のコードのように、lazyクロージャが初期化され値が設定される直前に1回だけ行いたい処理を書けば、「好きなタイミングで1回だけ処理をしてくれる関数」の完成です。

つまり、初回のViewDidLayoutSubviews1回だけ処理されるクロージャが実行された直後、lazy変数にはnilが返るので、2回目以降は呼ばれなくなるというわけですね!

これで1回だけ処理を実行する関数(厳密にはクロージャ)を実装することができました。

結構汎用性高いと思うので是非覚えておきましょう。