数据持久化方案解析(二十一) —— SwiftUI App中Core Data和CloudKit之间的数据共享(一)

版本记录

版本号 时间
V1.0 2022.05.30 星期一

前言

数据的持久化存储是移动端不可避免的一个问题,很多时候的业务逻辑都需要我们进行本地化存储解决和完成,我们可以采用很多持久化存储方案,比如说plist文件(属性列表)、preference(偏好设置)、NSKeyedArchiver(归档)、SQLite 3CoreData,这里基本上我们都用过。这几种方案各有优缺点,其中,CoreData是苹果极力推荐我们使用的一种方式,我已经将它分离出去一个专题进行说明讲解。这个专题主要就是针对另外几种数据持久化存储方案而设立。
1. 数据持久化方案解析(一) —— 一个简单的基于SQLite持久化方案示例(一)
2. 数据持久化方案解析(二) —— 一个简单的基于SQLite持久化方案示例(二)
3. 数据持久化方案解析(三) —— 基于NSCoding的持久化存储(一)
4. 数据持久化方案解析(四) —— 基于NSCoding的持久化存储(二)
5. 数据持久化方案解析(五) —— 基于Realm的持久化存储(一)
6. 数据持久化方案解析(六) —— 基于Realm的持久化存储(二)
7. 数据持久化方案解析(七) —— 基于Realm的持久化存储(三)
8. 数据持久化方案解析(八) —— UIDocument的数据存储(一)
9. 数据持久化方案解析(九) —— UIDocument的数据存储(二)
10. 数据持久化方案解析(十) —— UIDocument的数据存储(三)
11. 数据持久化方案解析(十一) —— 基于Core Data 和 SwiftUI的数据存储示例(一)
12. 数据持久化方案解析(十二) —— 基于Core Data 和 SwiftUI的数据存储示例(二)
13. 数据持久化方案解析(十三) —— 基于Unit Testing的Core Data测试(一)
14. 数据持久化方案解析(十四) —— 基于Unit Testing的Core Data测试(二)
15. 数据持久化方案解析(十五) —— 基于Realm和SwiftUI的数据持久化简单示例(一)
16. 数据持久化方案解析(十六) —— 基于Realm和SwiftUI的数据持久化简单示例(二)
17. 数据持久化方案解析(十七) —— 基于NSPersistentCloudKitContainer的Core Data和CloudKit的集成示例(一)
18. 数据持久化方案解析(十八) —— 基于NSPersistentCloudKitContainer的Core Data和CloudKit的集成示例(二)
19. 数据持久化方案解析(十九) —— 基于批插入和存储历史等高效CoreData使用示例(一)
20. 数据持久化方案解析(二十) —— 基于批插入和存储历史等高效CoreData使用示例(二)

开始

首先看下主要内容:

本文主要学习了SwiftUI AppCore DataCloudKit之间的数据共享,内容来自翻译

接着看下写作环境

Swift 5.5, iOS 15, Xcode 13

下面就是正文了。

2019 年全球开发者大会(WWDC)上,Apple 推出了只需几个步骤即可将 CloudKit 功能添加到由 Core-Data 支持的应用程序的功能。理论上,您只需要三个步骤。首先,将您的容器类型更新为 NSPersistentCloudKitContainer。其次,在您的应用中启用 iCloud 功能。第三,创建一个 iCloud 容器,在 iCloud 中托管您的数据。

完成这些步骤后,应用程序中的数据就会“自动”与 iCloud 同步。很好吧!但是,这样做的一个限制是您无法轻松地与其他人共享您的数据以做出贡献。在 WWDC 2021 上,Apple 推出了一种让您与其他 iCloud 用户共享数据并邀请他们为您的应用程序数据做出贡献的方法。

在本教程中,您将探索如何更新现有的 Core DataCloudKit 应用程序以共享数据并邀请用户为您的应用程序中的数据做出贡献。

注意:您需要满足以下先决条件才能完成本教程。

  • 付费开发者帐户——使用 CloudKit
  • 两个独立的 iCloud 帐户 — 启动共享过程。
  • 至少一台真实设备——发送和接受分享邀请。另外,更改共享权限,因为它在模拟器上无法正常工作。

打开启动项目。 当您检查起始代码时,您会发现起始项目是一个基本的 Core Data 应用程序,具有 CRUD(创建、读取、更新、删除)功能。

在项目的设置中,将签名团队设置为您的付费开发者帐户。

构建并运行。 您会看到一个带有空白状态消息和一个按钮的旅行日志屏幕。

