当我的@Published NSManagedObjects 数组更改时,我的 SwiftUI 列表不会更新

Posted

技术标签:

【中文标题】当我的@Published NSManagedObjects 数组更改时,我的 SwiftUI 列表不会更新【英文标题】:My SwiftUI List won't update when my @Published array of NSManagedObjects changes 【发布时间】:2019-09-04 18:33:52 【问题描述】:

我正在构建一个基本的笔记应用程序,我的应用程序的主页应该显示用户笔记的列表。 Note 用 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 

【问题讨论】:

虽然未被接受,但我在这里的回答有 4 个赞成票。看看这是否有帮助:***.com/questions/57511826/… 这似乎有效,谢谢@dfd!我不完全理解它为什么会起作用,以及当一个已经存在的情况下如何在没有override 关键字的情况下声明一个objectWillChange。哦,好吧,很快就会学到一些东西! 【参考方案1】:

在@dfd 的帮助下(请参阅here),我能够通过将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]
  



【讨论】:

似乎不再需要调用objectWillChange.send() 来向数组中添加新项目,但是更新现有项目呢?唯一有效的技巧是将Listid 属性更新为新的UUID,这样每次视图出现时都会随着新数据的变化而刷新,但每次刷新有点多。 遇到同样的问题。更新 Core Data 中的项目不会更新 UI(即使使用 objectWillChange.send() 函数。有解决方法吗?【参考方案2】:

使用 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)View 中创建新笔记,例如在按钮处理程序中,不需要那个帮助类。

【讨论】:

以上是关于当我的@Published NSManagedObjects 数组更改时,我的 SwiftUI 列表不会更新的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI,@Published 和 ForEach 没有更新我的视图

如何定义协议以包含带有 @Published 属性包装器的属性

如何将我的 SwiftUI 视图的 @State 交换为我的视图模型 @Published 变量?

Swift Combine - @Published 属性数组

如何在 ViewModel 中使用带有自定义类型和 @Published 的 @AppStorage?

@Published 用于计算属性(或最佳解决方法)