当我的@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()
来向数组中添加新项目,但是更新现有项目呢?唯一有效的技巧是将List
的id
属性更新为新的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 属性数组