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

如何在SwiftUI View被完全移除时可靠执行指定代码

核心问题说明

你遇到的@StateObject deinit不触发、.onDisappear误触发的问题,本质是SwiftUI的View是结构体值类型,生命周期和实际承载的UIViewController生命周期并不完全对齐:

  • @StateObject的持有方是SwiftUI内部的渲染托管对象,混编UIHostingController场景下,只要宿主VC没有被真正释放,StateObject就会被持续持有,导航过程中系统对VC的暂存逻辑很容易导致deinit延迟甚至不触发。
  • 原生.onDisappear只会在视图离开屏幕时触发,不管是被新页面覆盖(push/present新页)还是真的被移除,无法区分两种场景。
推荐实现方案

根据你UIKit混编的场景,优先选第一种方案,稳定性最高。

方案一:基于UIHostingController生命周期监听(混编场景首选)

因为你的SwiftUI页面最终是通过UIHostingController承载接入UIKit导航栈的,直接在宿主VC层面监听销毁时机,完全绕开SwiftUI生命周期的不确定性,所有场景(点击返回按钮、滑动dismiss、代码触发pop/dismiss)都能100%准确触发。
实现步骤:

  1. 自定义通用的UIHostingController基类,增加销毁回调:
class CallbackHostingController<Content: View>: UIHostingController<Content> {
    var onPermanentlyRemove: (() -> Void)?
    
    deinit {
        onPermanentlyRemove?()
    }
}
  1. 接入SwiftUI页面时,把页面销毁要执行的逻辑(取消订阅、资源释放等)传入回调即可:
// 初始化你的SwiftUI页面
let targetPage = YourSwiftUIPage()
// 用自定义宿主VC包装
let hostVC = CallbackHostingController(rootView: targetPage)
// 配置销毁回调
hostVC.onPermanentlyRemove = {
    // 这里的逻辑只会在页面真正被销毁时执行一次
    yourCancellable.cancel()
    clearPageCache()
}
// 推入导航栈
navigationController?.pushViewController(hostVC, animated: true)

这个方案和UIKit原生页面的deinit逻辑完全一致,不会受SwiftUI重绘、视图缓存的影响,是混编场景下最可靠的实现。

方案二:纯SwiftUI导航场景的修饰符方案

如果部分页面是纯SwiftUI NavigationStack 管理、不直接接触UIKit的,可以用下面的自定义修饰符实现,不会在push新页面覆盖时误触发:

extension View {
    func onPermanentlyRemoved(perform action: @escaping () -> Void) -> some View {
        modifier(PermanentRemoveModifier(action: action))
    }
}

private struct PermanentRemoveModifier: ViewModifier {
    let action: () -> Void
    @State private var isViewActive = true
    
    func body(content: Content) -> some View {
        content
            .onAppear {
                isViewActive = true
            }
            .onDisappear {
                isViewActive = false
                // 等主队列下一帧判断,如果页面没有重新显示,说明是真的被移除
                DispatchQueue.main.async {
                    guard !isViewActive else { return }
                    action()
                }
            }
    }
}

实现逻辑很简单:如果是push新页面覆盖当前页,用户后续返回时当前页会重新触发onAppear把标记位改回true,回调不会执行;如果是页面真的被pop出栈移除,不会再触发onAppear,回调就会准确执行。

避坑提示
  • 不要在SwiftUI的ObservableObject deinit里放核心资源释放逻辑,SwiftUI的渲染缓存可能导致对象持有时间远超页面实际展示时间,触发时机不可控。
  • 不要通过监听返回按钮点击、手势识别的方式判断页面移除,会漏掉代码触发pop、手势dismiss中断等边缘场景。
  • 模态弹出的Sheet/FullScreenCover场景,第一种宿主VC deinit的方案同样适用,dismiss完成后会自动触发回调。

内容的提问来源于stack exchange,提问作者J. Doe

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 20:12:20