はじめに
WWDC23(2023 Apple Worldwide Developers Confere)でUIKitの新機能が色々発表され、その中に一度ドロ沼で苦労した機能の手軽な解決策になりそうなviewIsAppearing(_:)
を見つけました。今後の利用頻度も高そうなので備忘録としてまとめました。
公式ドキュメント
viewIsAppearingの概要
機能概要
UIViewControllerのInstance Methodで画面が表示される時に呼び出されます。
対象バージョン
開発環境としてXcode15が必要で、iOS13以降で利用可能
呼び出されるタイミング
ざっくりまとめてしまうとviewWillAppear(_:)
の後、viewDidAppear(_:)
の前に一度の画面遷移で一回のみ呼び出されます。
ただし、viewWillAppear(_:)
とviewIsAppearing(_:)
のコールバックは同一のトランザクション内で実行されるため、両者で行われた変更は同時にユーザに表示されます。
viewWillLayoutSubviews(_:)
とviewDidLayoutSubviews(_:)
も同様のタイミングですが、これらは複数回呼び出されます。(※layoutSubviews() でも呼び出されます。)
また、viewIsAppearing(_:)
はviewDidAppear(_:)
と異なり画面遷移のアニメーション前に呼び出されます。
公式サイトより引用
特性と使い方
viewIsAppearing(_:)
はviewWillAppear(_:)
と同じく画面遷移のアニメーション前に呼び出されますが、viewWillAppear(_:)
と異なりビューのサイズや Trait Collection は決定してるため、それらをインプットにして画面描画前にレイアウトに対し変更を加えたい場合に使用します。
一例として、各種画面サイズとオブジェクトサイズに応じてレイアウトを変更するような場合にはそれぞれで分岐を入れていくのは現実的でなく、画面サイズオフセットを使うにも画面描画完了まで画面サイズが正しく取れないといったケースが考えられます。
そういったケースをviewIsAppearing(_:)
がスマートに解決してくれます。
実装例
遷移元画面(ボタンを配置し、クリックイベントをtoSecondView(_ sender: Any) に紐付け)
import UIKit
class ViewController: UIViewController {
@IBAction func toSecondView(_ sender: Any) {
let storyboard = UIStoryboard(name: "SecondView", bundle: nil)
guard let secondViewController = storyboard.instantiateViewController(withIdentifier: "secondView") as? SecondViewController else {
return
}
self.navigationController?.pushViewController(secondViewController, animated: true)
}
}
遷移先画面(Horizontal Stack ViewにLabelを横並びで配置)
import UIKit
class SecondViewController: UIViewController {
@IBOutlet weak var stackView: UIStackView!
@IBOutlet weak var heightLabel: UILabel!
@IBOutlet weak var randomStringLabel: UILabel!
public var randomString = "1234567890123456789"
override func viewDidLoad() {
super.viewDidLoad()
self.randomStringLabel.text = self.randomString
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// レイアウト修正
setStackViewAxis()
}
override func viewIsAppearing(_ animated: Bool) {
super.viewWillAppear(animated)
// レイアウト修正
// setStackViewAxis()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// レイアウト修正
// setStackViewAxis()
}
func setStackViewAxis() {
// 画面内に収まらない場合にはStackViewを縦並びに変更
if self.view.readableContentGuide.layoutFrame.width < self.stackView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).width {
self.stackView.axis = .vertical
self.randomStringLabel.numberOfLines = 0
self.heightLabel.text = "Vertical"
}
}
}
実際の動き
viewWillAppearでレイアウト修正
self.view.readableContentGuide.layoutFrame.widthが正しく取得できずif判定がfalseになり、ラベルが横並びのまま画面に収まりきらず右端が三点リーダーで丸められる。
viewIsAppearingでレイアウト修正
viewDidAppearでレイアウト修正
まとめ
以前、端末の画面サイズとオブジェクトサイズの関係で実装が複雑化し、可読性・保守性もかなぐり捨てて画面サイズオフセットとハードコーディング地獄の実装をしてしまったことがありました。
他にもviewIsAppearing(_:)
発表前はviewDidAppear(_:)
でレイアウト変更を行なっていましたが、画面のカクツキを見せないためにローディングビューを画面上に重ねたりしていました。
手軽に自然な形で実装できるようになり大変嬉しい限りです。