我有一个简单的待办事项清单演示应用程序,该应用程序是为了解SwiftUI与Core Data之间的关系而构建的。当我在Task
视图中修改TaskDetail
时,所做的更改不会反映在TextField
视图中的TaskRow
内。这两个视图都是ContentView
的子视图。
Sudo Fix :如果我将TextField
更改为Text
,则视图将按预期更新;但是,我需要从该行编辑title
中的Task
属性。
第二个选项:似乎每个教程都避免使用Core Data更新子视图中的数据。我可以使用@EnvironmentObject
轻松地跨视图(使用结构)同步数据。但是,保持环境数据和Core Data存储同步听起来像一场噩梦。我希望有一种更简单的方法:D
发布视频:https://youtu.be/JV-jQHpXE4Y
ContentView.swift
import SwiftUI
import CoreData
struct ContentView: View {
@Environment(\.managedObjectContext) var context
@FetchRequest(entity: Task.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Task.position, ascending: true)]) var tasks: FetchedResults<Task>
init() {
print("INIT - Content View")
}
var body: some View {
NavigationView {
VStack {
todoList
newButton
}
}
}
}
extension ContentView {
var todoList: some View {
List {
ForEach(self.tasks, id: \.id) { task in
NavigationLink(destination: TaskDetail(task: task)) {
TaskRow(task: task)
}
}
.onDelete { indices in
for index in indices {
self.context.delete(self.tasks[index])
try? self.context.save()
}
}
.onMove(perform: move)
}
.navigationBarItems(trailing: EditButton())
}
var newButton: some View {
Button(action: {
self.newTask()
}, label: {
Text("Add Random Task")
}).padding([.bottom, .top], 20)
}
}
extension ContentView {
private func newTask() {
let things = ["Cook", "Clean", "Eat", "Workout", "Program"]
let newItem = Task(context: self.context)
newItem.id = UUID()
newItem.title = things.randomElement()!
newItem.position = Int64(self.tasks.count)
newItem.completed = Bool.random()
try? self.context.save()
}
private func move(from source: IndexSet, to destination: Int) {
// Make an array of items from fetched results
var revisedItems: [Task] = self.tasks.map{ $0 }
// change the order of the items in the array
revisedItems.move(fromOffsets: source, toOffset: destination )
// update the userOrder attribute in revisedItems to
// persist the new order. This is done in reverse order
// to minimize changes to the indices.
for reverseIndex in stride(from: revisedItems.count - 1, through: 0, by: -1) {
revisedItems[reverseIndex].position = Int64(reverseIndex)
try? self.context.save()
}
}
}
TaskRow.swift
import SwiftUI
import CoreData
struct TaskRow: View {
@Environment(\.managedObjectContext) var context
@ObservedObject var task: Task
@State private var title: String
init(task: Task) {
self.task = task
self._title = State(initialValue: task.title ?? "")
print("INIT - TaskRow Initialized: title=\(title), completed=\(task.completed)")
}
var body: some View {
HStack {
TextField(self.task.title ?? "", text: self.$title) {
self.task.title = self.title
self.save()
}.foregroundColor(.black)
// Text(self.task.title ?? "")
Spacer()
Text("\(self.task.position)")
Button(action: {
self.task.completed.toggle()
self.save()
}, label: {
Image(systemName: self.task.completed ? "checkmark.square" : "square")
}).buttonStyle(BorderlessButtonStyle())
}
}
}
extension TaskRow {
func save() {
try? self.context.save()
print("SAVE - TaskRow")
}
}
TaskDetail.swift
import SwiftUI
struct TaskDetail: View {
@Environment(\.managedObjectContext) var context
@ObservedObject var task: Task
@State private var title: String
init(task: Task) {
self.task = task
self._title = State(initialValue: task.title ?? "")
print("INIT - TaskDetail Initialized: title=\(title), completed=\(task.completed)")
}
var body: some View {
Form {
Section {
TextField(self.title, text: self.$title) {
self.task.title = self.title
self.save()
}.foregroundColor(.black)
}
Section {
Button(action: {
self.task.completed.toggle()
self.save()
}, label: {
Image(systemName: self.task.completed ? "checkmark.square" : "square")
}).buttonStyle(BorderlessButtonStyle())
}
}
}
}
extension TaskDetail {
func save() {
try? self.context.save()
print("SAVE - TaskDetail")
}
}
这与TextField
中的'PlaceHolder'文本(第一个参数)有关。如果我在Task
中修改了TaskDetail
,然后导航回到ContentView
,它似乎没有更新。但是,如果我删除行中的文本(突出显示,退格键),则“ PlaceHolder”文本将包含更新后的值。
奇怪的是,退出应用程序并重新启动它会以深色字体显示TextField
中所做的更改(预期的行为而无需重新启动)。
答案 0 :(得分:0)
尝试以下
var body: some View {
HStack {
TextField(self.task.title ?? "", text: self.$title) {
self.task.title = self.title
self.save()
}.foregroundColor(.black)
.onReceive(task.objectWillChange) { _ in // << here !!
if task.title != self.title {
task.title = self.title
}
}
答案 1 :(得分:0)
使用SwiftUI很有趣(我对此没有经验)。但是我可以在不同论坛上有关TextField
的大多数问题中看到,绑定值可以由.constant
创建。因此,使用此:
TextField(self.task.title ?? "", text: .constant(self.task.title!))
现在应该可以使用。
GIF中的演示
答案 2 :(得分:0)
@Binding
代替@State
请记住,TextField
实际上是SwiftUI View
(通过继承),这一点很重要。父子关系实际上是TaskRow -> TextField
。
@State
用于表示视图的“状态”。尽管此值可以传递,但它并不意味着要被其他视图写入(它具有唯一的真理来源)。
在上述情况下,我实际上是将title
(通过$
前缀)传递给另一个视图,同时期望父级或子级修改title
属性。 @Binding
支持视图或属性与视图之间的双向通信。
@State Apple文档:https://developer.apple.com/documentation/swiftui/state
@绑定Apple文档:https://developer.apple.com/documentation/swiftui/binding
Jared Sinclair的包装器规则:https://jaredsinclair.com/2020/05/07/swiftui-cheat-sheet.html
更改TaskRow
和TaskDetail
视图可解决此问题:
TaskRow.swift
import SwiftUI
import CoreData
struct TaskRow: View {
@Environment(\.managedObjectContext) var context
@ObservedObject var task: Task
@Binding private var title: String
init(task: Task) {
self.task = task
self._title = Binding(get: {
return task.title ?? ""
}, set: {
task.title = $0
})
print("INIT - TaskRow Initialized: title=\(task.title ?? ""), completed=\(task.completed)")
}
var body: some View {
HStack {
TextField("Task Name", text: self.$title) {
self.save()
}.foregroundColor(.black)
Spacer()
Text("\(self.task.position)")
Button(action: {
self.task.completed.toggle()
self.save()
}, label: {
Image(systemName: self.task.completed ? "checkmark.square" : "square")
}).buttonStyle(BorderlessButtonStyle())
}
}
}
extension TaskRow {
func save() {
try? self.context.save()
print("SAVE - TaskRow")
}
}
TaskDetail.swift
import SwiftUI
struct TaskDetail: View {
@Environment(\.managedObjectContext) var context
@ObservedObject var task: Task
@Binding private var title: String
init(task: Task) {
self.task = task
self._title = Binding(get: {
return task.title ?? ""
}, set: {
task.title = $0
})
print("INIT - TaskDetail Initialized: title=\(task.title ?? ""), completed=\(task.completed)")
}
var body: some View {
Form {
Section {
TextField("Task Name", text: self.$title) {
self.save()
}.foregroundColor(.black)
}
Section {
Button(action: {
self.task.completed.toggle()
self.save()
}, label: {
Image(systemName: self.task.completed ? "checkmark.square" : "square")
}).buttonStyle(BorderlessButtonStyle())
}
}
}
}
extension TaskDetail {
func save() {
try? self.context.save()
print("SAVE - TaskDetail")
}
}