You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中如何触发ForEach循环内View的.onDisappear事件?

问题描述

发现DetailView仅被隐藏而非从界面移除,导致同一视图的.onDisappear事件不会触发,只有在切换不同item的视图时才会触发。需要在不将DetailView移出ForEach循环的前提下实现正确的消失逻辑。

原代码

struct TestItem: Identifiable, Hashable {
    let id = UUID()
    let name: String
}

let items: [TestItem] = [TestItem(name: "Name 1"), TestItem(name: "Name 2"), TestItem(name: "Name 3")]

struct ContentView: View {
    @State var selectedItem: TestItem? = nil
    @State var detailViewPresented: Bool = false
    
    var body: some View {
        List {
            ForEach(items) { item in
                VStack(alignment: .leading) {
                    Text(item.name)
                }
                .onTapGesture {
                    if selectedItem == item && detailViewPresented {
                        detailViewPresented = false
                    } else {
                        detailViewPresented = true
                    }

                    selectedItem = item
                }

                if selectedItem == item && detailViewPresented {
                    DetailView(item: item)
                }
            }
        }
    }
}

struct DetailView: View {
    let item: TestItem

    var body: some View {
        VStack {
            Text("Detail View")
            Text(item.name)
        }
        .onDisappear {
            print("DetailView Disappeared")
        }
    }
}

解决方案

方案1:重构状态逻辑,用单个ID控制展开状态

原代码的核心问题是用两个状态变量控制显示,可能导致SwiftUI视图层级优化未正确销毁DetailView。改用单个状态记录当前展开的item ID,能确保收起视图时,DetailView被明确从层级中移除,触发.onDisappear。

修改后代码:

struct TestItem: Identifiable, Hashable {
    let id = UUID()
    let name: String
}

let items: [TestItem] = [TestItem(name: "Name 1"), TestItem(name: "Name 2"), TestItem(name: "Name 3")]

struct ContentView: View {
    // 用单个状态记录当前展开的item ID
    @State private var expandedItemId: UUID? = nil
    
    var body: some View {
        List {
            ForEach(items) { item in
                VStack(alignment: .leading) {
                    Text(item.name)
                }
                .onTapGesture {
                    // 切换当前item的展开状态:已展开则收起,否则展开(自动关闭其他item)
                    expandedItemId = expandedItemId == item.id ? nil : item.id
                }

                // 仅当当前item匹配展开状态时显示DetailView
                if expandedItemId == item.id {
                    DetailView(item: item)
                }
            }
        }
    }
}

struct DetailView: View {
    let item: TestItem

    var body: some View {
        VStack {
            Text("Detail View")
            Text(item.name)
        }
        .onDisappear {
            print("DetailView Disappeared for \(item.name)")
        }
    }
}

方案2:强制视图刷新销毁(兼容原状态逻辑)

如果不想改动原有状态结构,可以给DetailView添加依赖于显示状态的ID,强制SwiftUI在显示状态变化时销毁并重建视图,从而触发.onDisappear。

修改原ContentView的两处逻辑:

  1. 优化点击手势逻辑:
.onTapGesture {
    if selectedItem == item {
        detailViewPresented.toggle()
    } else {
        selectedItem = item
        detailViewPresented = true
    }
}
  1. 给DetailView添加动态ID:
if selectedItem == item && detailViewPresented {
    DetailView(item: item)
        .id("\(item.id)-\(detailViewPresented)")
}

原理说明

SwiftUI的.onDisappear仅在视图从层级中被移除时触发,而非单纯隐藏。原代码中双状态控制可能因selectedItem未改变,被SwiftUI优化为视图隐藏而非销毁;通过单一ID控制或强制视图ID变化,能确保收起操作触发视图销毁,进而触发消失事件。

内容的提问来源于stack exchange,提问作者netsplatter

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 09:37:49