На iPad самая левая панель (боковая панель) SplitNavigationView имеет кнопку панели инструментов, которая отображает лист. Этот лист может загружать или удалять все объекты SwiftData. Эти объекты перечислены на второй панели (Содержимое). Нажатие кнопки загрузки создает новые образцы данных и без проблем отображает их. Однако нажатие кнопки удаления не оказывает никакого эффекта и не запускает обновление представления.
Контекст модели находится в среде, а список передается с помощью @Query. Я попытался внедрить ModelContext в SettingsSheet, а не получать к нему доступ через среду, но результат тот же. Фактически при перезапуске данные иногда даже не удаляются.
Я сократил код до минимума, чтобы показать проблему, и его нужно просто скопировать, вставить и запустить. Я неправильно понимаю, как операции SwiftData распространяются через среду? Требуется ли другое обращение с наличием листа? Любая помощь принимается с благодарностью.
import SwiftData
import SwiftUI
@main
struct ProblemTestApp: App {
let container: ModelContainer
var body: some Scene {
WindowGroup {
NavigationSplitView {
SidebarView()
} content: {
ContentView()
} detail: {
DetailTabbedView()
}
.modelContainer(container)
}
}
init() {
let schema = Schema( [ Monkey.self ] )
let configuration = ModelConfiguration("ProblemTestApp", schema: schema)
do {
container = try ModelContainer(for: schema, configurations: configuration)
} catch {
fatalError("Could not configure the SwiftData container.")
}
}
}
// SidebarView
struct SidebarView: View {
@State private var showingSettingsSheet = false
var body: some View {
Text("Sidebar Monkey")
.toolbar {
Button {
showingSettingsSheet.toggle()
} label: {
Label("Show settings", systemImage: "gearshape")
}
}
.sheet(isPresented: $showingSettingsSheet) { /* On dismiss. */ } content: {
SettingsSheet()
}
}
}
// ContentView
struct ContentView: View {
@Query var allMonkeys: [Monkey]
var body: some View {
List {
Text("Monkey count = \(allMonkeys.count)")
ForEach(allMonkeys) { monkey in
Text(monkey.name)
}
}
}
}
// DetailTabbedView
struct DetailTabbedView: View {
var body: some View {
Text("Detail Tabbed View (tabs to come)")
}
}
// Monkey model
@Model
final class Monkey: Identifiable {
var id: UUID = UUID()
var name: String = ""
init(name: String) {
self.name = name
}
}
// SettingsSheet
struct SettingsSheet: View {
@Environment(\.modelContext) var context
var body: some View {
NavigationStack {
HStack {
Button("Load") {
for _ in 0...9 {
let monkey = Monkey(name: String(Int.random(in: 0...999)))
context.insert(monkey)
}
}
Button("Delete") {
do {
try context.delete(model: Monkey.self)
print("Deleted all the Monkeys")
} catch {
print("Failed to delete all Monkeys.")
}
}
}
.navigationTitle("Monkey Settings")
}
}
}
@workingdog поддерживает Украину. В реальном приложении NavigationStack предназначен только для заголовка и панели инструментов. Есть ли лучший способ добиться этого? Я пробовал сохранить контекст после действий загрузки и удаления, но безуспешно.
try context.delete(model: Monkey.self)
Этот код удаляет что? Вы говорите удалить объект Monkey, но не конкретный.
Проверь это:
Как разрешить пользователям удалять строки из списка
Привет, спасибо за комментарий, но эта строка кода удаляет все объекты типа Monkey. Экономит время и код, когда вам нужно удалить их все сразу.
Ты прав. Я не читал ту часть, где написано, что он удалит все. Я думал, ты пытаешься удалить только один. Виноват!
Попробуйте поместить контейнер модели в группу окон, а не в разделенное представление. Также вам не нужно помечать свою модель как идентифицируемую, модель уже есть. Вам не нужно свойство id.
Хороший вопрос по поводу модели, спасибо. Перемещение modelContainer по-прежнему не вызывает обновление представления, но вызывает перезапуск (после удаления) для последовательного удаления объектов, чего раньше не происходило. Маленькие шаги, так что еще раз спасибо.
Проблема в функции, которую вы используете для удаления.
Когда я запускаю ваш код в отладчике, я замечаю несколько интересных вещей:
Сразу после вызова context.delete(model: Monkey.self)
я вижу в отладчике, что контекст модели не содержит удаленных объектов, поскольку свойство массива deletedModelsArray
пусто.
Более того, если я добавлю выборку перед удалением, а затем после вызова удаления проверю флаг isDeleted
объекта, то это будет false
.
Очевидно, что этот метод является своего рода пакетным методом, который не обновляет состояние ModelContext
или каких-либо объектов модели в памяти, и я предполагаю, что это сделано из соображений производительности, что имеет смысл. Хотя это могло быть лучше задокументировано.
Для операций рядом с пользовательским интерфейсом я рекомендую вместо этого прибегнуть к отдельным удалениям, потому что тогда объект ModelContext
и ваше представление будут правильно обновлены.
let monkeys = try context.fetch(FetchDescriptor<Monkey>())
for monkey in monkeys {
context.delete(monkey)
}
или более компактный, если вы предпочитаете
try context.fetch(FetchDescriptor<Monkey>()).forEach(context.delete)
Наблюдение: почему у вас на листе есть
NavigationStack
, вы не переходите ни к одному пункту назначения. Пробовали ли вы добавитьsave()
послеdelete
.