1

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

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

有谁知道为什么这段代码会崩溃以及可能的解决方法是什么?

谢谢。

4

2 回答 2

2

您的获取请求不起作用,因为在您创建和使用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
                }
            }
        }
    }
}
于 2020-03-19T16:50:04.617 回答
0

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

我将一个闭包传递给 the ArticleList,如果Button按下 a ,就会执行该回调。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
    }
}
于 2020-03-20T16:00:37.467 回答