【SwiftUI】viewWillAppear と viewWillDisappear に対応する

SwiftUI の View プロトコルには、onAppearonDisappear と言う2つのイベントが定義されています。

onAppear は画面が表示された時に呼ばれ、UIViewControllerviewDidAppear に該当します。

onDisappear は画面が表示されなくなった時に呼ばれ、こちらは viewDidDisappear に該当します。

ところが、画面が表示される前viewWillAppear と、画面が表示されなくなる前viewWillDisappear は現時点(2020年8月現在)では View プロトコルで定義されていません

viewWillAppear なんかはよく使う場面が多かったので、需要がありそうなのに何故なんでしょうね?

理由はよくわかりませんが、必要なもので無いものは作るしかないのでちょっと調べてみたところ、こちらの記事で viewWillDisappear に対応をしていたので、ありがたく模倣させて頂きました。

その記事では viewWillAppear については触れていませんでしたが、要領は全く同じなので一緒に紹介します。

【SwiftUI】viewWillAppear と viewWillDisappear に対応する

viewWillAppearviewWillDisappear の要領は同じです。

まず、UIViewControllerRepresentable プロトコルに準拠した構造体を作り、それを ViewModifier でカスタムView とし、されに View プロトコルの拡張(extension)として定義します。

ざっくりとした説明で分かりづらいかと思います(と言うか筆者の説明能力がない)ので早速コードを見ていきましょう。

onWillAppear の対応手順

ViewWillAppearHandler

struct ViewWillAppearHandler: UIViewControllerRepresentable {
    func makeCoordinator() -> ViewWillAppearHandler.Coordinator {
        Coordinator(onWillAppear: onWillAppear)
    }

    let onWillAppear: () -> Void

    func makeUIViewController(context: UIViewControllerRepresentableContext<ViewWillAppearHandler>) -> UIViewController {
        context.coordinator
    }

    func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<ViewWillAppearHandler>) {
    }

    typealias UIViewControllerType = UIViewController

    class Coordinator: UIViewController {
        let onWillAppear: () -> Void

        init(onWillAppear: @escaping () -> Void) {
            self.onWillAppear = onWillAppear
            super.init(nibName: nil, bundle: nil)
        }

        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }

        override func viewWillAppear(_ animated: Bool) {
            super.viewWillAppear(animated)
            onWillAppear()
        }
    }
}

ViewWillAppearModifier

struct ViewWillAppearModifier: ViewModifier {
    let callback: () -> Void

    func body(content: Content) -> some View {
        content
            .background(ViewWillAppearHandler(onWillAppear: callback))
    }
}

onWillAppear

extension View {
    func onWillAppear(_ perform: @escaping (() -> Void)) -> some View {
        self.modifier(ViewWillAppearModifier(callback: perform))
    }
}

onWillDisappear の対応手順

ViewWillDisappearHandler

struct ViewWillDisappearHandler: UIViewControllerRepresentable {
    func makeCoordinator() -> ViewWillDisappearHandler.Coordinator {
        Coordinator(onWillDisappear: onWillDisappear)
    }

    let onWillDisappear: () -> Void

    func makeUIViewController(context: UIViewControllerRepresentableContext<ViewWillDisappearHandler>) -> UIViewController {
        context.coordinator
    }

    func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<ViewWillDisappearHandler>) {
    }

    typealias UIViewControllerType = UIViewController

    class Coordinator: UIViewController {
        let onWillDisappear: () -> Void

        init(onWillDisappear: @escaping () -> Void) {
            self.onWillDisappear = onWillDisappear
            super.init(nibName: nil, bundle: nil)
        }

        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }

        override func viewWillDisappear(_ animated: Bool) {
            super.viewWillDisappear(animated)
            onWillDisappear()
        }
    }
}

ViewWillDisappearModifier

struct ViewWillDisappearModifier: ViewModifier {
    let callback: () -> Void

    func body(content: Content) -> some View {
        content
            .background(ViewWillDisappearHandler(onWillDisappear: callback))
    }
}

onWillDisappear

extension View {
    func onWillDisappear(_ perform: @escaping (() -> Void)) -> some View {
        self.modifier(ViewWillDisappearModifier(callback: perform))
    }
}

使用例

使用するときは、onAppear・onDisappear と同じく、任意の View 要素で宣言します(通常は最上位の View で宣言することが多い)。

struct ContentView: View {
    var body: some View {
        VStack {
            Text("onWillAppear と onWillAppear")
        }
        .onWillAppear {
            doSomething()
        }
        .onWillDisappear {
            doSomething()
        }
    }
}

以上、onWillAppear と onWillDisappear に対応する手順を紹介しました。

UIKit にあって SwiftUI に無いものは他にもありますが、ありそうで無いものについては敢えて用意していないという可能性もあり、SwiftUI の設計思想をより深く理解していたら、そもそもの自分のプログラムに問題があって、「そんなカスタマイズ必要ないよ」ってこともあるかもしれませんね

以上