点击Add Destination按钮。 这会将您带到一个屏幕以添加您最近访问的目的地。 在此屏幕上,您可以提供目的地的标题、描述和照片。

添加目的地后,您将在主屏幕上看到它,如下所示。

要删除目的地,请向左滑动以显示删除按钮。点击此按钮会从您的旅行日志中删除一个目的地。

要编辑或更新标题或说明,请点击目标单元格以查看详细信息屏幕。在此屏幕中,您可以执行一些操作。

右上角是编辑按钮,它提供了一个模式来编辑目的地的标题和描述。还有一个 share action 现在什么都不做。您将在本教程中构建此操作。


Types of CloudKit Databases

在开始启用 CloudKit 同步之前,您首先需要了解可以存储数据的三种类型的数据库。

  • Public:这里存储的数据是公开的,每个用户,无论他们是否登录 iCloud 帐户,都可以读取它。对于本教程,您将在此数据库中存储数据。
  • Private:此处存储的数据是与当前登录用户关联的私有数据。
  • Shared:此处存储的数据在其他登录用户的私有数据库之间共享。当您稍后开始共享过程时,如果其他用户与您共享记录,您将开始看到此处填充的数据。

Enabling CloudKit Syncing

准备共享数据的下一步是启用将数据存储在 iCloud 中。当您创建目的地时,数据将通过 Core Data 在您的应用程序中本地保存。

Signing & Capabilities 部分,添加 iCloud 功能。您需要确保此时已设置唯一的bundle identifier。然后,选中 CloudKit 复选框以启用该功能。

下一步是创建数据所在的容器。在 iCloud 部分,点击容器下方的 + 按钮以添加自定义容器。 在出现的窗口中,输入容器的名称。 一般准则是使用 com.company_name.bundle_identifier。 Xcode 在容器名称前加上 iCloud

最后一步是添加Background Modes功能并启用Remote Notifications。 这允许 CloudKitiCloud 中的数据发生更改并且您的设备需要更新以反映此更改时向您的设备发送静默推送通知。

现在您已经配置了 CloudKit,请在您将要测试的设备上登录您的 iCloud 帐户。

启动模拟器。 在主屏幕上,打开设置。 登录您的 Apple ID

构建并运行。 添加目的地。

最后,前往 CloudKit Console,以便您可以验证您的数据。

1. CloudKit Console Dashboard

通过 CloudKit 存储数据时,CloudKit 控制台允许您与相关数据进行交互并执行其他一些功能,例如查看日志。 登录控制台后,打开 CloudKit Database

进入此部分后,您需要指定要查看的容器。 在屏幕顶部,选择下拉菜单并单击您之前从 Xcode 创建的容器container

在数据部分下方,单击Records。 选择Private Database。 这是数据写入的默认数据库。

如果您尝试将记录类型选择为 CD_Destination 并从此处查询记录,您会收到一条错误消息,Field recordName isn’t marked queryable。 现在,您将解决此错误。

Schema部分下,选择Indexes。 选择 CD_Destination。 这是您在Core Data中的Destination entityCloudKit 为您的实体添加 CD 前缀,以将它们与传统的 CloudKit 记录区分开来。

单击Add Basic Index。 从列表中选择 recordName 并确保索引类型为 Queryable。 保存更改。

现在您已使您的记录可查询,请单击数据部分下的Records。 选择Private Database。 将记录类型指定为 CD_Destination。 将所选区域从 defaultZone 更新为自动生成的 com.apple.coredata.cloudkit.zone

单击Query Records以查看您之前在应用程序中创建的记录的列表! 最令人惊奇的是,由于您的数据现在已在 iCloud 中同步,您可以在登录到同一个 iCloud 帐户的完全不同的设备上运行您的应用程序并查看您的所有数据!


Updating NSPersistentCloudKitContainer to Prepare for Share

此时,您的应用程序可以将您的更改本地保存在设备上,同时还可以将它们与 iCloud 中的私有数据库同步。 但是,为了允许其他用户与这些数据进行交互,您需要更新您的 NSPersistentCloudKitContainer。 打开 CoreDataStack.swift。 该类包含与 Core Data 交互所需的所有必要方法和属性。 要开始共享过程,请将以下代码添加到您的 persistentContainer// TODO: 1 注释下方:

let sharedStoreURL = storesURL?.appendingPathComponent("shared.sqlite")
guard let sharedStoreDescription = privateStoreDescription
  .copy() as? NSPersistentStoreDescription else {
  fatalError(
    "Copying the private store description returned an unexpected value."
  )
}
sharedStoreDescription.url = sharedStoreURL

