«'StateObject' требует, чтобы 'соответствовал 'ObservableObject'» при создании формы с двойной записью (добавление/редактирование) для основных данных без использования ViewModel

Как создать форму с двойной записью (добавление/редактирование) для основных данных без использования ViewModel? Если у меня есть сущность Core Data с именем Scenario и я создаю подробное представление, чтобы добавить или изменить, где у меня есть:

@StateObject var scenario: Scenario

но хочу, чтобы он поддерживал нулевое значение, поэтому я могу использовать его как для добавления, так и для обновления, но когда я изменяю его на:

@StateObject var scenario: Scenario?

Я получаю эту ошибку:

Общая структура «StateObject» требует, чтобы «Сценарий?» соответствовать «НаблюдаемыйОбъект»

Я смотрел это видео Пола Хадсона, но это просто деталь для чтения. Также этот тоже близок, от Swift Arcade, но это редактирование, а не редактирование/добавление. Я думаю, что в большинстве демонстраций доминируют простые экраны с одним атрибутом, например «имя».

В этом видео с основными данными у Стюарта Линча есть двойная форма для добавления/редактирования, но он использует ViewModel. Я видел много контента о неиспользовании View Models, включая этот, который, казалось, имел смысл. Пол Хадсон также освещает тему MVVM.

Моя форма сложная, с записями отношений, поэтому я не хочу создавать две отдельные формы, одну для добавления и одну для редактирования.

Я пытаюсь сделать это простым. Любая помощь будет оценена по достоинству.

@StateObject предназначен для создания экземпляров (ссылочного типа) и владения ими. В подробном (дочернем) представлении вам нужно @ObservedObject передать экземпляр в методе init. Чтобы эффективно добавлять и редактировать записи Core Data в одном и том же представлении, посмотрите это видео. И да, на самом деле вам нужна модель представления.
vadian 30.06.2023 21:58
stackoverflow.com/questions/70869061/… Просто найдите представления добавления и редактирования.
lorem ipsum 01.07.2023 10:07

Это хорошо, @loremipsum. Спасибо. Я не знаю, почему есть промежуточное представление по сравнению с созданием нового объекта в функции, но это, вероятно, из-за моего непонимания SwiftUI. Это означает, что вы можете создать объект без имени или с временным именем... все необходимые атрибуты должны быть заполнены, но это разумно.

lcj 01.07.2023 13:49

Проблема в том, что при инициализации необходим ObservableObject. С представлением редактирования оно у вас уже есть. Для добавления необходимо его создать. Это автономный вид, просто перейдите к нему, и обо всем остальном позаботятся. Вы можете использовать функцию, если хотите. Важная часть - иметь его на инициализации. Вы можете создать объект с помощью кнопки, а затем «выбрать его». Если мне нужны начальные значения, я обычно устанавливаю их методом «пробуждения из вставки». Вы можете «откатить» или сбросить настройки при закрытии представления добавления. Таким образом, если пользователь отменит или не сохранит, вы избавитесь от нового.

lorem ipsum 01.07.2023 14:28

@loremipsum, на самом деле я выбрал ваше решение, а не приведенное ниже, потому что я не хотел, чтобы несколько объектов поддерживали различные формы состояния, когда это могли сделать основные данные.

lcj 01.07.2023 21:37

@loremipsum Мне бы очень хотелось увидеть создание объекта с кнопкой и выбрать его. Можете ли вы показать пример в качестве решения, и я выберу его.

lcj 01.07.2023 21:52
stackoverflow.com/questions/65947186/…
lorem ipsum 01.07.2023 22:10

@loremipsum, у меня получилось. Не могу сказать вам, что мне нравится использовать листы для этого, поскольку объект действительно сложный, но... Я отвечу решением, поскольку ссылка SO с двойной записью, которую вы разместили, использует модель представления, и я пытался избежать этого.

lcj 03.07.2023 13:42
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать 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
9
56
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Я выполняю редактирование, как показано ниже, используя дочерний контекст в качестве «блокнота» для редактирования, чтобы предотвратить влияние отмененных изменений на основной контекст:

