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

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]
}
问题根源分析
  1. Sheet对象引用不稳定
    Sheet是@Model引用类型,如果父视图传递的Sheet实例发生了不必要的引用变更(比如父视图重绘时重新创建了Sheet对象,或Sheet的属性修改导致SwiftUI判定其为新对象),会触发TransactionsView重绘,进而导致sheet闭包内的TransactionCreateView反复初始化。

  2. @Query更新触发重绘连锁反应
    TransactionsView中的@Query会监听Transaction数据变化,当在TransactionCreateView中插入新Transaction时,@Query会更新transactions数组,触发TransactionsView重绘。此时如果self.sheet被SwiftUI判定为依赖变化,就会重新创建TransactionCreateView。

  3. TransactionCreateView初始化逻辑问题
    在init中直接创建Transaction并赋值给@State,这种方式会导致父视图重绘时,TransactionCreateView的@State属性被重新初始化,进而触发视图反复创建。

  4. 父视图不必要重绘
    若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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 03:15:01