Использование результатов @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 ответа
Ваш запрос на выборку не работает, потому что контекст управляемого объекта еще не доступен в то время, когда вы создаете и используете 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
}
}
}
}
}
Хотя решение от @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
}
}