Использование результатов @FetchRequest в родительском представлении - PullRequest
1 голос
/ 19 марта 2020

Я пытаюсь абстрагировать некоторые компоненты на более мелкие части. Для этого я создал следующий листинг:

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 ]

1 голос
/ 19 марта 2020

Ваш запрос на выборку не работает, поскольку во время создания и использования представления 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
                }
            }
        }
    }
}
0 голосов
/ 20 марта 2020

Пока работает решение @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
    }
}
...