在父视图中使用@FetchRequest结果

时间:2020-03-19 15:33:25

标签: swiftui fetchrequest

我正在尝试将某些组件抽象为更小的部分。为此,我创建了以下清单:

struct ArticleList: View {
    var fetchRequest: FetchRequest<Article>
    var results: FetchedResults<Article> { fetchRequest.wrappedValue }

    init() {
        fetchRequest = FetchRequest<Article>(
            entity: Article.entity(),
            sortDescriptors: []
        )
    }

    var body: some View {
        ForEach(results) { article in
            Text(article.name ?? "")
        }
    }
}

现在我有一个容器,它将显示列表组件,如果满足子组件中的条件,还会显示一些其他内容:

struct Container: View {
    var body: some View {
        let articleList = ArticleList2()

        return Group {
            if articleList.results.isEmpty {
                Text("Add")
            }

            articleList
        }
    }
}

我的问题是,代码由于以下异常而崩溃:

Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)

进一步调试控制台,可为我提供以下反馈:

(lldb) po self.results
error: warning: couldn't get required object pointer (substituting NULL): Couldn't load 'self' because its value couldn't be evaluated

调试po self.fetchRequest可行,并且包含FetchRequest<Article>实例的一个实例。 po self.fetchRequest.wrappedValue提供与上述self.results相同的错误。

有人知道为什么这段代码崩溃了,有什么可能的解决方法吗?

谢谢。

2 个答案:

答案 0 :(得分:1)

您的提取请求无效,因为在创建和使用ArticleList视图时尚无托管对象上下文。

无论如何...在下面找到修改后的代码(我尝试将更改降到最低),您的代码有效。使用Xcode 11.4 / iOS 13.3进行了测试

struct ArticleList: View {
    // always keep View memebers private to safe yourself from misconcept
    private var fetchRequest: FetchRequest<Article>
    private var results: FetchedResults<Article> { fetchRequest.wrappedValue }
    private var reportEmpty: () -> ()

    init(_ onEmpty: @escaping () -> ()) {
        reportEmpty = onEmpty

        // FetchRequest needs @Environment(\.managedObjectContext) which is not available (!) yet
        fetchRequest = FetchRequest<Article>(
            entity: Article.entity(),
            sortDescriptors: []
        )
    }

    var body: some View {
        // here (!) results are valid, because before call body SwiftUI executed FetchRequest
        if self.results.isEmpty { 
            self.reportEmpty()
        }
        return Group {
            ForEach(results, id: \.self) { article in
                Text(article.name ?? "")
            }
        }
    }
}

struct Container: View {
    @State private var isEmpty = false

    var body: some View {
        return Group {
            if self.isEmpty { // use view state for any view's conditions
                Text("Add")
            }

            ArticleList { // View must live only in view hierarchy !!
                DispatchQueue.main.async {
                    self.isEmpty = true
                }
            }
        }
    }
}

答案 1 :(得分:0)

虽然@Asperi的解决方案有效,但我现在确实以不同的方式实现了它。

我将闭包传递到ArticleList中,如果按下Button,则会执行该回调。 Button仅在ArticleList为空的情况下可用,但是现在ArticleList负责显示该按钮(这使它对我更具可重用性:

struct ArticleList: View {
    var fetchRequest: FetchRequest<Article>
    var results: FetchedResults<Article> { fetchRequest.wrappedValue }

    let onCreate: (() -> Void)

    init(onCreate: @escaping (() -> Void)) {
        fetchRequest = FetchRequest<Article>(
            entity: Article.entity(),
            sortDescriptors: []
        )

        self.onCreate = onCreate
    }

    var body: some View {
        Group {
            if results.isEmpty {
                Button(action: onCreate) {
                    Text("Add")
                }
            }

            ForEach(results) { article in
                Text(article.name ?? "")
            }
        }

    }
}

struct Container: View {
    var body: some View {
        ArticleList(onCreate: onCreate)
    }

    func onCreate() {
        // Create the article inside the container
    }
}