swift - Content inside .sheet() being infinitely called - Stack Overflow
I have another project with almost identical setup without this problem, so I'm scratching my head as to why in this project, whenever a sheet is presented, the content code is being repeatedly called
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])
}
}
}
}
The sheet view
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 files
@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]
}
I have narrowed down the issue to TransactionCreateView(sheet: self.sheet)
being called repeatedly (verified via breakpoint), but I'm not sure why. I'm guessing that somehow somewhere is causing something to be redrawn repeatedly. My other project also has .sheet() attached in this similar fashion yet doesn't experience this issue.
I have another project with almost identical setup without this problem, so I'm scratching my head as to why in this project, whenever a sheet is presented, the content code is being repeatedly called
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])
}
}
}
}
The sheet view
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 files
@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]
}
I have narrowed down the issue to TransactionCreateView(sheet: self.sheet)
being called repeatedly (verified via breakpoint), but I'm not sure why. I'm guessing that somehow somewhere is causing something to be redrawn repeatedly. My other project also has .sheet() attached in this similar fashion yet doesn't experience this issue.
1 Answer
Reset to default 0The reason that the init get's called in an infinite loop is that in the init of the view you create a new Transaction
object and set the relationship property sheet
. Now since the passed Sheet
object is already persisted (inserted in the ModelContext) then SwiftData will automatically insert the new Transaction
object into the ModelContext instance (because otherwise the data would be inconsistent if only one end of the relationship existed in the context).
This insert of the new transaction will trigger the @Query
in the parent view and a redraw in which the child view gets called again and you have an infinite loop.
Furthermore it's worth noting that since the transaction is always inserted there is no way for the user to regret adding a transaction by pressing escape when the sheet is open.
The simple solution is to not assign the sheet when creating the transaction and instead doing that in the action for the "Add" button.
init(sheet: Sheet) {
self.sheet = sheet
transaction = Transaction(sheet: nil)
}
But maybe a better solution since you don't have so many properties is to instead create local @State
properties and create and insert the Transaction
in the "Add" button action
@State private var type: TransactionType = .income
@State private var value: Double = .zero
@State private var notesText: String = ""
let sheet: Sheet
var body: some View {
Form {
Picker("Type", selection: $type) {
ForEach(TransactionType.allCases, id: \.self) { type in
Text(type.rawValue.capitalized)
.tag(type)
}
}
.pickerStyle(.segmented)
TextField("Amount", value: $value, format: .number)
TextField("Notes", text: $notesText, prompt: Text("What was this for?"))
Button("Add") {
withAnimation {
let transaction = Transaction(timestamp: .now,
value: value,
type: type,
notes: notesText.isEmpty ? nil : notesText,
sheet: self.sheet)
modelContext.insert(transaction)
}
dismiss()
}
}
}
- 微软服软涉足iOS、安卓背后:以开发者为重
- Intel兵屯深圳,意欲何为?
- 2012年NEC云时代平台软件全国巡展启动
- [连载]巨头“心血之作”终失败(二):Google Chromebook
- html - HTML5 Canvas: How to draw on Canvas from Java or C++ server? - Stack Overflow
- visual studio 2017 - C++ build errors with wxWidgets 3.1.2 and Connect method - Stack Overflow
- soa - How to use a variable in a composite or wsdl, as I need to have this variable per environment? - Stack Overflow
- material ui - Using ShadowDOM to address MUI 5 vs MUI 6 Compatibility Issue? - Stack Overflow
- flutter - Uber category selection animation - Stack Overflow
- sql - Ranking query records in specific order - Stack Overflow
- amazon ses - Is it possible to send a RawMessage using Apache Camel AWS SES? - Stack Overflow
- postgresql - How to divide two NUMERIC values and get a result that has the maximum number of digits the NUMERIC type supports?
- Keeping Data In Denormalized NoSql Databases (CassandraScyllaDb) Tables In Sync? - Stack Overflow
- reactjs - NextAuth cookie not being sent when I use the development server, but being sent when I use HoppscotchBrowser to send
- android - OneSignal Not Subscribing Users - Stack Overflow
- docker - Pulling AWS Parameter Store secrets into a deployed Dockerized app on EC2 - Stack Overflow
- Angular mfe with @module-federationenhanced having issue to use ngx-translate with remote apps - Stack Overflow
sheet
should be ether@State
or@Binding
– Paulw11 Commented 13 hours agoTransactionCreateView
have you tried using@Bindable var transaction: Transaction
sinceclass Transaction
already conforms to theObservable
protocols. – workingdog support Ukraine Commented 12 hours ago