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

SwiftUI中如何在视图从自定义导航栈弹出时立即执行函数?

全屏滑动返回时,视图弹出后立即执行指定函数

问题背景

现有自定义导航栈实现了全屏左右滑动返回功能,需要在视图完全弹出导航栈后立即执行特定函数,要求不能在滑动中途或用户松手前触发。已知onDisappear方法存在延迟,无法满足实时触发的需求。

解决方案

核心思路是监听自定义滑动手势的结束状态,结合导航栈的变化判断是否完成pop操作,再触发指定函数。以下是修改后的完整代码:

完整修改后代码

import SwiftUI

struct ContentView: View {
    @State private var isEnabled: Bool = true
    var body: some View {
        FullSwipeNavigationStack {
             NavigationLink("全屏滑动返回视图") {
                 Text("Hello")
                     .enableFullSwipePop(isEnabled) {
                         // 这里放置视图弹出后需要执行的逻辑
                         print("视图已完全弹出,执行指定函数")
                     }
             }
        }
    }
}

struct FullSwipeNavigationStack<Content: View>: View {
    @ViewBuilder var content: Content
    @State private var customGesture: UIPanGestureRecognizer = {
        let gesture = UIPanGestureRecognizer()
        gesture.name = UUID().uuidString
        gesture.isEnabled = false
        return gesture
    }()
    
    var body: some View {
        NavigationStack {
            content
                .background {
                    AttachGestureView(gesture: $customGesture)
                }
        }
        .environment(\.popGestureID, customGesture.name)
        .onReceive(NotificationCenter.default.publisher(for: .init(customGesture.name ?? "")), perform: { info in
            if let userInfo = info.userInfo, let status = userInfo["status"] as? Bool {
                customGesture.isEnabled = status
            }
        })
    }
}

extension View {
    @ViewBuilder
    func enableFullSwipePop(_ isEnabled: Bool, onPopCompleted: (() -> Void)? = nil) -> some View {
        self
            .modifier(FullSwipeModifier(isEnabled: isEnabled, onPopCompleted: onPopCompleted))
    }
}

fileprivate struct PopNotificationID: EnvironmentKey {
    static var defaultValue: String?
}

fileprivate extension EnvironmentValues {
    var popGestureID: String? {
        get { self[PopNotificationID.self] }
        set { self[PopNotificationID.self] = newValue }
    }
}

fileprivate struct FullSwipeModifier: ViewModifier {
    var isEnabled: Bool
    let onPopCompleted: (() -> Void)?
    @Environment(\.popGestureID) private var gestureID
    @State private var isCurrentTopView = true
    
    func body(content: Content) -> some View {
        content
            .onChange(of: isEnabled, initial: true) { _, newValue in
                guard let gestureID = gestureID else { return }
                NotificationCenter.default.post(name: .init(gestureID), object: nil, userInfo: ["status": newValue])
            }
            .onDisappear {
                guard let gestureID = gestureID else { return }
                NotificationCenter.default.post(name: .init(gestureID), object: nil, userInfo: ["status": false])
                isCurrentTopView = false
            }
            .onAppear {
                isCurrentTopView = true
            }
            .onReceive(NotificationCenter.default.publisher(for: .init("FullSwipePopCompleted"))) { _ in
                if !isCurrentTopView {
                    onPopCompleted?()
                }
            }
    }
}

fileprivate struct AttachGestureView: UIViewRepresentable {
    @Binding var gesture: UIPanGestureRecognizer
    
    func makeUIView(context: Context) -> UIView { UIView() }
    
    func updateUIView(_ uiView: UIView, context: Context) {
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.02) {
            guard let parentVC = uiView.parentViewController,
                  let navVC = parentVC.navigationController else { return }
            
            if navVC.view.gestureRecognizers?.first(where: { $0.name == gesture.name }) == nil {
                navVC.addFullSwipeGesture(gesture)
                print("手势已添加")
            }
        }
    }
}

fileprivate extension UINavigationController {
    func addFullSwipeGesture(_ gesture: UIPanGestureRecognizer) {
        guard let gestureSelector = interactivePopGestureRecognizer?.value(forKey: "targets") else { return }
        
        gesture.setValue(gestureSelector, forKey: "targets")
        gesture.addTarget(self, action: #selector(handleFullSwipeGesture(_:)))
        view.addGestureRecognizer(gesture)
    }
    
    @objc private func handleFullSwipeGesture(_ gesture: UIPanGestureRecognizer) {
        // 仅在手势结束/取消时检查是否完成pop
        guard gesture.state == .ended || gesture.state == .cancelled else { return }
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
            // 发送pop完成通知
            NotificationCenter.default.post(name: .init("FullSwipePopCompleted"), object: nil)
        }
    }
}

fileprivate extension UIView {
    var parentViewController: UIViewController? {
        sequence(first: self) { $0.next }
            .first(where: { $0 is UIViewController }) as? UIViewController
    }
}

关键修改说明

  • 给自定义滑动手势添加状态监听handleFullSwipeGesture,仅在手势结束或取消时发送FullSwipePopCompleted通知,避免中途触发
  • 扩展enableFullSwipePop方法,新增onPopCompleted回调参数,用于传入需要执行的函数
  • 在FullSwipeModifier中通过isCurrentTopView标记当前视图是否处于栈顶,结合通知判断视图是否已被完全弹出,确保回调只在正确时机触发
  • 延迟0.05秒发送通知是为了确保导航栈完成更新,避免判断逻辑出错

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 16:00:57