swiftui - Wrong data displayed when quickly switching between sheets - Stack Overflow

I made the following minimal code reproducing my problem.If you add some entries, open one entry, dism

I made the following minimal code reproducing my problem. If you add some entries, open one entry, dismiss the sheet by swiping down and then quickly open another entry, the first entry is displayed even though you opened another one. If you take a little more time before opening another entry, it works fine. Do you have any idea where this might be coming from? I guess I'm making mistakes in the use of SwiftData but I can't figure out where.

I know there is a similar question, this one: Wrong View Displayed When Rapidly Switching Between Sheets but it doesn't solve my problem, it makes no difference.

import SwiftUI
import SwiftData

struct ContentView: View {
    @Query private var entries: [Entry]
    @Environment(\.modelContext) private var context
    @State private var selectedEntry: Entry?
    @State private var isAdding = false
    
    var body: some View {
        NavigationView {
            VStack {
                List {
                    ForEach(entries) { entry in
                        Text(entry.content)
                            .onTapGesture {
                                selectedEntry = entry
                            }
                    }
                }
            }
            .navigationTitle("Entries")
            .toolbar {
                ToolbarItem {
                    Button("Add entry", systemImage: "plus") {
                        isAdding = true
                    }
                }
            }
            .sheet(isPresented: $isAdding) {
                NavigationStack {
                    EntryDetail(entry: nil, in: context.container)
                }
            }
            .sheet(item: $selectedEntry) { entry in
                NavigationStack {
                    EntryDetail(entry: entry, in: context.container)
                }
            }
        }
    }
}

struct EntryDetail: View {
    private let entry: Entry?
    
    @State private var localEntry: Entry
    private let localContext: ModelContext
    
    @Environment(\.dismiss) private var dismiss
    
    var body: some View {
        VStack {
            Form {
                TextField("Type your entry", text: $localEntry.content, axis: .vertical)
            }
        }
        .navigationTitle("Entry")
        .toolbar {
            ToolbarItem(placement: .confirmationAction) {
                Button("Save") {
                    try? localContext.save()
                    dismiss()
                }
            }
        }
    }
    
    init(entry: Entry?, in container: ModelContainer) {
        localContext = ModelContext(container)
        localContext.autosaveEnabled = false
        if let entry {
            localEntry = localContext.model(for: entry.id) as? Entry ?? Entry(content: "")
        } else {
            let newEntry = Entry(content: "")
            localContext.insert(newEntry)
            localEntry = newEntry
        }
        self.entry = entry
    }
}

@Model
class Entry {
    var content: String
    
    init(content: String) {
        self.content = content
    }
}

I made the following minimal code reproducing my problem. If you add some entries, open one entry, dismiss the sheet by swiping down and then quickly open another entry, the first entry is displayed even though you opened another one. If you take a little more time before opening another entry, it works fine. Do you have any idea where this might be coming from? I guess I'm making mistakes in the use of SwiftData but I can't figure out where.

I know there is a similar question, this one: Wrong View Displayed When Rapidly Switching Between Sheets but it doesn't solve my problem, it makes no difference.

import SwiftUI
import SwiftData

struct ContentView: View {
    @Query private var entries: [Entry]
    @Environment(\.modelContext) private var context
    @State private var selectedEntry: Entry?
    @State private var isAdding = false
    
    var body: some View {
        NavigationView {
            VStack {
                List {
                    ForEach(entries) { entry in
                        Text(entry.content)
                            .onTapGesture {
                                selectedEntry = entry
                            }
                    }
                }
            }
            .navigationTitle("Entries")
            .toolbar {
                ToolbarItem {
                    Button("Add entry", systemImage: "plus") {
                        isAdding = true
                    }
                }
            }
            .sheet(isPresented: $isAdding) {
                NavigationStack {
                    EntryDetail(entry: nil, in: context.container)
                }
            }
            .sheet(item: $selectedEntry) { entry in
                NavigationStack {
                    EntryDetail(entry: entry, in: context.container)
                }
            }
        }
    }
}

struct EntryDetail: View {
    private let entry: Entry?
    
    @State private var localEntry: Entry
    private let localContext: ModelContext
    
    @Environment(\.dismiss) private var dismiss
    
    var body: some View {
        VStack {
            Form {
                TextField("Type your entry", text: $localEntry.content, axis: .vertical)
            }
        }
        .navigationTitle("Entry")
        .toolbar {
            ToolbarItem(placement: .confirmationAction) {
                Button("Save") {
                    try? localContext.save()
                    dismiss()
                }
            }
        }
    }
    