此代码将共享数据库配置为存储与您共享的记录。 为此,您复制您的 privateStoreDescription 并将其 URL 更新为 sharedStoreURL

接下来,在 // TODO: 2 注释下添加以下代码:

guard let containerIdentifier = privateStoreDescription
  .cloudKitContainerOptions?.containerIdentifier else {
  fatalError("Unable to get containerIdentifier")
}
let sharedStoreOptions = NSPersistentCloudKitContainerOptions(
  containerIdentifier: containerIdentifier
)
sharedStoreOptions.databaseScope = .shared
sharedStoreDescription.cloudKitContainerOptions = sharedStoreOptions

此代码使用您的私有商店描述中的标识符创建 NSPersistentContainerCloudKitContainerOptions。 除此之外,您将 databaseScope 设置为 .shared。 最后一步是为您创建的 sharedStoreDescription 设置 cloudKitContainerOptions 属性。

接下来,在 // TODO: 3 注释下方添加以下代码:

container.persistentStoreDescriptions.append(sharedStoreDescription)

此代码将您共享的 NSPersistentStoreDescription 添加到容器中。

最后并在 // TODO: 4 下,替换:

container.loadPersistentStores { _, error in
  if let error = error as NSError? {
    fatalError("Failed to load persistent stores: \(error)")
  }
}

container.loadPersistentStores { loadedStoreDescription, error in
  if let error = error as NSError? {
    fatalError("Failed to load persistent stores: \(error)")
  } else if let cloudKitContainerOptions = loadedStoreDescription
    .cloudKitContainerOptions {
    guard let loadedStoreDescritionURL = loadedStoreDescription.url else {
      return
    }
    if cloudKitContainerOptions.databaseScope == .private {
      let privateStore = container.persistentStoreCoordinator
        .persistentStore(for: loadedStoreDescritionURL)
      self._privatePersistentStore = privateStore
    } else if cloudKitContainerOptions.databaseScope == .shared {
      let sharedStore = container.persistentStoreCoordinator
        .persistentStore(for: loadedStoreDescritionURL)
      self._sharedPersistentStore = sharedStore
    }
  }
}

上面的代码在加载时存储了对每个存储的引用。它检查 databaseScope 并确定它是private还是shared。然后,它根据范围设置persistent store


Presenting UICloudSharingController

UICloudSharingController 是一个视图控制器,它显示用于从 CloudKit 共享记录中添加和删除人员的标准屏幕。此控制器邀请其他用户为应用程序中的数据做出贡献。只有一个问题:这个控制器是一个 UIKit 控制器,而你的应用是 SwiftUI

解决方案在 CloudSharingController.swift 中。 CloudSharingView 符合协议 UIViewControllerRepresentable 并包装了 UIKit UICloudSharingController 以便您可以在 SwiftUI 中使用它。 CloudSharingView 具有三个属性:

  • CKShare:您用于共享的记录类型。
  • CKContainer:存储您的私有、共享或公共数据库的容器。
  • Destination:包含您正在共享的数据的实体。

makeUIViewController(context:) 中,会发生以下操作。它:

  • 1) 配置共享的标题。当 UICloudSharingController 呈现给用户时,他们必须有一些共享数据的上下文。在这种情况下,您使用目的地的标题。
  • 2) 使用sharecontainer属性创建 UICloudSharingController。演示样式设置为 .formSheet,代理使用 CloudSharingCoordinator 设置。这遵循UICloudSharingControllerDelegate。此代理包含在共享发生某些操作时通知您的便捷方法,例如错误和共享状态。现在您已经了解 CloudSharingView 的工作原理,是时候将它连接到您的共享按钮了。

现在,打开 DestinationDetailView.swift。此视图包含共享按钮的逻辑。第一步是创建一个准备共享数据的方法。为此,iOS 15 引入了 share(_:to:)。将以下代码添加到扩展块:

private func createShare(_ destination: Destination) async {
  do {
    let (_, share, _) = 
    try await stack.persistentContainer.share([destination], to: nil)
    share[CKShare.SystemFieldKey.title] = destination.caption
    self.share = share
  } catch {
    print("Failed to create share")
  }
}

上面的代码调用 share(_:to:) 的异步版本来共享您选择的目的地。 如果没有错误,则设置共享的标题。 从这里,您存储对从 share 方法返回的 CKShare 的引用。 当您展示 CloudSharingView 时,您将使用默认共享。

