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会尝试接管该值的生命周期,导致视图反复初始化,最终触发无限循环。
修复步骤
- 修改
IncludedUnitScreenView的属性声明,将@State改为普通常量(外部传入的值无需在视图内部修改):
// 替换原有的@State声明 private let theIncludedUnit: IncludedUnit private let theIncludedUnitId: Int
- 保持
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") }
- 额外优化建议
- 避免在
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
相关产品推荐
相关产品推荐

