🕊️

【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 を使わずに書くこともできます。

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

.onAppearと.taskの違い

今まで .onAppear のなかで Task を使って非同期を行なっていたのが、.task に変わったという認識でいいと思います。
また、 .onAppear の方が早く呼ばれると聞いたのですが、「早く呼ばれているのか」、「処理が早いのか」はわかりませんでした。

.task を使うと .onDisappear で制御をしなくていいので便利ですね。

画面遷移時の挙動

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