现在您有了执行共享的方法,您需要在点击共享按钮时显示 CloudSharingView。 在您这样做之前,请考虑一个小警告:只有尚未共享的对象才调用 share(_:to:)。 要检查这一点,请添加一些代码来确定相关对象是否已共享。

回到 CoreDataStack.swift,添加以下扩展:

extension CoreDataStack {
  private func isShared(objectID: NSManagedObjectID) -> Bool {
    var isShared = false
    if let persistentStore = objectID.persistentStore {
      if persistentStore == sharedPersistentStore {
        isShared = true
      } else {
        let container = persistentContainer
        do {
          let shares = try container.fetchShares(matching: [objectID])
          if shares.first != nil {
            isShared = true
          }
        } catch {
          print("Failed to fetch share for \(objectID): \(error)")
        }
      }
    }
    return isShared
  }
}

此扩展包含与共享相关的代码。 该方法检查传入的 NSManagedObjectIDpersistentStore,看它是否是 sharedPersistentStore。 如果是,则该对象已被共享。 否则,请使用 fetchShares(matching:)查看您是否有与相关 objectID 匹配的对象。 如果匹配返回,则该对象已被共享。 一般来说,您将在视图中使用 NSManagedObject

将以下方法添加到您的扩展中:

func isShared(object: NSManagedObject) -> Bool {
  isShared(objectID: object.objectID)
}

使用此代码,您可以确定目标是否已共享,然后采取适当的措施。

将以下代码添加到 CoreDataStack

var ckContainer: CKContainer {
  let storeDescription = persistentContainer.persistentStoreDescriptions.first
  guard let identifier = storeDescription?
    .cloudKitContainerOptions?.containerIdentifier else {
    fatalError("Unable to get container identifier")
  }
  return CKContainer(identifier: identifier)
}

在这里,您使用持久容器存储描述创建了 CKContainer 属性。

当您准备展示您的 CloudSharingView 时,您需要此属性,因为 CloudSharingView的第二个参数是一个 CKContainer

使用此代码,导航回 DestinationDetailView.swift 以显示 CloudSharingView。 为此,您需要一个 state 属性来控制 CloudSharingView 作为工作表的呈现。

首先,将以下属性添加到 DestinationDetailView

@State private var showShareSheet = false

其次,您需要在 List 中添加工作表修饰符以呈现 CloudSharingView。 在现有的工作表修饰符上方添加以下代码:

.sheet(isPresented: $showShareSheet, content: {
  if let share = share {
    CloudSharingView(
      share: share, 
      container: stack.ckContainer, 
      destination: destination
    )
  }
})

当布尔值为true时,此代码使用 showShareSheet 呈现 CloudSharingView。 要切换此布尔值,您需要更新共享按钮内的逻辑。

替换:

print("Share button tapped")

if !stack.isShared(object: destination) {
  Task {
    await createShare(destination)
  }
}
showShareSheet = true 

这个逻辑首先检查对象是否被共享。 如果未共享,则从目标对象创建共享。 完成该任务后,将显示 CloudSharingViewshowShareSheet 设置为 true。 您现在已准备好展示云共享视图并添加人员为您的日记做出贡献。

在真实设备上登录您的 iCloud 帐户。 构建并运行。 添加目的地。 在设备上运行的原因是向第二个 iCloud 帐户发送邀请。 最常见的选项是通过电子邮件或短信。

添加目的地后,点击destination以查看 DestinationDetailView。 在此处,点击右上角的Share按钮。 选择您想要的交付方式并发送邀请。

注意:您也可以使用模拟器共享邀请。 打开您要共享的数据,然后点击共享按钮。 从这里,复制链接并使用模拟器中的 Safari 浏览器通过电子邮件发送。


Accepting Share Invitations

现在您已经向第二个用户发送了邀请,您需要将应用程序设置为接受邀请并将数据添加到共享存储中。 导航到 AppDelegate.swift 你会看到 SceneDelegate 是空的。 在这里,您将添加代码以接受共享。

第一步是实现UIKit scene场景代理方法windowScene(_:userDidAcceptCloudKitShareWith:)。 当用户点击之前共享的链接并接受邀请时,代理会调用此方法并启动应用程序。 将以下方法添加到 SceneDelegate

func windowScene(
  _ windowScene: UIWindowScene,
  userDidAcceptCloudKitShareWith cloudKitShareMetadata: CKShare.Metadata
) {
  let shareStore = CoreDataStack.shared.sharedPersistentStore
  let persistentContainer = CoreDataStack.shared.persistentContainer
  persistentContainer.acceptShareInvitations(
    from: [cloudKitShareMetadata], into: shareStore
  ) { _, error in
    if let error = error {
      print("acceptShareInvitation error :\(error)")
    }
  }
}

