SwiftUI: Core Data @FetchRequest и List, отображающие управляемые объекты - потеря данных при блокировке
У меня проблема с потерей данных в управляемых объектах Core Data (все свойства, несмотря на.objectID) становятся нулевыми после перехода на экран блокировки и обратно.
Я отлаживал это и обнаружил такую проблему.
struct SimpleContactsList: View {
@FetchRequest var contacts: FetchedResults<Contact>
let companyId: String
init(companyId: String) {
self.companyId = companyId
self._contacts = FetchRequest(
entity: Contact.entity(),
sortDescriptors: [
NSSortDescriptor(key: "name", ascending: true, selector: #selector(NSString.caseInsensitiveCompare(_:))),
NSSortDescriptor(keyPath: \Contact.createdAt, ascending: true)
],
predicate: NSPredicate(format: "company.id == %@", companyId)
)
}
var body: some View {
List {
ForEach(Array(self.contacts.enumerated()), id: \.1.objectID) { (i, contact) in
ContactRow(contact: contact)
}
}
}
}
Приведенный выше упрощает код, он работает нормально. Вы можете заблокировать экран и вернуться, и строки будут перезагружены с помощью управляемых объектов с ошибочными основными данными.
Но простое добавление ZStack, VStack вокруг ContactRow(contact: contact) нарушает это, и переход на экран блокировки, а затем снова приводит к перезагрузке этого списка с пустыми управляемыми объектами (со свойствами nil), а список пуст (если я использую необязательное развертывание contact.name?? "") или просто вылетает приложение, если я использую перед развертыванием o как contact.name!
Так что это ломается
Изменение моих SimpleContactLists на это нарушение кода, например добавление ZStack, предотвращает обновление элементов списка из базы данных?
List {
ForEach(Array(self.contacts.enumerated()), id: \.1.objectID) { (i, contact) in
ZStack {
ContactRow(contact: contact)
}
.listRowBackground( (i%2 == 0) ? Color("DarkRowBackground") : .white)
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
}
.onDelete(perform: self.delete)
}
ОБНОВИТЬ
contact NSManagedObject пуст, но ссылки на self.contacts[i] НЕТ! Зачем?
List {
ForEach(Array(self.contacts.enumerated()), id: \.1.objectID) { (i, contact) in
Button(action: {
self.selectedId = self.contacts[i].id!
self.showDetails = true
}) {
Contact(contact: contact)
//ContactRow(contact: self.contacts[i])
.background(Color.white.opacity(0.01))
}
.buttonStyle(ListButtonStyle())
.listRowBackground( (i%2 == 0) ? Color("DarkRowBackground") : .white)
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
}
.onDelete(perform: self.delete)
}
ОБНОВЛЕНИЕ 2
У меня есть такое решение. Это помогает в некоторых случаях List-from-FetchResults, но не является желательным решением в более сложных примерах, таких как List, заполненный управляемыми объектами в.sheet() (поэтому здесь мне нужно прекратить представление такого листа)
@State var theId = UUID()
List {
//rows
}
.id(theId)
.onReceive(appBecomeActivePublisher, perform: { _ in
self.theId = UUID()
}
extension UIApplication {
static var didBecomeActivePublisher: AnyPublisher<UIKit.Notification, Never> {
NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)
.eraseToAnyPublisher()
}
}
1 ответ
Просто предположение, но, возможно, потому, что вы используете enumerated()
в вашем ForEach
. Если вас интересует индекс, попробуйте вместо этого использовать это:
ForEach(0..<contacts.count, id: \.self) { i in
// rest appears to be the same
}
Это будет фактически обновляться сейчас, когда ваши полученные результаты обновятся.
Просто чтобы немного расширить это, но FetchRequest
и поэтому FetchedResults
являются update()
известно, они предназначены для обновления быстрого пользовательского интерфейса View
с body
. enumerated()
нет, это просто сборник. Итак, когда это изменится, вашbody
все равно, он не подписался на его изменения. когдаFetchedResults
изменения, ваш body
делает уход, он будет подписан, и поэтому он обновляет себя и повторно выводит его содержимое.