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

SwiftUI导航链接无限循环导致APP冻结问题排查

问题分析与解决方案

核心问题定位

两种路径下的行为差异,根源在于登录后切换根视图导致导航栈生命周期异常,再加上视图构建中的几个不良实践(动态生成ID、AnyView滥用、ViewModel重复创建)共同触发了无限循环。


解决方案1:移除List的动态ID

ProjectsView中给List添加了.id(UUID()),这会导致每次视图刷新时List的身份完全重置,SwiftUI会强制重建整个列表。登录后跳转时,这种重建会和新导航栈的初始化产生冲突,引发后续视图的更新异常。

修改ProjectsView代码:

var body: some View {
    ZStack {
        VStack {
            List {
                ForEach(Array(projectsViewModel.projectRowViewModels.enumerated()),
                        id: \.0) { index, projectViewModel in
                    Section {
                        NavigationLink(destination: CurrentProjectView(currentProjectViewModel: projectsViewModel.selectedProjectViewModel(at: index))) {
                            ProjectRow(projectRowViewModel: projectViewModel)
                        }
                    }
                }
            }
            // 移除这行:.id(UUID())
        }
    }
}

解决方案2:缓存SurveyViewModel避免重复创建

getSurveyViewModel每次调用都生成新的ViewModel实例,会导致NavigationLink的目标视图不断触发刷新,进而引发父视图的循环重建。需要缓存ViewModel实例,保证同一索引返回同一个对象。

修改CurrentProjectViewModel:

class CurrentProjectViewModel: ObservableObject {
    // 新增缓存变量
    private var cachedSurveyViewModels: [SurveyViewModel]?
    
    func getSurveyViewModel(selectedSurveyIndex: Int) -> SurveyViewModel {
        if cachedSurveyViewModels == nil {
            cachedSurveyViewModels = surveys.enumerated().map { index, survey in
                SurveyViewModel(survey: survey, index: index)
            }
        }
        return cachedSurveyViewModels![selectedSurveyIndex]
    }
}

解决方案3:移除AnyView包装

getNavigationLink中使用AnyView会擦除视图类型信息,破坏SwiftUI的视图身份跟踪机制,尤其是在导航场景下容易引发更新异常。直接返回NavigationLink即可:

@ViewBuilder
private func getSurveySection() -> some View {
    ForEach(currentProjectViewModel.surveys.enumeratedArray(),
            id: \.element) { index, field in
        if index == 0 {
            // 保留你的按钮代码
        }
        // 直接返回NavigationLink,不需要AnyView包装
        NavigationLink(field) {
            SurveyView(surveyViewModel: currentProjectViewModel.getSurveyViewModel(selectedSurveyIndex: index))
        }
    }
}

// 可删除原getNavigationLink函数,直接在getSurveySection中构建链接

解决方案4:统一导航栈生命周期

当前根据isSignedIn切换整个根视图(LoginView和NavigationStack二选一),登录后会销毁LoginView并重建NavigationStack,这种切换会导致导航栈的状态不一致。改为将NavigationStack作为根容器,内部切换内容:

修改main代码:

@StateObject var authStatusManager = Factory.shared.authStatusManager

var body: some Scene {
    WindowGroup {
        NavigationStack {
            if authStatusManager.isSignedIn {
                ProjectsView(projectsViewModel: projectViewModel)
            } else {
                LoginView(loginViewModel: loginViewModel)
            }
        }
    }
}

这样无论登录前后,都使用同一个NavigationStack,避免了根视图切换带来的生命周期问题。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 12:03:28