我目前正在尝试清理我的 Core Data/SwiftUI 代码,其中一部分是将代码从我的视图移出并移入我的 ViewModel。由于
@FetchRequest
似乎在虚拟机中不起作用,我尝试创建类似的在虚拟机中工作的东西(基本上是 NSFetchedResultsController
的包装器):
class FetchedData<ResultType: NSManagedObject>: NSFetchedResultsController<NSFetchRequestResult>, NSFetchedResultsControllerDelegate, ObservableObject {
@Published var value: [ResultType] = []
convenience init(entity: ResultType.Type) {
let request = NSFetchRequest<NSFetchRequestResult>()
request.entity = ResultType.entity()
request.sortDescriptors = []
super.init(
fetchRequest: request,
managedObjectContext: PersistenceController.shared.context,
sectionNameKeyPath: nil,
cacheName: nil
)
self.delegate = self
do {
try self.performFetch()
value = (self.fetchedObjects as? [ResultType]) ?? []
} catch {
print("Could not perform fetch")
}
}
internal func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
guard let newValue = controller.fetchedObjects as? [ResultType] else {
return
}
value = newValue
}
}
到目前为止一切顺利,我可以按如下方式使用它:
struct ContentView: View {
class ViewModel: ObservableObject {
private var cancellables: [AnyCancellable] = []
var data = FetchedData(entity: Element.self)
init() {
data.$value.sink { _ in
self.objectWillChange.send()
}
.store(in: &cancellables)
}
}
@StateObject var viewModel = ViewModel()
var body: some View {
List {
ForEach(viewModel.data.value, id: \.self) { element in
// show UI for element
}
}
}
}
现在我想稍微简化一下它的用法,并将
FetchedData
制作为一个集合,这样我就可以直接使用它,而不必使用 value
。我添加了这个:
extension FetchedData: RandomAccessCollection {
typealias Element = ResultType
typealias Index = Int
typealias Indices = Range<Int>
typealias Iterator = IndexingIterator<[Element]>
subscript(position: Int) -> ResultType {
return value[position]
}
var startIndex: Int {
return value.startIndex
}
var endIndex: Int {
return value.endIndex
}
__consuming func makeIterator() -> IndexingIterator<[Element]> {
return value.makeIterator()
}
}
然后将我的视图更改为:
struct ContentView: View {
class ViewModel: ObservableObject {
let data = LiveData(entity: Element.self)
}
@StateObject var viewModel = ViewModel()
var body: some View {
Group {
List {
ForEach(viewModel.data, id: \.self) { element in
// show UI for element
}
}
}
}
}
它...有点管用。它将显示并更新我的数据,我可以添加
Element
,视图就会更新得很好。当我尝试使用以下方法删除数据时出现问题:
.onDelete { indexSet in
indexSet.forEach { index in
let element = viewModel.data[index]
viewContext.delete(element)
}
try? viewContext.save()
}
我的应用程序将崩溃,并在扩展程序的
Fatal error: Index out of range
方法中出现 subscript
异常。
例如,如果我的集合中有 5 个元素并删除其中一个,首先会发生删除,然后 SwiftUI 显然会从
viewModel.data
查询索引 4(该元素已不存在,因为只剩下 4 个元素)。奇怪的是,SwiftUI 似乎在此之前查询了 endIndex
,它返回了正确的值,所以我不知道为什么它会查询它知道超出范围的东西。另外,似乎出于某种原因,删除最后一个元素似乎效果很好。
我真的无法理解这一点。因为它使用
viewModel.data.values
而不是仅仅 viewModel.data
来工作,我想我的 extension FetchedData: RandomAccessCollection
有问题,但我不明白这里的问题。
如有任何帮助,我们将不胜感激!
不确定您是否仍然遇到这个问题,但我遇到了同样的事情。我解决这个问题的唯一方法是在 ForEach 中使用索引
ForEach(viewModel.data.indices, id: \.self) { idx in
// show UI for element
// using viewModel.data[idx] e.g.
Text(viewModel.data[idx].description)
}
可悲的是,我无法解释为什么这有效,但依赖底层集合却行不通。
我也有同样的问题。我无法发表评论,因为我的声誉太低。我已经尝试了一切。我相当确定这是 SwiftUI 框架中的一个错误。