我的SwiftUI列表在@Published管理对象数组发生变化时不会更新

3
我正在构建一个基本笔记应用程序,其中应用程序的主页应显示用户笔记的列表。一个笔记使用Note类表示,这是由Core Data生成的类。(我的终极目标是一个通过NSPersistentCloudKitContainer与CloudKit同步的笔记应用程序。)
到目前为止,当用户加载应用程序时,列表显示正确的笔记数据。然而,当我尝试通过点击newNoteButton来创建新笔记时,笔记数组会改变,但我的UI不会改变。我必须重新加载应用程序才能看到新笔记。我可能做错了什么?以下是代码: NoteList.swift
struct NoteList: View {

  @EnvironmentObject var userNotes: UserNotes

  var newNoteButton: some View {
    Button(action: {
      self.userNotes.createNewNote()
      self.userNotes.objectWillChange.send()
    }) {
      Image(systemName: "plus")
        .imageScale(.large)
        .accessibility(label: Text("New Note"))
    }
  }

  var body: some View {
    NavigationView {
      List {
        ForEach(self.userNotes.notes) { note in
          NavigationLink(destination: NoteDetail(note: self.$userNotes.notes[self.userNotes.notes.firstIndex(of: note)!])) {
            Text(note.unsecuredContent!)
          }
        }
      }
      .navigationBarTitle(Text("Notes"), displayMode: .inline)
      .navigationBarItems(trailing: newNoteButton)
    }
  }

}

UserNotes.swift

class UserNotes: NSObject, ObservableObject {

  @Published var notes: [Note] = []

  var managedObjectContext: NSManagedObjectContext? = nil

  var fetchedResultsController: NSFetchedResultsController<Note> {
    if _fetchedResultsController != nil {
      return _fetchedResultsController!
    }

    let fetchRequest: NSFetchRequest<Note> = Note.fetchRequest()

    // Set the batch size to a suitable number.
    fetchRequest.fetchBatchSize = 20

    // Edit the sort key as appropriate.
    let sortDescriptor = NSSortDescriptor(key: "unsecuredContent", ascending: false)

    fetchRequest.sortDescriptors = [sortDescriptor]

    // Edit the section name key path and cache name if appropriate.
    // nil for section name key path means "no sections".
    let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest,
                                                               managedObjectContext: self.managedObjectContext!,
                                                               sectionNameKeyPath: nil, cacheName: "Master")
    aFetchedResultsController.delegate = self
    _fetchedResultsController = aFetchedResultsController

    do {
      try _fetchedResultsController!.performFetch()
    } catch {
      // Replace this implementation with code to handle the error appropriately.
      // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
      let nserror = error as NSError
      fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
    }

    return _fetchedResultsController!
  }
  var _fetchedResultsController: NSFetchedResultsController<Note>? = nil

  override init() {
    super.init()
    managedObjectContext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
    notes = fetchedResultsController.sections![0].objects as! [Note]
  }

  func createNewNote() {
    let newNote = Note(context: managedObjectContext!)

    // If appropriate, configure the new managed object.
    newNote.unsecuredContent = "New CloudKit note"

    // Save the context.
    do {
      try managedObjectContext!.save()
    } catch {
      // Replace this implementation with code to handle the error appropriately.
      // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
      let nserror = error as NSError
      fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
    }
  }

}

extension UserNotes: NSFetchedResultsControllerDelegate {

  func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
    notes = controller.sections![0].objects as! [Note]
  }

}

Note.swift(由Core Data生成)

//  This file was automatically generated and should not be edited.
//

import Foundation
import CoreData

@objc(Note)
public class Note: NSManagedObject {

}

Note.swift(扩展) :

extension Note: Identifiable {}

1
虽然我的回答没有被采纳,但它已经获得了4个赞。看看这是否有帮助:https://dev59.com/XbXna4cB1Zd3GeqPP7xP#57513103 - user7014451
似乎可以工作,谢谢@dfd!但我不完全明白它为什么能工作,以及如何在已有一个objectWillChange的情况下不使用override关键字声明。不过没关系,这是一个需要学习的东西! - Eugene
2个回答

0

在 @dfd 的帮助下(请参见此处),我能够通过将 Combine 导入到我的 UserNotes 类中,添加 objectWillChange 并调用 objectWillChange.send() 来解决此问题:

import Foundation
import UIKit
import CoreData
import Combine

class UserNotes: NSObject, ObservableObject {

  var objectWillChange = PassthroughSubject<Void, Never>()

  @Published var notes: [Note] = [] {
    willSet {
      objectWillChange.send()
    }
  }

  var managedObjectContext: NSManagedObjectContext? = nil

  var fetchedResultsController: NSFetchedResultsController<Note> {
    if _fetchedResultsController != nil {
      return _fetchedResultsController!
    }

    let fetchRequest: NSFetchRequest<Note> = Note.fetchRequest()

    // Set the batch size to a suitable number.
    fetchRequest.fetchBatchSize = 20

    // Edit the sort key as appropriate.
    let sortDescriptor = NSSortDescriptor(key: "unsecuredContent", ascending: false)

    fetchRequest.sortDescriptors = [sortDescriptor]

    // Edit the section name key path and cache name if appropriate.
    // nil for section name key path means "no sections".
    let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest,
                                                               managedObjectContext: self.managedObjectContext!,
                                                               sectionNameKeyPath: nil, cacheName: "Master")
    aFetchedResultsController.delegate = self
    _fetchedResultsController = aFetchedResultsController

    do {
      try _fetchedResultsController!.performFetch()
    } catch {
      // Replace this implementation with code to handle the error appropriately.
      // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
      let nserror = error as NSError
      fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
    }

    return _fetchedResultsController!
  }
  var _fetchedResultsController: NSFetchedResultsController<Note>? = nil

  override init() {
    super.init()
    managedObjectContext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
    notes = fetchedResultsController.sections![0].objects as! [Note]
  }

  func createNewNote() {
    let newNote = Note(context: managedObjectContext!)

    // If appropriate, configure the new managed object.
    newNote.unsecuredContent = UUID().uuidString // Just some random crap

    // Save the context.
    do {
      try managedObjectContext!.save()
    } catch {
      // Replace this implementation with code to handle the error appropriately.
      // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
      let nserror = error as NSError
      fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
    }
  }

}

extension UserNotes: NSFetchedResultsControllerDelegate {

  func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
    notes = controller.sections![0].objects as! [Note]
  }

}


1
似乎调用 objectWillChange.send() 已不再必要来向数组中添加新项,但是更新现有项呢?唯一有效的方法是将 Listid 属性更新为新的 UUID,这样它就会在每次视图出现时刷新以显示新的数据更改,但每次都刷新有点过度了。 - Ever Uribe
遇到了同样的问题。在 Core Data 中更新项目不会更新 UI(即使使用 objectWillChange.send() 函数)。有什么解决方法吗? - cocos2dbeginner
你是如何解决这个问题的? - RPSM

0

使用SwiftUI时,获取结果控制器的位置在View中,如下所示:

@Environment(\.managedObjectContext) var moc
@FetchRequest(entity: Note.entity(), sortDescriptors: []) var notes: FetchedResults<Note>

var body: some View {
    VStack{
        List{
            ForEach(notes, id: \.self) { note in
            ...
            }
        }
    }
}

您还可以执行 Note(self.moc) 来在视图中创建新的笔记,例如在按钮处理程序中,而不需要那个辅助类。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接