Sheet弹窗中TransactionCreateView重复调用问题排查求助
有两个配置几乎完全相同的SwiftUI项目,其中一个在展示sheet弹窗时,TransactionCreateView(sheet: self.sheet)会被反复调用(已通过断点确认)。推测存在触发视图重复重绘的因素,但另一个同配置项目无此问题。相关代码如下:
TransactionsView
struct TransactionsView: View { @Environment(\.modelContext) private var modelContext let sheet: Sheet @Query private var transactions: [Transaction] @State private var showCreate = false init(sheet: Sheet) { // init code to query for transactions matching Sheet ID } var body: some View { List { ForEach(transactions) { tx in // Standard row stuff } .onDelete(perform: deleteTxs) } .toolbar { ToolbarItem { Button(action: addTransaction) { Label("Add", systemImage: "plus") } } } .sheet(isPresented: $showCreate) { TransactionCreateView(sheet: self.sheet) .presentationDetents([.medium, .large]) } } private func addTransaction() { withAnimation { showCreate.toggle() } } private func deleteTxs(offsets: IndexSet) { withAnimation { for index in offsets { modelContext.delete(transactions[index]) } } } }
TransactionCreateView
struct TransactionCreateView: View { @Environment(\.dismiss) var dismiss @Environment(\.modelContext) private var modelContext @State private var transaction: Transaction @State private var notesText: String = "" let sheet: Sheet init(sheet: Sheet) { self.sheet = sheet self.transaction = Transaction(sheet: self.sheet) } var body: some View { Form { Picker("Type", selection: $transaction.type) { ForEach(TransactionType.allCases, id: \.self) { type in Text(type.rawValue.capitalized) .tag(type) } } .pickerStyle(.segmented) TextField("Amount", value: $transaction.value, format: .number) TextField("Notes", text: $notesText, prompt: Text("What was this for?")) Button("Add") { withAnimation { transaction.notes = notesText.isEmpty ? nil : notesText modelContext.insert(transaction) } dismiss() } } .onAppear { notesText = transaction.notes ?? "" } } }
Model文件
@Model final class Transaction { var timestamp: Date var value: Double var type: TransactionType var notes: String? @Relationship(inverse: \Sheet.transactions) var sheet: Sheet? } enum TransactionType: String, Codable, CaseIterable { case income case expense }
@Model final class Sheet { var timestamp: Date var title: String var transactions: [Transaction] }
Sheet对象引用不稳定
Sheet是@Model引用类型,如果父视图传递的Sheet实例发生了不必要的引用变更(比如父视图重绘时重新创建了Sheet对象,或Sheet的属性修改导致SwiftUI判定其为新对象),会触发TransactionsView重绘,进而导致sheet闭包内的TransactionCreateView反复初始化。@Query更新触发重绘连锁反应
TransactionsView中的@Query会监听Transaction数据变化,当在TransactionCreateView中插入新Transaction时,@Query会更新transactions数组,触发TransactionsView重绘。此时如果self.sheet被SwiftUI判定为依赖变化,就会重新创建TransactionCreateView。TransactionCreateView初始化逻辑问题
在init中直接创建Transaction并赋值给@State,这种方式会导致父视图重绘时,TransactionCreateView的@State属性被重新初始化,进而触发视图反复创建。父视图不必要重绘
若TransactionsView的父视图存在频繁变化的@State/@Binding属性,会导致整个视图树重绘,间接影响sheet内的视图初始化。
- 稳定Sheet对象引用:检查父视图传递Sheet的方式,用
@State/@StateObject持有Sheet实例,避免频繁创建新引用。 - 调整Transaction初始化时机:将Transaction的创建移到
onAppear中,避免在init中初始化@State属性:struct TransactionCreateView: View { // ... 其他属性 @State private var transaction: Transaction? init(sheet: Sheet) { self.sheet = sheet } var body: some View { Form { // ... 表单内容 } .onAppear { transaction = Transaction(sheet: sheet) notesText = transaction?.notes ?? "" } } } - 添加视图标识稳定化:在TransactionCreateView上添加
.id(showCreate),确保仅当弹窗状态变化时才重新创建视图:.sheet(isPresented: $showCreate) { TransactionCreateView(sheet: self.sheet) .presentationDetents([.medium, .large]) .id(showCreate) } - 排查父视图重绘原因:检查父视图中是否存在不必要的状态变化,减少无意义的视图树重绘。
内容的提问来源于stack exchange,提问作者Dovizu

