在SwiftUI中更改对象的子对象时刷新视图



我正在开发一个具有两个实体MyList和MyListItem的CoreData应用程序。MyList可以有多个MyListItem(一对多(。当应用程序启动时,我可以看到所有列表。我可以点击列表转到列表项目。在那个屏幕上,我点击一个按钮,将一个项目添加到所选列表中。添加项目之后,当我返回到所有列表屏幕时,我看不到计数中反映的项目数量。原因是MyListsView不会再次呈现,因为列表的数量没有改变。

完整的代码如下所示:

import SwiftUI
import CoreData
extension MyList {

static var all: NSFetchRequest<MyList> {
let request = MyList.fetchRequest()
request.sortDescriptors = []
return request
}
}
struct DetailView: View {

@Environment(.managedObjectContext) var viewContext
let myList: MyList

var body: some View {
VStack {
Text("Detail View")
Button("Add List Item") {

let myListP = viewContext.object(with: myList.objectID) as! MyList

let myListItem = MyListItem(context: viewContext)
myListItem.name = randomString()
myListItem.myList = myListP

try? viewContext.save()

}
}
}

func randomString(length: Int = 8) -> String {
let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
return String((0..<length).map{ _ in letters.randomElement()! })
}
}
class ViewModel: NSObject, ObservableObject {

@Published var myLists: [MyList] = []

private var fetchedResultsController: NSFetchedResultsController<MyList>
private(set) var context: NSManagedObjectContext

override init() {
self.context = CoreDataManager.shared.context

fetchedResultsController = NSFetchedResultsController(fetchRequest: MyList.all, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
super.init()
fetchedResultsController.delegate = self

do {
try fetchedResultsController.performFetch()
guard let myLists = fetchedResultsController.fetchedObjects else { return }
self.myLists = myLists

}  catch {
print(error)
}
}
}
extension ViewModel: NSFetchedResultsControllerDelegate {
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
guard let myLists = controller.fetchedObjects as? [MyList] else { return }
self.myLists = myLists
}
}
struct MyListsView: View {

let myLists: [MyList]

var body: some View {
List(myLists) { myList in
NavigationLink {
DetailView(myList: myList)
} label: {
HStack {
Text(myList.name ?? "")
Spacer()

Text("((myList.items ?? []).count)")
}
}
}
}
}
struct ContentView: View {

@StateObject private var vm = ViewModel()
@Environment(.managedObjectContext) var viewContext

var body: some View {
NavigationView {
VStack {

// when adding an item to the list the MyListView view is
// not re-rendered
MyListsView(myLists: vm.myLists)

Button("Change List") {

}
}
}
}

func randomString(length: Int = 8) -> String {
let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
return String((0..<length).map{ _ in letters.randomElement()! })
}
}

在ContentView内部有一个名为";我的列表视图";。添加项目时不会渲染该视图。从那以后,根据这种观点,没有任何变化,因为列表的数量仍然相同。

你是如何解决这个问题的?

更新:

如果我再添加一个像ListCellView这样的视图级别,会发生什么,如下所示:

struct MyListCellView: View {

@StateObject var vm: ListCellViewModel

init(vm: ListCellViewModel) {
_vm = StateObject(wrappedValue: vm)
}

var body: some View {
HStack {
Text(vm.name)
Spacer()

Text("((vm.items).count)")
}
}
}
@MainActor
class ListCellViewModel: ObservableObject {

let myList: MyList

init(myList: MyList) {
self.myList = myList
self.name = myList.name ?? ""
self.items = myList.items!.allObjects as! [MyListItem]
print(self.items.count)
}

@Published var name: String = ""
@Published var items: [MyListItem] = []
}
struct MyListsView: View {

@StateObject var vm: ViewModel

init(vm: ViewModel) {
_vm = StateObject(wrappedValue: vm)
}

var body: some View {
let _ = Self._printChanges()
List(vm.myLists) { myList in
NavigationLink {
DetailView(myList: myList)
} label: {
MyListCellView(vm: ListCellViewModel(myList: myList))
}
}
}
}

现在,计数再次没有更新。

您的ViewModelObserveableObject,但您没有在MyListsView中观察到它。初始化MyListsView时,您设置了一个let常量。当然不会更新。改为:

struct MyListsView: View {

@ObservedObject var vm: ViewModel
init(viewModel: ViewModel) {
self.vm = viewModel
}

var body: some View {
List(vm.myLists) { myList in
NavigationLink {
DetailView(myList: myList)
} label: {
HStack {
Text(myList.name ?? "")
Spacer()

Text("((myList.items ?? []).count)")
}
}
}
}
}

现在,ViewModel中的@Published将导致MyListView在发生更改时发生更改,这包括添加相关实体。

我们在SwiftUI中不需要MVVM,View数据结构已经填补了这个角色,属性包装器使它们表现得像是两全其美的对象。在您的情况下,对列表使用@FetchRequest属性包装器,对细节使用@ObservedObject属性包装器。对模型数据的任何更改都将调用主体。检查Xcode中应用程序模板中的代码,并检查核心数据。它看起来像这样:

struct ContentView: View {
@Environment(.managedObjectContext) private var viewContext
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: Item.timestamp, ascending: true)],
animation: .default)
private var items: FetchedResults<Item>

var body: some View {
NavigationView {

List {
ForEach(items) { item in
NavigationLink {
DetailView(item: item)
} label: {
Text(item.timestamp!, formatter: itemFormatter)
}
}
.onDelete(perform: deleteItems)
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
EditButton()
}
ToolbarItem {
Button(action: addItem) {
Label("Add Item", systemImage: "plus")
}
}
}
Text("Select an item")
}
}
...
struct DetailView: View {
@ObservedObject var item: Item

var body: some View {
Text("Item at (item.timestamp!, formatter: itemFormatter)")

最新更新