【问题标题】:SwiftUI @FetchRequest Core Data Changes to Relationships Don't RefreshSwiftUI @FetchRequest 对关系的核心数据更改不刷新
【发布时间】:2020-03-09 18:27:57
【问题描述】:

具有实体Node 的核心数据模型具有namecreatedAt、多对多关系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 questionsame solution 不同,但是FWIW 我无法使提议的解决方案发挥作用,而且解决方案本身感觉像是一种妥协,尤其是在context 是如何在一个非标准方式(据我所知)。但归根结底,我只想要一个可行的解决方案,所以我愿意接受任何建议。
  • 我在这里发布了一个适合您的解决方案来解决类似的问题:stackoverflow.com/a/65309334/7965564

标签: swift core-data swiftui combine


【解决方案1】:

看看ObservedObjectCollection,如果您需要观察相关对象的变化而不显示观察它们的子视图,这很有用。

在我的例子中,一个启发性的例子是一个视图(观察 relating 对象),它呈现了一个从 related 对象派生的值,而没有观察相关对象本身。 p>

【讨论】:

    猜你喜欢
    • 2020-04-17
    • 2014-06-06
    • 2012-01-18
    • 1970-01-01
    • 2020-10-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多