I'm using a child context like this:

    struct ItemEditorConfig: Identifiable {
        let id = UUID()
        let context: NSManagedObjectContext
        let item: Item
        
        init(viewContext: NSManagedObjectContext, objectID: NSManagedObjectID) {
            // create the scratch pad context
            context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
            context.parent = viewContext
            // load the item into the scratch pad
            item = context.object(with: objectID) as! Item
        }
    }
    
    struct ItemEditor: View {
        @ObservedObject var item: Item // this is the scratch pad item
        @Environment(\.managedObjectContext) private var context
        @Environment(\.dismiss) private var dismiss // causes body to run
        let onSave: () -> Void
        @State var errorMessage: String?
        
        var body: some View {
            NavigationView {
                Form {
                    Text(item.timestamp!, formatter: itemFormatter)
                    if let errorMessage = errorMessage {
                        Text(errorMessage)
                    }
                    Button("Update Time") {
                        item.timestamp = Date()
                    }
                }
                .toolbar {
                    ToolbarItem(placement: .navigationBarLeading) {
                        Button("Cancel") {
                            dismiss()
                        }
                    }
                    ToolbarItem(placement: .navigationBarTrailing) {
                        Button("Save") {
                            // first save the scratch pad context then call the handler which will save the view context.
                            do {
                                try context.save()
                                errorMessage = nil
                                onSave()
                            } catch {
                                let nsError = error as NSError
                                errorMessage  = "Unresolved error \(nsError), \(nsError.userInfo)"
                            }
                        }
                    }
                }
            }
        }
    }

    struct EditItemButton: View {
        let itemObjectID: NSManagedObjectID
        @Environment(\.managedObjectContext) private var viewContext
        @State var itemEditorConfig: ItemEditorConfig?
        
        var body: some View {
            Button(action: edit) {
                Text("Edit")
            }
            .sheet(item: $itemEditorConfig, onDismiss: didDismiss) { config in
                ItemEditor(item: config.item) {
                    do {
                        try viewContext.save()
                    } catch {
                        // Replace this implementation with code to handle the error appropriately.
                        // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
                        let nsError = error as NSError
                        fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
                    }
                    itemEditorConfig = nil // dismiss the sheet
                }
                .environment(\.managedObjectContext, config.context)
            }
        }
        
        func edit() {
            itemEditorConfig = ItemEditorConfig(viewContext: viewContext, objectID: itemObjectID)
        }
        
        func didDismiss() {
            // Handle the dismissing action.
        }
    }
    
    struct DetailView: View {
        @ObservedObject var item: Item
        
        var body: some View {
            Text("Item at \(item.timestamp!, formatter: itemFormatter)")
                .toolbar {
                    ToolbarItem(placement: .navigationBarTrailing) {
                        EditItemButton(itemObjectID: item.objectID)
                    }
                }
        }
    }

Спасибо! Просматриваю сейчас. Я шел по этому пути, так как мне очень нравится идея внесения всех изменений и возможности их отменить... также мне приходилось иметь дело с вложенными наблюдаемыми объектами.

lcj 01.07.2023 14:24

Недостатком было то, что у меня было несколько слоев различных типов настойчивости.

lcj 01.07.2023 14:33

Я не думаю, что могу делать такие промежуточные объекты. Я уже могу откатить изменения в основных объектах данных и не хочу заново создавать каждую сущность в модели данных.

lcj 03.07.2023 12:35
Ответ принят как подходящий

Я следовал шаблону в посте, рекомендованном @loremipsum, но адаптировал его, чтобы мне не пришлось включать модель представления, что является дизайнерским решением (об этом много споров, но для этого примера может быть достаточно SwiftUI + Core Data) .

Я добавил объект в представление:

@State var newScenario: Scenario? = nil

Добавлен лист для просмотра нового элемента:

        .sheet(item: $newScenario, onDismiss: {
            moc.rollback()
        }, content: { newItem in
            NavigationView{
                ScenarioDetailView(scenario: newItem)
            }
        })

Кнопка для его создания:

        .toolbar {
            ToolbarItemGroup (placement: .navigationBarTrailing) {
                Button {
                    createNewScenario()
                } label: {
                    
                    Image(systemName: "plus")
                }
                .padding()
            }
        }

и функция для создания нового сценария, вызывающего отображение листа:

func createNewScenario() {
    var scenario = Scenario(context: moc)
    scenario.name = "New Scenario"
    newScenario = scenario
}

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