首页 > 解决方案 > Swift UI 列表未在 Cloudkit 同步时更新

问题描述

我正在尝试创建一个简单的应用程序,它利用 CloudKit 在多个 iOS 设备(通过 iCloud)和可能的 macOS 设备(仍然通过 iCloud)之间进行同步。

问题: 我有一个核心数据实体,它似乎在我的应用程序中本地运行得很好。当我切换到使用 Cloudkit 时,如果不关闭/重新打开应用程序,我将无法在另一台设备上看到更改。

我在 Xcode 中使用 Cloudkit 模板和 SwiftUI 生命周期。即,PersistenceController 和托管对象上下文。

认为这是一个不令人耳目一新的问题,但我不是 100% 确定。一旦不同设备上的应用程序关闭并重新打开,它就会成功加载新数据。这适用于添加和删除。

测试: 我已经使用 Testflight 以及在本地运行两个模拟器对此进行了测试。

代码:

持久性控制器.swift


import CoreData

struct PersistenceController {
    static var shared = PersistenceController()

    static var preview: PersistenceController = {
        let result = PersistenceController(inMemory: true)
        let viewContext = result.container.viewContext
        for i in 0..<10 {
            let newItem = Card(context: viewContext)
            newItem.cardValue = String(i)
            newItem.cardOrder = Int32(i)
        }
        do {
            try viewContext.save()
        } catch {
            // Replace this implementation with code to handle the error appropriately.
            // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
            let nsError = error as NSError
            fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
        }
        return result
    }()
    
    
    //Is this needed?
    lazy var updateContext: NSManagedObjectContext = {
        let _updateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
        _updateContext.parent = PersistenceController.shared.updateContext
        return _updateContext
    }()

    let container: NSPersistentCloudKitContainer

    init(inMemory: Bool = false) {
        container = NSPersistentCloudKitContainer(name: "Cards")
        if inMemory {
            container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
        }

        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })

        container.viewContext.automaticallyMergesChangesFromParent = true
        container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
    }
    
}

内容视图.swift


import SwiftUI
import CoreData

struct ContentView: View {
    @Environment(\.managedObjectContext) var managedObjectContext
    
    @FetchRequest(entity: Card.entity(),
                  sortDescriptors: [NSSortDescriptor(key: "cardOrder", ascending: true)])
    var cards: FetchedResults<Card>
    
    
    var body: some View {
        
        NavigationView {
            VStack {
                Text("Scrum +")
                    .font(.largeTitle)
                    .fontWeight(.regular)
                
                Spacer()
                List(cards, id: \.id)  {card in
                        NavigationLink(destination: PointRow(card: card)) {
                                PointRow(card: card)
                        }
                }
                
                Spacer()
                NavigationLink(destination: SettingsView().environment(\.managedObjectContext, self.managedObjectContext)
) {
                    Text("Card Settings")
                }
                .padding(.bottom)
                
            }
        }
    }
}

SettingsView.swift(保存/删除/重新排序发生的地方)


import SwiftUI

struct SettingsView: View {
    
    
    @State private var editMode = EditMode.inactive
    
    @State private var showModal = false
    
    @Environment(\.managedObjectContext) var managedObjectContext
    
    @FetchRequest(sortDescriptors: [NSSortDescriptor(key: "cardOrder", ascending: true)])
    var cards: FetchedResults<Card>
    
    
    var body: some View {
        VStack {
            Text("Card Values")
                .font(.largeTitle)
            HStack {
                EditButton()
                    .padding(.leading)
                Spacer()
                addButton
                    .padding(.trailing)
            }
            .padding(.vertical)
            List {
                ForEach(cards) {
                    item in
                    Text(item.cardValue!)
                }
                .onDelete(perform: onDelete)
                .onMove(perform: onMove)
                .environment(\.editMode, $editMode)
            }
            
            .sheet(isPresented: $showModal) {
                //Show the view to add a new card.
                SettingsModal(showModal: self.$showModal, numberOfCards: cards.count).environment(\.managedObjectContext, self.managedObjectContext)
            }
        }
    }
    
    
    private var addButton: some View {
        switch editMode {
        case .inactive:
            return AnyView(Button(action: onAdd) { Image(systemName: "plus") })
        default:
            return AnyView(EmptyView())
        }
    }
    
    
    
    func onAdd() {
        showModal = true
    }
    
    
    func onDelete(indexSet: IndexSet) {
        //        print("Deleting card at index -> " + indexSet.first)
        
        let cardToDelete = self.cards[indexSet.first!]
        self.managedObjectContext.delete(cardToDelete)
        
        do {
            try self.managedObjectContext.save()
        } catch {
            print(error)
        }
        
        reorder()
    }
    
    
    private func onMove(source: IndexSet, destination: Int) {
        
        //        pointCards.points.move(fromOffsets: source, toOffset: destination)
        
        //        source.
        
        let firstCard = self.cards[source.first!]
        
        //If there is a card located in the destination.
        if (destination < self.cards.count) {
            let secondCard = self.cards[destination]
            
            
            let tmp = Int(secondCard.cardOrder)
            
            //Increment all place holders from the destination on.
            for i in tmp..<self.cards.count {
                self.cards[i].cardOrder += 1
            }
        }
        
        firstCard.cardOrder = Int32(destination)
        
        do {
            try self.managedObjectContext.save()
        } catch {
            print(error)
        }
        
        self.managedObjectContext.refreshAllObjects()
        
        //Reorder just in case moved to the end.
        reorder()
    }
    
    
    private func reorder() {
        
        for i in 0..<cards.count {
            cards[i].cardOrder = Int32(i)
        }
        
        do {
            try self.managedObjectContext.save()
        } catch {
            print(error)
        }
    }
}

我使用 Cloudkit 的核心数据模型和权利选择的屏幕截图。 能力

CoreData 属性

更新: 我找到了解决方案。Persistent Controller 应该在 init 函数中添加以下三行:


        //Setup auto merge of Cloudkit data
        container.viewContext.automaticallyMergesChangesFromParent = true
        container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
        
        //Set the Query generation to .current. for dynamically updating views from Cloudkit
        try? container.viewContext.setQueryGenerationFrom(.current)

然后这必须在真实设备上运行。无论出于何种原因,我都无法在模拟器中使用它,但在真实设备上,它会在大约 30 秒内同步,而无需关闭应用程序。

感谢大家的帮助!

标签: iosswiftswiftuicloudkit

解决方案


推荐阅读