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

如何测量NavigationStack及其子栈的停留时长?

解决方案

要统计NavigationStack及其所有子栈的总停留时长,核心是基于整个导航栈的活跃周期来控制计时器的启停,而非单个子视图的appear/disappear。以下是具体实现方案:

1. 核心思路

在NavigationStack的根视图(或你需要统计的分支根视图)的onAppear中启动计时器,onDisappear中停止计时器。因为在SwiftUI的NavigationStack中,push子视图时父视图不会触发onDisappear(父视图仍存在于栈中,仅被覆盖),只有当整个导航栈分支被完全移除(比如pop回上一级页面、切换Tab、关闭Sheet等)时,根视图才会触发onDisappear,这样就能统计到该栈及其所有子视图的总停留时长。

2. 实现代码

第一步:创建计时器管理类

用ObservableObject封装计时器逻辑,方便在视图间共享和状态监听:

import SwiftUI

class NavigationStackTimer: ObservableObject {
    // 实时显示的累计时长
    @Published var elapsedSeconds: Int = 0
    private var timer: Timer?
    private var startDate: Date?
    
    // 启动计时器
    func startTracking() {
        guard timer == nil else { return }
        startDate = Date()
        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in
            guard let self = self, let start = self.startDate else { return }
            self.elapsedSeconds = Int(Date().timeIntervalSince(start))
        }
    }
    
    // 停止计时器并重置临时状态
    func stopTracking() {
        timer?.invalidate()
        timer = nil
        startDate = nil
    }
    
    // 重置累计时长
    func reset() {
        elapsedSeconds = 0
        stopTracking()
    }
}

第二步:在导航栈根视图中使用

将计时器绑定到导航栈的根视图,控制启停:

struct MainNavStackView: View {
    // 初始化计时器实例
    @StateObject private var stackTimer = NavigationStackTimer()
    
    var body: some View {
        NavigationStack {
            VStack(spacing: 20) {
                // 显示实时累计时长
                Text("当前导航栈总停留时长: \(stackTimer.elapsedSeconds)秒")
                    .font(.title)
                
                // 跳转到子视图
                NavigationLink("进入子视图1", destination: ChildView1())
                NavigationLink("进入子视图2", destination: ChildView2())
            }
            .padding()
            // 进入导航栈时启动计时
            .onAppear {
                stackTimer.startTracking()
            }
            // 离开导航栈时停止计时
            .onDisappear {
                stackTimer.stopTracking()
            }
        }
    }
}

// 子视图示例(无需处理计时器)
struct ChildView1: View {
    var body: some View {
        VStack {
            Text("子视图1")
            NavigationLink("进入孙视图", destination: GrandChildView())
        }
    }
}

struct ChildView2: View {
    var body: some View {
        Text("子视图2")
    }
}

struct GrandChildView: View {
    var body: some View {
        Text("孙视图")
    }
}

3. 扩展场景:多分支独立统计

如果需要分别统计不同导航分支的停留时长,可以通过环境对象传递计时器,在分支根视图中控制启停:

struct ContentView: View {
    @StateObject private var timerA = NavigationStackTimer()
    @StateObject private var timerB = NavigationStackTimer()
    
    var body: some View {
        NavigationStack {
            VStack(spacing: 20) {
                Text("分支A总时长: \(timerA.elapsedSeconds)秒")
                Text("分支B总时长: \(timerB.elapsedSeconds)秒")
                
                NavigationLink("进入分支A", destination: BranchAView())
                    .environmentObject(timerA)
                
                NavigationLink("进入分支B", destination: BranchBView())
                    .environmentObject(timerB)
            }
        }
    }
}

struct BranchAView: View {
    @EnvironmentObject private var timer: NavigationStackTimer
    
    var body: some View {
        VStack {
            Text("分支A")
            NavigationLink("A的子视图", destination: BranchAChild())
        }
        .onAppear { timer.startTracking() }
        .onDisappear { timer.stopTracking() }
    }
}

// BranchBView 实现类似

4. 注意事项

  • 若需要累计多次进入同一导航栈的时长,可修改计时器类,增加totalElapsedSeconds变量,在stopTracking时将当前会话时长累加到总时长中。
  • 确保计时器的weak self引用,避免内存泄漏。
  • 如果导航栈是通过sheet或fullScreenCover展示的,根视图的onDisappear会在弹窗关闭时触发,同样适用该方案。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 00:34:53