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
相关产品推荐
相关产品推荐

