Как создать форму с двойной записью (добавление/редактирование) для основных данных без использования ViewModel? Если у меня есть сущность Core Data с именем Scenario и я создаю подробное представление, чтобы добавить или изменить, где у меня есть:
@StateObject var scenario: Scenario
но хочу, чтобы он поддерживал нулевое значение, поэтому я могу использовать его как для добавления, так и для обновления, но когда я изменяю его на:
@StateObject var scenario: Scenario?
Я получаю эту ошибку:
Общая структура «StateObject» требует, чтобы «Сценарий?» соответствовать «НаблюдаемыйОбъект»
Я смотрел это видео Пола Хадсона, но это просто деталь для чтения. Также этот тоже близок, от Swift Arcade, но это редактирование, а не редактирование/добавление. Я думаю, что в большинстве демонстраций доминируют простые экраны с одним атрибутом, например «имя».
В этом видео с основными данными у Стюарта Линча есть двойная форма для добавления/редактирования, но он использует ViewModel. Я видел много контента о неиспользовании View Models, включая этот, который, казалось, имел смысл. Пол Хадсон также освещает тему MVVM.
Моя форма сложная, с записями отношений, поэтому я не хочу создавать две отдельные формы, одну для добавления и одну для редактирования.
Я пытаюсь сделать это простым. Любая помощь будет оценена по достоинству.
Это хорошо, @loremipsum. Спасибо. Я не знаю, почему есть промежуточное представление по сравнению с созданием нового объекта в функции, но это, вероятно, из-за моего непонимания SwiftUI. Это означает, что вы можете создать объект без имени или с временным именем... все необходимые атрибуты должны быть заполнены, но это разумно.
Проблема в том, что при инициализации необходим ObservableObject. С представлением редактирования оно у вас уже есть. Для добавления необходимо его создать. Это автономный вид, просто перейдите к нему, и обо всем остальном позаботятся. Вы можете использовать функцию, если хотите. Важная часть - иметь его на инициализации. Вы можете создать объект с помощью кнопки, а затем «выбрать его». Если мне нужны начальные значения, я обычно устанавливаю их методом «пробуждения из вставки». Вы можете «откатить» или сбросить настройки при закрытии представления добавления. Таким образом, если пользователь отменит или не сохранит, вы избавитесь от нового.
@loremipsum, на самом деле я выбрал ваше решение, а не приведенное ниже, потому что я не хотел, чтобы несколько объектов поддерживали различные формы состояния, когда это могли сделать основные данные.
@loremipsum Мне бы очень хотелось увидеть создание объекта с кнопкой и выбрать его. Можете ли вы показать пример в качестве решения, и я выберу его.
Отвечает ли это на ваш вопрос? Используйте одно и то же представление для добавления и редактирования объектов CoreData
@loremipsum, у меня получилось. Не могу сказать вам, что мне нравится использовать листы для этого, поскольку объект действительно сложный, но... Я отвечу решением, поскольку ссылка SO с двойной записью, которую вы разместили, использует модель представления, и я пытался избежать этого.





Я выполняю редактирование, как показано ниже, используя дочерний контекст в качестве «блокнота» для редактирования, чтобы предотвратить влияние отмененных изменений на основной контекст:
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)
}
}
}
}
Спасибо! Просматриваю сейчас. Я шел по этому пути, так как мне очень нравится идея внесения всех изменений и возможности их отменить... также мне приходилось иметь дело с вложенными наблюдаемыми объектами.
Недостатком было то, что у меня было несколько слоев различных типов настойчивости.
Я не думаю, что могу делать такие промежуточные объекты. Я уже могу откатить изменения в основных объектах данных и не хочу заново создавать каждую сущность в модели данных.
Я следовал шаблону в посте, рекомендованном @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
}
@StateObjectпредназначен для создания экземпляров (ссылочного типа) и владения ими. В подробном (дочернем) представлении вам нужно@ObservedObjectпередать экземпляр в методеinit. Чтобы эффективно добавлять и редактировать записи Core Data в одном и том же представлении, посмотрите это видео. И да, на самом деле вам нужна модель представления.