【发布时间】:2020-06-12 22:27:23
【问题描述】:
当我的应用程序的主屏幕出现时,我正在使用 CloudKit 从 iCloud 获取大量 Tool 对象,并且我正在通过过滤器运行这些对象。 Tool 数组存储在名为 UserData 的 ObservableObject 类中的 ToolViewModel 对象 toolVM 中。
这是我的 ViewModel 和我的视图代码:
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 的超类(因为我还有两个类似的 ViewModel,所以我介绍了这个):
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 加载数据之前初始化。我用空的Tool 数组初始化 ViewModel 中的tools 变量。因此,视图出现时不会显示任何工具。
虽然tools 是一个@Published 变量,但在异步iCloud 加载过程完成后视图不会重新加载。这是我所期望的行为。一旦我开始在搜索字段中输入一些搜索字符串,工具就会出现。这只是关于第一次加载。
在 UserData 初始化器中初始化 toolVM 也不起作用,因为我无法从异步加载闭包中访问它。
有趣的是:如果我将toolVM 变量作为@ObservedObject 移动到视图本身(您可以在我的视图中看到它,我在代码中注释掉了这一行),视图将 数据加载完成后重新加载。不幸的是,这不是我的选择,因为我需要在应用程序的其他部分访问 toolVM ViewModel,因此我将它存储在我的 UserData 类中。
我认为它与异步加载有关。
【问题讨论】: