当我的应用程序主屏幕出现时,我正在使用CloudKit从iCloud中获取大量Tool
对象,并且正在通过过滤器运行这些对象。 Tool
数组存储在名为ToolViewModel
的toolVM
类中的ObservableObject
对象UserData
中。
这是我的ViewModel和我的View代码:
class ToolViewModel: InstrumentViewModel { @Published var tools = [Tool]() func filter(searchString: String, showFavoritesOnly: Bool) -> [Tool] { let list = super.filter(searchString: searchString, showFavoritesOnly: showFavoritesOnly) return list.map{ $0 as! Tool }.sorted() } func cleanUpCategories(from category: String) { super.cleanUpCategories(instruments: tools, from: category) } } struct ToolList: View { // @ObservedObject var toolVM = ToolViewModel() @ObservedObject var userData: UserData @State private var searchString = "" @State private var showCancelButton: Bool = false var filteredTools: [Tool] { userData.toolVM.filter(searchString: searchString, showFavoritesOnly: userData.showFavoritesOnly) } var body: some View { NavigationView { VStack { // Search view SearchView(searchString: $searchString, showCancelButton: $showCancelButton) .padding(.horizontal) // Tool list List { ForEach(filteredTools) { tool in NavigationLink(destination: ToolDetail(tool: tool, userData: self.userData)) { ToolRow(tool: tool) } } // ForEach ends .onDelete(perform: onDelete) } // List ends } // VStack ends .navigationBarTitle("Tools") } // NavigationView ends .onAppear() { if self.userData.toolVM.tools.isEmpty { // Tools (probably) haven't been loaded yet (or are really empty), so try it self.userData.updateTools() } } } ... }
这是ViewModel的超类(因为我还有另外两个类似的ViewModels,我对此进行了介绍:]]
class InstrumentViewModel: ObservableObject { @Published var categories = [InstrumentCategory]() @Published var instrumentCategories = [String: [Instrument]]() func filter(searchString: String, showFavoritesOnly: Bool) -> [Instrument] { var list = [Instrument]() for category in categories { if category.isSelected && instrumentCategories[category.name] != nil { if searchString == "" { list += showFavoritesOnly ? instrumentCategories[category.name]!.filter { $0.isFavorite } : instrumentCategories[category.name]! } else { list += showFavoritesOnly ? instrumentCategories[category.name]!.filter { $0.isFavorite && $0.contains(searchString: searchString) } : instrumentCategories[category.name]!.filter { $0.contains(searchString: searchString) } } } } return list } func setInstrumentCategories(instruments: [Instrument]) { var categoryStrings = Set<String>() for instrument in instruments { categoryStrings.insert(instrument.category) } for categoryString in categoryStrings.sorted() { categories.append(InstrumentCategory(name: categoryString)) } } }
这是我的UserData类:
final class UserData: ObservableObject { @Published var showFavoritesOnly = false @Published var toolVM = ToolViewModel() func updateTools() { DataHelper.loadFromCK(instrumentType: .tools) { (result) in switch result { case .success(let loadedInstruments): self.toolVM.tools = loadedInstruments as! [Tool] self.toolVM.setInstrumentCategories(instruments: loadedInstruments) self.toolVM.instrumentCategories = Dictionary(grouping: self.toolVM.tools, by: { $0.category }) debugPrint("Successfully loaded instruments of type Tools and initialized categories and category dictionary") case .failure(let error): debugPrint(error.localizedDescription) } } } }
也是最后但并非最不重要的,实际上是从iCloud加载数据的帮助器类:
struct DataHelper { static func loadFromCK(instrumentType: InstrumentCKDataTypes, completion: @escaping (Result<[Instrument], Error>) -> ()) { let predicate = NSPredicate(value: true) let query = CKQuery(recordType: instrumentType.rawValue, predicate: predicate) getCKRecords(instrumentType: instrumentType, forQuery: query, completion: completion) } private static func getCKRecords(instrumentType: InstrumentCKDataTypes, forQuery query: CKQuery, completion: @escaping (Result<[Instrument], Error>) -> ()) { CKContainer.default().publicCloudDatabase.perform(query, inZoneWith: CKRecordZone.default().zoneID) { results, error in if let error = error { DispatchQueue.main.async { completion(.failure(error)) } return } guard let results = results else { return } switch instrumentType { case .tools: DispatchQueue.main.async { completion(.success(results.compactMap { Tool(record: $0) })) } case .drivers: DispatchQueue.main.async { completion(.success(results.compactMap { Driver(record: $0) })) } case .adapters: DispatchQueue.main.async { completion(.success(results.compactMap { Adapter(record: $0) })) } } } } }
我遇到的问题如下:该视图将在从iCloud加载数据之前初始化。我使用空的
tools
数组初始化ViewModel中的Tool
变量。因此,显示视图时不会显示任何工具。尽管
tools
是@Published
变量,但在异步iCloud加载过程完成后,视图将不会重新加载。这是我所期望的行为。一旦我开始在搜索字段中输入一些搜索字符串,这些工具便会出现。这只是关于第一次加载。
也不起作用是在toolVM
初始化程序中初始化UserData
,因为我无法从异步加载闭包中访问它。
足够有趣:如果我将toolVM
变量作为@ObservedObject
移入视图本身(您可以在我的视图中看到,我在代码中注释了这一行),则视图[[will
toolVM
ViewModel,因此我将其存储在UserData类中。我认为它与异步加载有关。 当我的应用程序主屏幕出现时,我正在使用CloudKit从iCloud提取大量工具对象,并且正在通过过滤器运行这些对象。工具数组存储在ToolViewModel中...
ToolViewModel
参考未更改,因此没有任何内容在UserData
级别发布。这是可能的解决方案-强制有意发布:DataHelper.loadFromCK(instrumentType: .tools) { (result) in
switch result {
case .success(let loadedInstruments):
self.toolVM.tools = loadedInstruments as! [Tool]
self.toolVM.setInstrumentCategories(instruments: loadedInstruments)
self.toolVM.instrumentCategories = Dictionary(grouping: self.toolVM.tools, by: { $0.category })
debugPrint("Successfully loaded instruments of type Tools and initialized categories and category dictionary")
self.objectWillChange.send() // << this !!
case .failure(let error):
debugPrint(error.localizedDescription)
}
}