此代码首先获取对您在本教程开头创建的 sharedPersistentStore 的引用。 iOS 15 中的新功能是 acceptShareInvitations(from:into:completion:)persistentContainer 调用此方法。 它接受共享并将必要的元数据添加到 sharedPersistentStore 中。 而已!

现在,是时候在第二台设备上构建和运行了,登录到第二个 iCloud 帐户。 如果您从真实设备发送邀请,那可以是模拟器。

当应用程序启动时,您会注意到共享日记条目没有显示。 这是因为您还没有接受邀请。 此时,如果您通过短信分享了邀请,请打开信息Messages并点击邀请。

当对话框询问您是否要打开邀请时,选择Open

您现在可以在第二台设备上看到共享条目。 很令人吃惊吧!


Fetching Shared Data

至此,您已经创建了一个日记条目并与其他用户共享。 一旦您在第二台设备上接受共享,它现在就是您在iCloud 中共享区域的一部分。 因此,当应用程序启动并且您与 iCloud 同步时,您在 iCloud 中的数据会与您的设备同步并自动显示。 但是,您没有任何关于共享的元数据。 最终目标不仅是显示共享条目,还要获取有关参与共享的人员的信息。

为此,您将实现 fetchShares(matching:)。 当您需要确定一个对象是否为isShared时,您已经实现了一次此方法。 打开 CoreDataStack.swift 并将以下代码添加到扩展中:

func getShare(_ destination: Destination) -> CKShare? {
  guard isShared(object: destination) else { return nil }
  guard let shareDictionary = try? persistentContainer.fetchShares(matching: [destination.objectID]),
    let share = shareDictionary[destination.objectID] else {
    print("Unable to get CKShare")
    return nil
  }
  share[CKShare.SystemFieldKey.title] = destination.caption
  return share
}

上面的代码执行以下操作:

  • 检查对象是否共享。 如果它没有关联的共享记录,则无需继续。
  • 使用 fetchShares(matching:),返回匹配的 NSManagedObjectID 及其关联的 CKShare 的字典。
  • 从字典中提取 CKShare
  • 使用destination的标题设置共享的标题。
  • 返回 CKShare

要使用这个新方法,请打开 DestinationDetailView.swift。 目标是在详细视图出现时为该对象获取关联的 CKShare。 添加以下代码作为 List 的修饰符之一:

.onAppear(perform: {
  self.share = stack.getShare(destination)
})

此代码使用 getShare(_:)并检索 CKShare。 您需要提取有关此共享参与者的信息。 使用此代码,在您的第二台设备上构建和运行。 点击共享对象以转到详细信息屏幕。 看到数据现在存在于Participants部分的底部。

注意每个用户的角色和权限。一个用户显示为Owner,另一个显示为Private User,两个用户都具有Read-Write权限。这意味着不仅所有者而且第二个用户都可以修改与他们共享的数据。

为了更改对这些数据的权限和访问权限,Apple 已经为您完成了所有繁重的工作。到您从中创建共享的第一台设备,因为您需要成为Owner才能访问Share Options。构建并运行,然后执行以下步骤:

  • 1) 点击您要为其更新权限的条目。
  • 2) 在详细信息屏幕中,点击Share操作。
  • 3) 请注意 CloudSharingView 根据它所拥有的关于 CKShare 的信息在新的上下文中启动。在此屏幕中,您可以全局更新权限或为特定参与者更新权限。选择Share Options并将权限更新为仅查看(View only)。有权访问此共享的每个人都只有读取(Read)权限。

请注意,用户当前可以读取和写入正在修改权限的条目。

在更新权限的上下文中观察 CloudSharingView

share options。 将权限更改为View only

再次构建并运行。 更改会同步,具有更新权限的条目现在显示为Read-Only

1. Displaying Private Data Versus Shared Data

目前,当您启动该应用程序时,您日记中的条目看起来都一样。 区分私人记录与共享记录的唯一方法是点击详细信息并查看参与者列表中的角色。 要改进这一点,请返回 HomeView.swift。 然后,替换以下代码:

VStack(alignment: .leading) {
  Image(uiImage: UIImage(data: destination.image ?? Data()) ?? UIImage())
    .resizable()
    .scaledToFill()

  Text(destination.caption)
    .font(.title3)
    .foregroundColor(.primary)

  Text(destination.details)
    .font(.callout)
    .foregroundColor(.secondary)
    .multilineTextAlignment(.leading)
}

