【问题标题】:Using @FetchRequest results inside a parent view在父视图中使用 @FetchRequest 结果
【发布时间】:2020-03-19 15:33:25
【问题描述】:

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

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&lt;Article&gt; 实例的一个实例。 po self.fetchRequest.wrappedValue 提供与上述self.results 相同的错误。

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

谢谢。

【问题讨论】:

    标签: swiftui fetchrequest


    【解决方案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
                    }
                }
            }
        }
    }
    

    【讨论】:

    • 虽然我可以确认这行得通,但感觉不对,因为视图大楼内的状态发生了变化。这意味着如果状态发生变化,视图的层次结构将被构建两次。这也会触发执行多个 fetch 请求。
    • @TobiasTom,延迟刷新是常规做法,如果没有这种方法,您甚至不想进行一些操作(实际上它相当于setNeedsLayoutsetNeedsDisplay。SwiftUI处理这种情况并且不会重新- 渲染未更新的视图。但关于获取数据,这是您的责任,不应依赖或影响显示视图。其他一切由您决定。
    【解决方案2】:

    虽然@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
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2020-01-31
      • 2020-05-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-04-14
      • 1970-01-01
      • 2020-06-16
      相关资源
      最近更新 更多