.onDisappearは、インターフェース階層からViewが削除されたときにクロージャを実行するSwiftUIの修飾子です。呼び出しは、画面を閉じるとき、タブを切り替えるとき、モーダルウィンドウを閉じるとき、または要素が表示範囲外にスクロールされたときに発生します。Apple Developer Documentation(2026)によると、onDisappearはクラッシュシナリオやシステムのウォッチドッグによるアプリ終了時に呼び出しを保証しません。SwiftUIの詳細はSwiftUIの記事をご覧ください。
重要なポイント
.onDisappearは、Voidクロージャを受け取り、Viewが階層から削除されたときに実行するSwiftUIのView修飾子です。.onAppearとともに、画面の完全なライフサイクル(表示→作業→非表示)を形成します。AppleはonDisappearをSwiftUIとともにiOS 13でUIKitのviewDidDisappearの類似機能として導入しました。
構文的に.onDisappearはonAppearと同じです。任意のViewを修飾し、SwiftUIレンダラーがViewの削除時に呼び出すクロージャをアタッチします。遷移アニメーションの完了後にのみviewDidDisappearが作動するUIKitとは異なり、SwiftUIのonDisappearはアニメーションが終了する前でも呼び出される可能性があります。
基本構文はonAppearと同じく簡潔です。修飾子は追加パラメータを取らず、メインスレッドで同期的に実行されるクロージャのみを受け取ります。
struct DetailView: View {
var body: some View {
Text("Detail Screen")
.onDisappear {
print("DetailViewが画面から消えました")
}
}
}
呼び出しメカニズムはonAppearの逆です。子要素が最初にonDisappearを受け取り、次に親が受け取ります。このchild-firstルールにより、親のリソースが解放される前に子のリソースが解放されます。子要素が親のデータに依存する場合、親のコンテキストなしで正しく終了できる必要があります。
SwiftUIは、Viewがレンダリンググラフから削除されるとonDisappearを呼び出します。トリガーには、NavigationStackからのポップ、TabViewの切り替え、モーダル(sheet/fullScreenCover)の解除、条件付きでレンダリングされるView(if/switch)が含まれます。ListやScrollViewでは、セルがプリフェッチバッファの外にスクロールされたときにonDisappearが呼び出されます。
Child-first順序とは、VStackに3つの子Viewがある場合、最初に各子に対して逆順でonDisappearが呼び出され、次に親に対して呼び出されることを意味します。これは適切なクリーンアップのために重要です。親のViewModelが共有リソースを解放する前に、子のタイマーがキャンセルされます。
struct ParentView: View {
var body: some View {
VStack {
ChildView(id: "A")
ChildView(id: "B")
}
.onDisappear {
print("Parent onDisappear — 最後")
}
}
}
struct ChildView: View {
let id: String
var body: some View {
Text("Child \(id)")
.onDisappear {
print("Child \(id) onDisappear")
}
}
}
コンソール出力:Child B onDisappear、Child A onDisappear、Parent onDisappear — 最後。onAppearと比較して逆順になることで、正しいクリーンアップチェーンが保証されます。
呼び出しシナリオはコンテナの種類によって異なります。NavigationStackでは、pop-to-root、通常のpop-back、またはスワイプ(interactivePopGestureRecognizer)による画面解除時にonDisappearが作動します。TabViewでは、タブを切り替えると非表示のタブにonDisappearが、表示されるタブにonAppearが呼び出されます。両方の修飾子はほぼ同時に作動します。
SheetとfullScreenCoverでは、プログラムによる解除(@Environment(\.dismiss)経由)または下スワイプジェスチャーによってonDisappearが呼び出されます。重要な詳細:sheetが開かれていてもユーザーがアプリを切り替えた場合、実際に閉じられるまでonDisappearは呼び出されません。
| シナリオ | onDisappearの呼び出し | 備考 |
|---|---|---|
| NavigationStackのPop | はい | アニメーション直後 |
| TabViewのタブ切替 | はい | 現在のタブ |
| Sheetの解除 | はい | アニメーション完了前 |
| Listのスクロール | はい | セルがプリフェッチ領域外に |
| アプリの最小化 | いいえ | 呼び出し保証なし |
| クラッシュ/watchdog kill | いいえ | 呼び出されない |
主なユースケースはリソースのクリーンアップ、状態の保存、トラッキングです。onAppearとは異なり、onDisappearのタスクは終了時に実行され、Viewは一度だけ非表示になるため重複チェックは不要です。
状態の保存は、画面を離れるときにデータを保存する必要があるフォームで特に便利です。タイマーとCombineサブスクリプションは、画面に戻ったときのリークを防ぐためにonDisappearでキャンセルされます。
struct FormView: View {
@State private var draftText = ""
@State private var timer: Timer?
var body: some View {
TextField("Enter text", text: $draftText)
.onAppear {
timer = Timer.scheduledTimer(withTimeInterval: 60, repeats: true) { _ in
saveDraft()
}
}
.onDisappear {
timer?.invalidate()
timer = nil
saveDraft()
}
}
private func saveDraft() {
UserDefaults.standard.set(draftText, forKey: "draft")
}
}
タイマーのキャンセルにより、画面が閉じられた後のコード実行を防ぎます。キャンセルしないと、タイマーが現在のViewに属さなくなった@Stateを更新しようとし、実行時警告が発生する可能性があります。
画面滞在時間は古典的なトラッキングシナリオです。onAppearで時間を記録し、onDisappearで差分を計算してセッション時間を含む分析イベントを送信します。
struct TrackedView: View {
@State private var appearTime: Date?
var body: some View {
Text("Tracked Screen")
.onAppear {
appearTime = Date()
Analytics.shared.logEvent("screen_view", params: ["screen": "TrackedView"])
}
.onDisappear {
if let start = appearTime {
let duration = Date().timeIntervalSince(start)
Analytics.shared.logEvent("screen_close", params: [
"screen": "TrackedView",
"duration_ms": Int(duration * 1000)
])
}
}
}
}
主な違いは呼び出し順序と実行保証です。onAppearはViewが階層に追加されるときに呼び出され、再作成時に再度呼び出される可能性があります。onDisappearは削除時に呼び出され、通常のクローズ時にのみ作動が保証され、クラッシュシナリオでは保証されません。
WWDC 2024によると、AppleはonDisappearをクリーンアップポイントとして扱い、データコミットポイントとしては扱わないことを推奨しています。重要なデータ(支払い、登録)はViewが非表示になる瞬間ではなくリアルタイムで保存する必要があります。アプリが最小化された場合、onDisappearは保証されないからです。
| 特性 | .onAppear | .onDisappear |
|---|---|---|
| 呼び出しタイミング | Viewが階層に追加 | Viewが階層から削除 |
| 順序 | Parent-first | Child-first |
| 保証 | 高い | クラッシュ時はなし |
| 主なタスク | 初期化 | クリーンアップ |
| 再呼び出し | View再作成時 | 非表示ごとに1回 |
推奨:onDisappearは重要でないクリーンアップとトラッキングにのみ使用してください。データの永続化にはAppDelegateのscenePhaseまたはapplicationWillTerminate通知を使用してください。
シナリオ1:YouTubeのような再生。動画詳細画面で、onDisappearが再生位置をUserDefaultsに保存します。再開時にonAppearがUserDefaultsから位置を復元し、継続的な視聴体験を提供します。
シナリオ2:Combineサブスクリプションのキャンセル。ViewModelがCombineパブリッシャーを使用する場合、onDisappearがcancellable?.cancel()でサブスクリプションをキャンセルします。これにより、画面を離れた後のUI更新を防ぎます。これはページネーションを含むリストや検索クエリで特に重要です。
シナリオ3:WebSocketの切断。リアルタイム接続を使用するアプリ(メッセンジャー、相場フィード)では、onDisappearがバッテリーとデータを節約するためにWebSocket接続を閉じます。画面に戻るときにonAppearで再接続が行われます。
WebSocketは、画面を離れるときに閉じる必要がある典型的なリソースです。以下の例では、onDisappearがソケットを切断し、onAppearが再接続します。これにより、複数の画面を持つアプリで大幅なデータ節約が実現します。
struct ChatView: View {
@StateObject private var socket = WebSocketManager()
var body: some View {
ChatListView(messages: socket.messages)
.onAppear {
socket.connect()
}
.onDisappear {
socket.disconnect()
}
}
}
重要:TabViewのタブを切り替えるとき、現在のタブのonDisappearと次のタブのonAppearはほぼ同時に作動します。WebSocketの場合、これにより切断-再接続のサイクルが発生し、不要な負荷が生じる可能性があります。解決策は遅延を使用するか、次の画面でソケットが必要かどうかを確認することです。
よくある質問
はい、.onDisappearはアプリのクラッシュ(crash、watchdog kill)、画面を閉じずにアプリを最小化する場合、またはアプリがバックグラウンドで終了されるシステムシナリオでは呼び出しを保証しません。重要なデータにはscenePhaseまたはapplicationWillTerminateを使用してください。
.onDisappearはViewが階層から削除されたときに作動する、特定の画面に対するローカルコールバックです。.scenePhase(@Environment(\.scenePhase)経由)はアプリ全体の状態(active、inactive、background)が変化したときに作動します。画面時間のトラッキングにはonAppear+onDisappearを、グローバルな状態の永続化にはscenePhaseを使用してください。
原因はタブの高速切り替えや同じ画面の繰り返しpush/popです。SwiftUIが新しいViewインスタンスを作成し、古いものを削除し、再度作成することがあり、そのたびにonDisappearとonAppearが呼び出されます。.id()、.equatable()を使用していないか、親のbodyでViewを再作成していないか確認してください。
はい、.onDisappearはmacOS(10.15+)で完全にサポートされており、同じ動作をします。ウィンドウを閉じる、スプリットビューパネルを削除する、モーダルを解除するときに呼び出されます。macOSでは、onDisappearはウィンドウを閉じるだけでなく非表示にするときにも呼び出されるため、macOSアプリではこれを考慮することが重要です。
@StateにURLSessionTaskへの参照を保存し、onDisappearでtask.cancel()を呼び出します。代替として、Viewが非表示になると自動的に非同期操作をキャンセルする.task修飾子を使用します。.taskはURLSessionを含むすべての非同期操作に推奨されます。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。