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

SwiftUI导航异常:IncludedUnitScreenView陷入无限创建循环求助

问题描述

应用导航流程为:ArmyListView -> ArmyListDetailsView -> IncludedUnitScreenView。ArmyListView创建NavigationStack后跳转至ArmyListDetailsView正常,但点击ArmyListDetailsView中的条目时,IncludedUnitScreenView无法打开,通过调试窗口的NSLog语句发现该视图陷入无限创建循环。

相关代码

ArmyListDetailsView代码

struct ArmyListDetailsView: View {
    @Environment(\.modelContext) private var modelContext
    @Query private var theIncludedUnits: [IncludedUnit]
    @Query private var theArmyList: [ArmyList]
    @State var shouldPresentSheet = false
    
    init(armyList: String = "") {
        let includedUnitsPredicate = #Predicate<IncludedUnit> { includedUnit in
            armyList.isEmpty || includedUnit.armyList.localizedStandardContains(armyList)
        }
        _theIncludedUnits = Query(filter: includedUnitsPredicate, sort: \IncludedUnit.armyList)
        
        let armyListPredicate = #Predicate<ArmyList> { armylist in
            armyList.isEmpty || armylist.listTitle.localizedStandardContains(armyList)
        }
        _theArmyList = Query(filter: armyListPredicate, sort: \ArmyList.listTitle)
    }
    
    var body: some View {
        let armyList = theArmyList.first.unsafelyUnwrapped
        List {
            Section(header: Text("List Details")) {
                ArmyListCoreDetails(theArmyList: armyList)
            }
            
            if !theIncludedUnits.isEmpty {
                let groupedIncludedUnits = Dictionary(grouping: theIncludedUnits, by: \.category)
                ForEach(Array(groupedIncludedUnits.keys), id: \.self) { category in
                    Section(header: Text(category)) {
                        let categoryIncludedUnits = getIncludedUnitsForCategory(theCategory: category, theIncludedUnits: groupedIncludedUnits)
                        
                        ForEach(categoryIncludedUnits, id: \.id) { theIncludedUnit in
                            NavigationLink {
                                IncludedUnitScreenView(theIncludedUnitId: theIncludedUnit.id,
                                                       theIncludedUnit: theIncludedUnit)
                            } label: {
                                IncludedUnitRow(includedUnit: theIncludedUnit)
                            }
                        }
                    }
                }
            } else {
                ContentUnavailableView {
                    Label("No units have been added to this list.\nPress + to choose some.", systemImage: "doc.on.doc")
                }
            }
        }
        .navigationTitle(armyList.listTitle)
    }
}

IncludedUnitScreenView代码

struct IncludedUnitScreenView: View {
    @Environment(\.modelContext) private var modelContext
    @Query private var theUnitCompositions: [UnitComposition]
    @Query private var theLoadouts: [Loadout]
    @State private var theIncludedUnit: IncludedUnit
    @State private var theIncludedUnitId: Int
    
    init(theIncludedUnitId: Int = 0, theIncludedUnit: IncludedUnit) {
        let unitCompositionPredicate = #Predicate<UnitComposition> { unitComposition in
            theIncludedUnitId == 0 || unitComposition.includedUnitId == theIncludedUnitId
        }
        _theUnitCompositions = Query(filter: unitCompositionPredicate)
        
        let loadoutPredicate = #Predicate<Loadout> { loadout in
            theIncludedUnitId == 0 || loadout.includedUnitId == theIncludedUnitId
        }
        _theLoadouts = Query(filter: loadoutPredicate)
        
        self.theIncludedUnit = theIncludedUnit
        self.theIncludedUnitId = theIncludedUnitId
        
        NSLog("IncludedUnitScreenView: Completed the init")
    }
    
    var body: some View {
        
        List {
            Section(header: Text("Unit Details")) {
                IncludedUnitCoreDetails(theIncludedUnit: theIncludedUnit)
            }
            
            Section(header: Text("Models")) {
                ForEach(theUnitCompositions, id: \.model) { theUnitComposition in
                    NavigationLink {
                        LoadoutListView(theLoadoutList: theLoadouts.filter {$0.model == theUnitComposition.model})
                    } label: {
                        IncludedUnitDetailsRow(
                            theIncludedUnit: theIncludedUnit,
                            theUnitComposition: theUnitComposition,
                            theLoadouts: theLoadouts.filter {$0.model == theUnitComposition.model}
                        )
                    }
                }
            }
        }
    }
}
问题原因及解决方案

核心问题

IncludedUnitScreenView错误地将外部传入的参数声明为@State属性。@State是SwiftUI用于管理视图内部私有状态的包装器,用它存储外部传入的值时,SwiftUI会尝试接管该值的生命周期,导致视图反复初始化,最终触发无限循环。

修复步骤

  1. 修改IncludedUnitScreenView的属性声明,将@State改为普通常量(外部传入的值无需在视图内部修改):
// 替换原有的@State声明
private let theIncludedUnit: IncludedUnit
private let theIncludedUnitId: Int
  1. 保持init方法中的赋值逻辑不变,直接初始化常量属性即可:
init(theIncludedUnitId: Int = 0, theIncludedUnit: IncludedUnit) {
    let unitCompositionPredicate = #Predicate<UnitComposition> { unitComposition in
        theIncludedUnitId == 0 || unitComposition.includedUnitId == theIncludedUnitId
    }
    _theUnitCompositions = Query(filter: unitCompositionPredicate)
    
    let loadoutPredicate = #Predicate<Loadout> { loadout in
        theIncludedUnitId == 0 || loadout.includedUnitId == theIncludedUnitId
    }
    _theLoadouts = Query(filter: loadoutPredicate)
    
    self.theIncludedUnit = theIncludedUnit
    self.theIncludedUnitId = theIncludedUnitId
    
    NSLog("IncludedUnitScreenView: Completed the init")
}
  1. 额外优化建议
    • 避免在body中重复执行theLoadouts.filter操作,可提取为计算属性减少重复计算:
      private var groupedLoadouts: [String: [Loadout]] {
          Dictionary(grouping: theLoadouts, by: \.model)
      }
      
      后续在body中用groupedLoadouts[theUnitComposition.model] ?? []替代过滤操作。
    • theArmyList.first.unsafelyUnwrapped存在崩溃风险,若theArmyList为空会直接崩溃,建议添加空值处理逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 12:13:11