2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ViewIsAppearingの備忘録 ~画面描画前にレイアウトを計算・変更する~

Posted at

はじめに

WWDC23(2023 Apple Worldwide Developers Confere)でUIKitの新機能が色々発表され、その中に一度ドロ沼で苦労した機能の手軽な解決策になりそうなviewIsAppearing(_:)を見つけました。今後の利用頻度も高そうなので備忘録としてまとめました。

公式ドキュメント

viewIsAppearingの概要

機能概要

UIViewControllerのInstance Methodで画面が表示される時に呼び出されます。

対象バージョン

開発環境としてXcode15が必要で、iOS13以降で利用可能

呼び出されるタイミング

ざっくりまとめてしまうとviewWillAppear(_:)の後、viewDidAppear(_:)の前に一度の画面遷移で一回のみ呼び出されます。
ただし、viewWillAppear(_:)viewIsAppearing(_:)のコールバックは同一のトランザクション内で実行されるため、両者で行われた変更は同時にユーザに表示されます。
viewWillLayoutSubviews(_:)viewDidLayoutSubviews(_:)も同様のタイミングですが、これらは複数回呼び出されます。(※layoutSubviews() でも呼び出されます。)
また、viewIsAppearing(_:)viewDidAppear(_:)と異なり画面遷移のアニメーション前に呼び出されます。

viewIsAppearing.png
公式サイトより引用

特性と使い方

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になり、ラベルが横並びのまま画面に収まりきらず右端が三点リーダーで丸められる。
viewWillAppear.gif

viewIsAppearingでレイアウト修正

ラベルが縦並びに変更済みで画面が表示される。
viewIsAppearing.gif

viewDidAppearでレイアウト修正

画面表示完了後、ラベルが横並びから縦並びに変更される。
viewDidAppear.gif

まとめ

以前、端末の画面サイズとオブジェクトサイズの関係で実装が複雑化し、可読性・保守性もかなぐり捨てて画面サイズオフセットとハードコーディング地獄の実装をしてしまったことがありました。
他にもviewIsAppearing(_:)発表前はviewDidAppear(_:)でレイアウト変更を行なっていましたが、画面のカクツキを見せないためにローディングビューを画面上に重ねたりしていました。
手軽に自然な形で実装できるようになり大変嬉しい限りです。

2
0
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
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?