VStack(alignment: .leading) {
  Image(uiImage: UIImage(data: destination.image ?? Data()) ?? UIImage())
    .resizable()
    .scaledToFill()

  Text(destination.caption)
    .font(.title3)
    .foregroundColor(.primary)

  Text(destination.details)
    .font(.callout)
    .foregroundColor(.secondary)
    .multilineTextAlignment(.leading)
                
  if stack.isShared(object: destination) {
    Image(systemName: "person.3.fill")
      .resizable()
      .scaledToFit()
      .frame(width: 30)
  }
}

此代码使用 isShared 来确定记录是否是共享的一部分。 构建并运行。 请注意,您的共享记录现在有一个图标,表明它已与其他用户共享。


Challenge Time

要进一步改进应用程序,请在允许编辑或删除记录等特定操作之前考虑用户的角色和权限。

1. Challenge One

persistentContainer 上使用canUpdateRecord(forManagedObjectWith:)canDeleteRecord(forManagedObjectWith:),调整视图逻辑,使其考虑权限。

你能弄清楚吗? 请参阅以下解决方案:

打开 CoreDataStack.swift。 在 isShared(object:) 下,添加以下方法:

func canEdit(object: NSManagedObject) -> Bool {
  return persistentContainer.canUpdateRecord(
    forManagedObjectWith: object.objectID
  )
}
func canDelete(object: NSManagedObject) -> Bool {
  return persistentContainer.canDeleteRecord(
    forManagedObjectWith: object.objectID
  )
}

这些方法根据对象的权限返回一个布尔值。

接下来,打开 DestinationDetailView.swift。 查找包含 Text("Edit") 按钮的 ToolBarItem。 将以下修饰符添加到 Button

.disabled(!stack.canEdit(object: destination))

编辑按钮现在被禁用,除非您对此数据具有读/写权限。

最后,打开 HomeView.swift 并查找 swipeActions 修饰符。 现在,您应该会看到> 一个带有 Label("Delete", systemImage: "trash")Button。 将以下修饰符添加到 Button

.disabled(!stack.canDelete(object: destination))

使用此代码,只有具有适当权限的用户才能执行编辑或删除等操作。

2. Challenge Two

目前您的应用程序存在一个小bug。 如果您是共享数据的所有者,您可以随时停止共享此数据。 发生这种情况时,执行 cloudSharingControllerDidStopSharing(_:)。 在此挑战中,更新代码,以便在不再与他人共享帖子时从第二个设备中删除此数据。

你想清楚了吗? 请参阅以下解决方案:

打开 CoreDataStack.swift。 在 isShared(object:) 下,添加:

func isOwner(object: NSManagedObject) -> Bool {
  guard isShared(object: object) else { return false }
  guard let share = try? persistentContainer.fetchShares(matching: [object.objectID])[object.objectID] else {
    print("Get ckshare error")
    return false
  }
  if let currentUser = share.currentUserParticipant, currentUser == share.owner {
    return true
  }
  return false
}

该方法:

  • 检查对象是否有关联的 CKShare。 如果不是,它立即返回 false
  • 尝试通过 fetchShares(_:) 方法获取 CKShare。 如果没有找到任何匹配的共享,则返回 false。
  • 最后,如果该共享的当前用户是所有者,则返回 true。 否则,它返回 false。

使用此代码,打开 CloudSharingController.swift并将以下代码添加到 cloudSharingControllerDidStopSharing(_:)

if !stack.isOwner(object: destination) {
  stack.delete(destination)
}

就是这些! 现在,当您停止共享特定对象并且用户与 CloudKit 同步时,该对象将从他们的设备中删除。

在本教程中,您学习了使用 CloudKit共享Core Data的重要步骤,包括:

  • 使用 NSPersistentCloudKitContainer 共享数据。
  • 展示云共享视图。
  • 接受共享邀请并获取共享数据。
  • 区分 SwiftUI 中的私有数据和共享数据。

您了解了 iOS 15 中引入的新方法并解决了应用程序中的挑战和小bug

有关更多信息,请查看 Apple 关于 Build apps that share data through CloudKit and Core Data的视频。

这是 Apple 关于Synchronizing a Local Store to the Cloud的示例项目。

后记

本篇主要讲述了使用 SwiftUI AppCore DataCloudKit之间的数据共享,感兴趣的给个赞或者关注~~~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容