我有一个待办事项列表应用程序。任务显示在列表中
@ObservableObject
。用户可以打开详细信息视图来编辑任务属性(截止日期、位置、日程安排、注释等)。
我在此详细信息表的顶部有“保存”和“取消”按钮。保存或放弃对持久存储的更改可以正常工作并关闭详细信息表,使用户返回到列表。但是,点击“取消”时,已编辑的版本仍显示在列表中。我想要一种干净的方法来恢复该详细信息表中所做的所有更改,而不会弄乱我的代码。
这是我所做的事情的简化版本。请随意浏览一点(或很多),我留下了一些额外的细节来演示为什么我在这里找到的其他解决方案感觉像是错误的方法。
struct TaskListView: View {
@State private var selectedTask: Binding<TaskItemEditorViewModel>? = nil
@ObservedObject private var taskList: TaskListViewModel
var body: some View {
List {
ForEach($taskList.nextActions, id: \.uuid) { task in
TaskListRow(task: task)
.swipeActions {
Button {
selectedTask = task
} label: {
editSwipeActionLabel
}
}
}
}
.sheet(item: $selectedTask, onDismiss: { selectedTask = nil }) { task in
NavigationView {
TaskDetailView(dataStack: dataSource, draftTask: task)
}
}
}
}
struct TaskDetailView: View {
@Environment(\.dismiss) var dismiss
@State private var notesModalIsPresented = false
@Binding private var draftTask: TaskItemEditorViewModel
var body: some View {
Form {
Section(header: Text("I need to...")) {
TextEditor(text: $draftTask.title)
.font(.headline)
.multilineTextAlignment(.leading)
PriorityStepper(priority: $draftTask.priority)
}
Section(header: Text("Task repitition & expiration")) {
ExpirationDatePicker(selectedDate: $draftTask.dueDate, dateIsSet: $draftTask.dueDateIsSet)
RecurrencePicker(recurrence: $draftTask.recurrence, recurrenceIsSet: $draftTask.hasRecurrence)
}
Section(header: Text("Task notes")) {
if draftTask.hasNotes {
editNotesButton
Text(draftTask.notes)
} else {
addNotesButton
}
}
Section() {
HStack {
Spacer()
markCompleteButton
Spacer()
}
}
}
.sheet(isPresented: $notesModalIsPresented, content: {
NotesEditor(notesText: $draftTask.notes, taskHasNotes: $draftTask.hasNotes, isPresented: $notesModalIsPresented)
})
.navigationBarItems(trailing: saveButton)
.navigationBarItems(leading: cancelButton)
}
private var saveButton: some View {
return Button {
// this saves to persistent storage
try? draftTask.saveChanges()
dismiss()
} label: {
Text("Done")
}
}
private var cancelButton: some View {
return Button {
// this basically restores the original properties,
// before any changes were made on this screen
draftTask.revertChanges()
dismiss()
} label: {
Text("Cancel")
}
}
}
如上所示,我尝试简单地调用我的视图模型的
.revertChanges
。这会将所有属性恢复为上次从数据库检索到的属性,即打开详细视图之前的原始状态。但是,当用户返回到 TaskListView
时,这一点不会反映出来。所做的任何更改仍然存在(尽管没有保存到数据库中)。
我还尝试将 @Binding var 复制到 @State var 中,对其进行编辑,然后在关闭工作表时将更改复制回来(或不复制),如此处建议的那样。
所以我就进去了
TaskDetailView
:
@Binding var sourceTask: TaskItemEditorViewModel
@State var draftTask: TaskItemEditorViewModel
这样初始化:
init(draftTask: Binding<TaskItemEditorViewModel>) {
self._sourceTask = draftTask
self._draftTask = State(initialValue: draftTask.wrappedValue)
}
唯一的问题是,绑定(文本字段、步进器、选择器等)不再与
@State
一起使用,而不是 @Binding
。
这个人似乎通过添加一堆额外的代码
.onAppear
,为其对象属性添加额外的@State
变量(例如我的$draftTask.title
,$draftTask.priority
等)来解决这个问题。
我什至没有尝试过这种解决方法,因为对我来说它违背了 SwiftUI 的全部目的。我已经有了完全用 UIKit 构建的应用程序的工作版本。这种混乱的代码是我将这样的视图转换为 SwiftUI 并利用绑定的一个重要原因。
正如我在此处链接的第一个 SO 线程中所述,这种行为在 iOS 联系人应用程序等中很明显。您可以打开编辑模式,然后选择保存更改或取消。经过大量的谷歌搜索和反复试验后,我觉得我一定错过了一些如此明显的东西,但我一生都找不到它。
编辑澄清:两种视图模型类型都是类,而不是结构。
您需要删除
Binding<TaskItemEditorViewModel>
和非标准 SwiftUI 的 State(initialValue: draftTask.wrappedValue)
。
Apple 建议解决此问题的方法是创建一个
@State
结构来保存详细数据。请参阅 SwiftUI WWDC 2020 中的数据要点 4:18