#swiftui #count #binding #predicate #fetchrequest
Вопрос:
Родительское представление отправляет дочернему представлению строку фильтра предикатов для a FetchRequest
и a Binding
для возврата FetchedResults
количества.
Строка фильтра является @State
свойством родительского представления. Изменение строки фильтра в родительском элементе приводит к тому, что дочерний элемент обновляет FetchRequest
.
Как я могу заставить дочернее представление обновить Binding
полученное с помощью нового FetchedResults
счетчика?
Смотрите Комментарии к коду в Child
моих попытках.
struct Parent: View {
@State private var filter = "" // Predicate filter
@State private var fetchCount = 0 // To be updated by child
var body: some View {
VStack {
// Create core data test records (Entity "Item" with a property named "name")
Button("Create Test Items") {
let context = PersistenceController.shared.container.viewContext
let names = ["a", "ab", "aab", "b"]
for name in names {
let item = Item(context: context)
item.name = name
try! context.save()
}
}
// Buttons to modify the filter to update the fetch request
HStack {
Button("Add") {
filter = filter "a"
}
Button("Remove") {
filter = String(filter.prefix(filter.count-1 >= 0 ? filter.count-1 : 0))
}
Text("Filter: (filter)")
}
Text("Fetch count in parent view (not ok): (fetchCount)")
Child(filter: filter, fetchCount: $fetchCount)
Spacer()
}
}
}
struct Child: View {
@FetchRequest var fetchRequest: FetchedResults<Item>
@Binding var fetchCount: Int
init(filter: String, fetchCount: Binding<Int>) {
let predicate = NSPredicate(format: "name BEGINSWITH[c] %@", filter)
self._fetchRequest = FetchRequest<Item>(
entity: Item.entity(),
sortDescriptors: [],
predicate: filter.isEmpty ? nil : predicate
)
self._fetchCount = fetchCount
// self.fetchCount = fetchRequest.count // "Modifying state during view updates, this will cause undefined behavior"
}
var body: some View {
VStack {
Text("Fetch count in child view (ok): (fetchRequest.count)")
.onAppear { fetchCount = fetchRequest.count } // Fires once, not whenever the predicate filter changes
// The ForEach's onAppear() doesn't update the count when the results are reduced and repeatedly updates for every new item in results
ForEach(fetchRequest) { item in
Text(item.name ?? "nil")
}
//.onAppear { fetchCount = fetchRequest.count }
}
}
}
Ответ №1:
есть 2 вещи, которые мне нужно было сделать, чтобы ваш код работал. Сначала вам нужно обновить фильтр в дочернем представлении при изменении фильтра в родительском. Это делается в моем коде с использованием традиционной привязки. Затем, поскольку onAppear происходит в основном только один раз, я использовал onReceive для обновления счета выборки. Вот мой код:
РЕДАКТИРОВАТЬ: с предложениями от @Jesse Blake
import Foundation
import SwiftUI
struct Child: View {
@FetchRequest var fetchRequest: FetchedResults<Item>
@Binding var fetchCount: Int
var filter: String // <-- here
init(filter: String, fetchCount: Binding<Int>) {
self.filter = filter // <-- here
let predicate = NSPredicate(format: "name BEGINSWITH[c] %@", filter)
self._fetchRequest = FetchRequest<Item>(
entity: Item.entity(),
sortDescriptors: [],
predicate: filter.isEmpty ? nil : predicate
)
self._fetchCount = fetchCount
}
var body: some View {
VStack {
Text("Fetch count in child view (ok): (fetchRequest.count)")
ForEach(fetchRequest) { item in
Text(item.name ?? "nil")
}
}
// in older ios
// .onChange(of: fetchRequest.count) { newVal in // <-- here
// fetchCount = fetchRequest.count
// }
.onReceive(fetchRequest.publisher.count()) { _ in // <-- here
fetchCount = fetchRequest.count
}
}
}
struct Parent: View {
@Environment(.managedObjectContext) private var viewContext
@State private var filter = "" // Predicate filter
@State private var fetchCount = 0 // To be updated by child
var body: some View {
VStack (spacing: 50) {
// Create core data test records (Entity "Item" with a property named "name")
Button("Create Test Items") {
let context = PersistenceController.shared.container.viewContext
let names = ["a", "ab", "aab", "b"]
for name in names {
let item = Item(context: context)
item.name = name
try! context.save()
}
}
// Buttons to modify the filter to update the fetch request
HStack {
Button("Add") {
filter = filter "a"
}
Button("Remove") {
filter = String(filter.prefix(filter.count-1 >= 0 ? filter.count-1 : 0))
}
Text("Filter: (filter)")
}
Text("Fetch count in parent view (not ok): (fetchCount)")
Child(filter: filter, fetchCount: $fetchCount) // <-- here
Spacer()
}
}
}
Комментарии:
1.
.onRecieve(fetchRequest.publisher)
кажется, это отличное решение, за исключением одного случая: когда фильтрFetchRequest
не возвращает результатов, счетчик не обновляется в родительском файле. Знаете ли вы, как заставить издателя опубликовать событие, даже если основные данные не дают результатов?2. попробуйте это:
.onReceive(fetchRequest.publisher.count()) {...}
. Я обновлю свой ответ, если он вас устроит.3. Это работает! Обновляя ваш ответ, мне не нужно было
import Combine
или использоватьBinding
входChild
для фильтра (так как дочернее представление не записывается в фильтр). Простое добавление.onRecieve
сделало свое дело.4. Обновил свой ответ. Если это ответ на ваш вопрос, пожалуйста, отметьте его как правильный галочкой, спасибо.