【问题标题】:SwiftUI and CloudKit asynchronous data loadingSwiftUI 和 CloudKit 异步数据加载
【发布时间】:2020-06-12 22:27:23
【问题描述】:

当我的应用程序的主屏幕出现时,我正在使用 CloudKit 从 iCloud 获取大量 Tool 对象,并且我正在通过过滤器运行这些对象。 Tool 数组存储在名为 UserDataObservableObject 类中的 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 类中。

我认为它与异步加载有关。

【问题讨论】:

    标签: swiftui cloudkit


    【解决方案1】:

    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)
        }
    }
    

    -

    替代解决方案是将ToolList 视图的ToolViewModel 依赖部分分离到具有观察到的ToolViewModel 的专用较小子视图中,并从userData 传递引用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-11-03
      • 1970-01-01
      • 2011-06-24
      • 2021-01-03
      • 2020-11-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多