ios - 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 的核心数据模型和权利选择的屏幕截图。 能力
更新: 我找到了解决方案。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 秒内同步,而无需关闭应用程序。
感谢大家的帮助!
解决方案
推荐阅读
- javascript - WebPack Externals:如何为 UMD 库目标“找到”正则表达式选项的依赖项?
- angular - 启用或禁用 ngFor 迭代元素
- android - 如何在flutter中定义变量和常量?
- angular - 如何访问导入的 JSON 文件中的集合元素?
- node.js - 脚本标签可以获取 EJS 变量吗?
- javascript - 如何从 VueJS 通过 URL 下载文件并将其保存到变量
- python - 从联系信息列表中抓取电话号码
- adobe - 用户接受 cookie 后如何加载 Adobe Launch Tools?(没有页面重新加载)
- javascript - 为什么我不能在 React 中选择我的单选按钮?
- c# - 如何使用 C# 在 UWP 应用中保存对象列表?