SwiftUI和CloudKit异步数据加载

问题描述 投票:0回答:1

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

swiftui cloudkit
1个回答
0
投票
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) } }

© www.soinside.com 2019 - 2024. All rights reserved.