Удаление SwiftData не вызывает обновление представления в SplitNavigationView — iPad

На 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")
        }
    }
}

Наблюдение: почему у вас на листе есть NavigationStack, вы не переходите ни к одному пункту назначения. Пробовали ли вы добавить save() после delete.

workingdog support Ukraine 26.06.2024 01:56

@workingdog поддерживает Украину. В реальном приложении NavigationStack предназначен только для заголовка и панели инструментов. Есть ли лучший способ добиться этого? Я пробовал сохранить контекст после действий загрузки и удаления, но безуспешно.

Magnas 26.06.2024 07:51
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
2
115
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

try context.delete(model: Monkey.self)

Этот код удаляет что? Вы говорите удалить объект Monkey, но не конкретный.

Проверь это:

Как разрешить пользователям удалять строки из списка

Привет, спасибо за комментарий, но эта строка кода удаляет все объекты типа Monkey. Экономит время и код, когда вам нужно удалить их все сразу.

Magnas 26.06.2024 07:48

Ты прав. Я не читал ту часть, где написано, что он удалит все. Я думал, ты пытаешься удалить только один. Виноват!

Bitco Software 26.06.2024 08:12

Попробуйте поместить контейнер модели в группу окон, а не в разделенное представление. Также вам не нужно помечать свою модель как идентифицируемую, модель уже есть. Вам не нужно свойство id.

Bitco Software 26.06.2024 08:19

Хороший вопрос по поводу модели, спасибо. Перемещение modelContainer по-прежнему не вызывает обновление представления, но вызывает перезапуск (после удаления) для последовательного удаления объектов, чего раньше не происходило. Маленькие шаги, так что еще раз спасибо.

Magnas 26.06.2024 08:41
Ответ принят как подходящий

Проблема в функции, которую вы используете для удаления.

Когда я запускаю ваш код в отладчике, я замечаю несколько интересных вещей:

Сразу после вызова 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)

Другие вопросы по теме