【发布时间】:2020-03-09 18:27:57
【问题描述】:
具有实体Node 的核心数据模型具有name、createdAt、多对多关系children 和一对一关系parent(均为可选)。使用 CodeGen 类定义。
使用带有parent == nil 谓词的@FetchRequest,可以获取根节点,然后使用关系遍历树。
根节点 CRUD 可以很好地刷新视图,但对子节点的任何修改在重新启动之前都不会显示,尽管更改会保存在 Core Data 中。
以下代码中的最简单示例说明了删除子节点的问题。删除在 Core Data 中有效,但如果删除是在子级上,则视图不会刷新。如果在根节点上,视图刷新工作正常。
我是 Swift 新手,如果这是一个相当初级的问题,我深表歉意,但是如何在更改子节点时刷新视图?
import SwiftUI
import CoreData
extension Node {
class func count() -> Int {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let fetchRequest: NSFetchRequest<Node> = Node.fetchRequest()
do {
let count = try context.count(for: fetchRequest)
print("found nodes: \(count)")
return count
} catch let error as NSError {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
}
}
struct ContentView: View {
@Environment(\.managedObjectContext) var managedObjectContext
@FetchRequest(entity: Node.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Node.createdAt, ascending: true)], predicate: NSPredicate(format: "parent == nil"))
var nodes: FetchedResults<Node>
var body: some View {
NavigationView {
List {
NodeWalkerView(nodes: Array(nodes.map { $0 as Node }) )
}
.navigationBarItems(trailing: EditButton())
}
.onAppear(perform: { self.loadData() } )
}
func loadData() {
if Node.count() == 0 {
for i in 0...3 {
let node = Node(context: self.managedObjectContext)
node.name = "Node \(i)"
for j in 0...2 {
let child = Node(context: self.managedObjectContext)
child.name = "Child \(i).\(j)"
node.addToChildren(child)
for k in 0...2 {
let subchild = Node(context: self.managedObjectContext)
subchild.name = "Subchild \(i).\(j).\(k)"
child.addToChildren(subchild)
}
}
}
do {
try self.managedObjectContext.save()
} catch {
print(error)
}
}
}
}
struct NodeWalkerView: View {
@Environment(\.managedObjectContext) var managedObjectContext
var nodes: [Node]
var body: some View {
ForEach( self.nodes, id: \.self ) { node in
NodeListWalkerCellView(node: node)
}
.onDelete { (indexSet) in
let nodeToDelete = self.nodes[indexSet.first!]
self.managedObjectContext.delete(nodeToDelete)
do {
try self.managedObjectContext.save()
} catch {
print(error)
}
}
}
}
struct NodeListWalkerCellView: View {
@ObservedObject var node: Node
var body: some View {
Section {
Text("\(node.name ?? "")")
if node.children!.count > 0 {
NodeWalkerView(nodes: node.children?.allObjects as! [Node] )
.padding(.leading, 30)
}
}
}
}
编辑:
一个简单但不令人满意的解决方案是让NodeListWakerCellView 使用另一个@FetchRequest 检索孩子,但这感觉不对,因为对象已经可用。为什么要运行另一个查询?但也许这是目前附加发布功能的唯一方法?
我想知道是否有另一种方法可以将Combine 发布者直接用于儿童,可能在.map 内?
struct NodeListWalkerCellView: View {
@ObservedObject var node: Node
@FetchRequest var children: FetchedResults<Node>
init( node: Node ) {
self.node = node
self._children = FetchRequest(
entity: Node.entity(),
sortDescriptors: [NSSortDescriptor(keyPath: \Node.createdAt, ascending: false)],
predicate: NSPredicate(format: "%K == %@", #keyPath(Node.parent), node)
)
}
var body: some View {
Section {
Text("\(node.name ?? "")")
if node.children!.count > 0 {
NodeWalkerView(nodes: children.map({ $0 as Node }) )
.padding(.leading, 30)
}
}
}
}
【问题讨论】:
-
@Asperi 我在发布之前查看了该问题,除非我误解了某些内容,否则这个问题会有所不同,因为 tree walker
map步骤在视图之间传递了一个数组。我意识到same question与same solution不同,但是FWIW 我无法使提议的解决方案发挥作用,而且解决方案本身感觉像是一种妥协,尤其是在context是如何在一个非标准方式(据我所知)。但归根结底,我只想要一个可行的解决方案,所以我愿意接受任何建议。 -
我在这里发布了一个适合您的解决方案来解决类似的问题:stackoverflow.com/a/65309334/7965564
标签: swift core-data swiftui combine