    init(entry: Entry?, in container: ModelContainer) {
        localContext = ModelContext(container)
        localContext.autosaveEnabled = false
        if let entry {
            localEntry = localContext.model(for: entry.id) as? Entry ?? Entry(content: "")
        } else {
            let newEntry = Entry(content: "")
            localContext.insert(newEntry)
            localEntry = newEntry
        }
        self.entry = entry
    }
}

@Model
class Entry {
    var content: String
    
    init(content: String) {
        self.content = content
    }
}
Share Improve this question edited Mar 1 at 14:24 Edward asked Feb 28 at 12:05 EdwardEdward 458 bronze badges 6
  • 1 Note, NavigationView is deprecated use NavigationStack. Try replacing @State private var localEntry: Entry with @Bindable var localEntry: Entry – workingdog support Ukraine Commented Feb 28 at 12:53
  • @workingdogsupportUkraine @Bindable var localEntry: Entry solved the problem, thanks a lot! – Edward Commented Mar 1 at 14:41
  • glad it worked for you. The so called duplicate that closed your question has nothing to do with your problem. You are using SwiftData and importantly @Model class Entry not a struct. – workingdog support Ukraine Commented Mar 1 at 23:00
  • @workingdogsupportUkraine Yes, I hope this question will be unlocked so people can find the right answer instead of being misled. – Edward Commented Mar 2 at 8:37
  • EntryDetail init looks like a mistake and it should be moved to an action in the parent and passed in – malhal Commented Mar 3 at 15:16
 |  Show 1 more comment

2 Answers 2

Reset to default 1

Note, NavigationView is deprecated use NavigationStack.

Replace

@State private var localEntry: Entry

with

@Bindable var localEntry: Entry

I have no idea what all that flexing in the .init of EntryDetail is about. And why is the entry private? The entry should simply be passed into the view. If it's nil, you add a new entry, if not, you edit it.

There is no need for everything to happen in the .init. You check whether entry is nil in .onAppear, and if not, you copy its properties values to the local state for editing.

On save, if entry is not nil, you copy the state values back into the object, otherwise create a new entry with the values from local state and insert it into the context.

This is the typical approach shown in most Apple's SwiftUI tutorial and demo projects.

Here's the complete code, cleaned up and commented:

import SwiftUI
import SwiftData

struct SheetSwitchingView: View {
    
    @Query private var entries: [Entry]
    
    @Environment(\.modelContext) private var context
    
    @State private var selectedEntry: Entry?
    @State private var isAdding = false
    
    var body: some View {
        NavigationStack {
            VStack {
                List {
                    ForEach(entries) { entry in
                        Text(entry.content)
                            .onTapGesture {
                                selectedEntry = entry
                            }
                    }
                }
            }
            .navigationTitle("Entries")
            .toolbar {
                ToolbarItem {
                    Button("Add entry", systemImage: "plus") {
                        isAdding = true
                    }
                }
            }
            .sheet(isPresented: $isAdding) {
                NavigationStack {
                    SheetSwitchingEntryDetail(entry: nil)
                }
            }
            .sheet(item: $selectedEntry) { entry in
                NavigationStack {
                    SheetSwitchingEntryDetail(entry: entry)
                }
            }
        }
    }
}

struct SheetSwitchingEntryDetail: View {
    
    //Parameters
    let entry: Entry?
    
    //State values
    @State private var entryContent: String = ""

    //Environment values
    @Environment(\.modelContext) private var modelContext
    @Environment(\.dismiss) private var dismiss
    
    //Computed properties
    private var editorTitle: String {
        entry == nil ? "Add Entry" : "Edit Entry"
    }
    
    //Body
    var body: some View {
        Form {
            TextField("Type your entry", text: $entryContent, axis: .vertical)
        }
        .navigationTitle(editorTitle)
        .toolbar {
            ToolbarItem(placement: .confirmationAction) {
                Button("Save") {
                    save()
                    dismiss()
                }
            }
        }
        .onAppear {
            //Load any entry content into local state for editing
            entryContent = entry?.content ?? ""
        }
    }
    
    func save() {
        
        //Existing entry
        if let entry {
            
            //Copy state values back into object
            entry.content = entryContent
        }
        
        //New entry
        else {
            
            //Create new Entry
            let newEntry = Entry(content: entryContent)
            
            //Insert into context
            modelContext.insert(newEntry)
            
        }

                //Save context
        try? modelContext.save()
    }
}

@Model
class Entry {
    var content: String
    
    init(content: String) {
        self.content = content
    }
}

#Preview {
    SheetSwitchingView()
        .modelContainer(for: Entry.self, inMemory: true)
}

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1743616658a4479097.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信