🕊️

【SwiftUI】 .onAppear と .task

2022/11/29に公開

SwiftUIではViewが表示されるタイミングで1度だけ呼ばれるコールバックメソッドとして.onAppear.task があります。

.onAppear

https://developer.apple.com/documentation/swiftui/view/onappear(perform:)

定義

func onAppear(perform action: (() -> Void)? = nil) -> some View

Viewが表示されるタイミングで1度だけ呼ばれます。
そのタイミングで行いたい処理をクロージャーで指定してあげます。

使用例

struct ContentView: View {
    var body: some View {
				
        Text("Sample")
	    .onAppear {
	        print("onAppear")
	    }
    }
}

.task

https://developer.apple.com/documentation/swiftui/view/task(priority:_:)

定義

func task(
    priority: TaskPriority = .userInitiated,
    _ action: @escaping () async -> Void
) -> some View

第一引数の priority: で第二引数の async(非同期)で定義された動作の優先度を指定します。 指定しなければ .userInitiated が適用されます。
また、Viewが非表示になったタイミングで自動でタスクをキャンセルします。
そのため、画面が再表示されたときに処理結果が重複するのを回避することができます。

基本的に.onAppearの代わりとして使うことができます。
.onAppearと同様にViewが表示されるタイミングで1度だけ呼ばれます。
使い方も同様で、処理をクロージャーで指定してあげます。

使用例

struct ContentView: View {
    let url = URL(string: "https://example.com")!
    @State private var message = "Loading..."

    var body: some View {
        Text(message)
            .task {
                do {
                    var receivedLines = [String]()
		    for try await line in url.lines {
		        receivedLines.append(line)
		        message = "Received \(receivedLines.count) lines"
	            }
                } catch {
                    message = "Failed to load"
            }
	}
    }
}

上記はApple Developerで紹介されている使用例です。
.onAppearのように await を使わずに書くこともできます。
ですが、このように使用するなら.onAppearでいいとは思います。

struct ContentView: View {
    var body: some View {
				
        Text("Sample")
            .task {
                print(".task")
            }
    }
}

.onAppearと.taskはどちらを使えばいいのか

そもそも何が違うのか

今まで .onAppear のなかで Task を使って非同期を行なっていたのが、.task を使うと Taskを使わずに済むというのと、
.taskは、Viewが非表示になるとタスクを自動でキャンセルします。
.task を使うと .onDisappear で制御をしなくていいので便利ですね。
また、 .onAppear の方が早く呼ばれると聞いたのですが、「早く呼ばれているのか」、「処理が早いのか」はわかりませんでした。

どちらを使えばいいのか

結局どちらがいいのという問題ですが、基本的に.onAppearでいいと思います。
ですが、処理を中断して欲しい場合がある時は、上記したように.onDisappearで制御しなくていい(自動でタスクをキャンセルしてくれる)ので.taskを使用するといいと思います。

画面遷移時の挙動

SwiftUIには
Sheet を使ったモーダル遷移と
Navigation を使ったスタック遷移があります。

Navigation を使った遷移では、子Viewから親Viewに戻ってくるタイミングで .onAppear、 .task ともに呼ばれますが、Sheetを使った遷移では呼ばれません。
また、当然ですが NavigationStack、NavigationView に .onAppear、.task を使用すると戻ってきたタイミングでは呼ばれません。

struct ContentView: View {
    var body: some View {
        
        NavigationStack {
            VStack {
                NavigationLink {
                    SecondView()
                    
                } label: {
                    Text("Navigation")
                }
            }
        }
        .onAppear {
            print(".onAppear")
        }
        .task {
            print(".task")
        }
    }
}

上記のコードでそのまま試しています。

Discussion