如何获取已存在共享的 CKShare 对象(获取共享 URL)

Posted

技术标签:

【中文标题】如何获取已存在共享的 CKShare 对象(获取共享 URL)【英文标题】:How to fetch the CKShare object of an already existing share (to get share URL) 【发布时间】:2019-04-08 17:06:07 【问题描述】:

我正在尝试让我的应用程序的用户能够检索到他们已经共享的记录的 iCloud 共享链接。创建共享时,我可以使用let share = CKShare(rootRecord: CKRecord) 后跟Apple 的UISharingController 来获取URL。但是,每次我使用这种方法时,旧链接都会失效,并将其他人踢出他们已经加入的共享。如何允许用户(记录所有者)随时获取记录 CKShare 对象的可共享 URL?

//Initially shares the record
let share = CKShare(rootRecord: newRecord)
share[CKShare.SystemFieldKey.title] = "Group" as CKRecordValue?        
let modifyRecordsOperation = CKModifyRecordsOperation( recordsToSave: [newRecord, share], recordIDsToDelete: nil)

//Activates the UISharingController (code not shown)

【问题讨论】:

【参考方案1】:

假设记录已被分享,您可以通过以下方式获取邀请其他参与者的 URL:

if let shareReference = record.share 
    database.fetch(withRecordID: shareReference.recordID)  (share, error) in
        let shareURL = (share as? CKShare)?.url
    

如果您想为现有共享再次显示 UICloudSharingController(不生成新 URL),您可以这样做:

let sharingController = UICloudSharingController  (_, prepareCompletionHandler) in

    func createNewShare() 
        let operationQueue = OperationQueue()
        operationQueue.maxConcurrentOperationCount = 1

        let share = CKShare(rootRecord: record)
        share[CKShare.SystemFieldKey.title] = "Title" as CKRecordValue
        share.publicPermission = .readWrite

        let modifyRecordsOp = CKModifyRecordsOperation(recordsToSave: [share, record], recordIDsToDelete: nil)
        modifyRecordsOp.modifyRecordsCompletionBlock =  records, recordIDs, error in
            prepareCompletionHandler(share, self, error)
        
        modifyRecordsOp.database = database
        operationQueue.addOperation(modifyRecordsOp)
    

    if let shareReference = record.share 
        database.fetch(withRecordID: shareReference.recordID)  (share, error) in
            guard let share = share as? CKShare else 
                createNewShare()
                return
            
            prepareCompletionHandler(share, self, nil)
        
     else 
        createNewShare()
    

【讨论】:

【参考方案2】:

听起来您在这里遇到了多个问题。我建议仔细检查 UICloudSharingController documentation。

第一个问题是在您提供的代码中,您每次都在创建一个新的共享,而您要做的是创建一次,然后在您想要与之交互的任何时候显示该共享(包括获取共享 URL)。创建共享后,您可能希望以某种方式将其保存在设备上(可以像将其存储在 UserDefaults 中一样简单)。

第二个问题是听起来您没有正确实例化 UICloudSharingController。当您最初创建 CKShare 时,您必须使用 UICloudSharingController init(preparationHandler:) 初始化程序并在其中创建共享。

根据不同初始化程序的文档(强调我的):

重要

您必须使用正确的初始化程序初始化控制器 方法。如果 CKRecord 是,不要使用 init(preparationHandler:) 已经分享了。同样,不要使用 init(share:container:) 如果 CKRecord 不共享。使用错误的初始化程序会导致错误 保存记录时。

然后,当您想要显示 shareSheet 时,您使用 UICloudSharingController init(share:container:) 初始化程序,并将您持久保存到设备的共享提供给它。

这是 UICloudSharingController 文档中提供的用于创建 CKShare 的代码:

func presentCloudSharingController(_ sender: Any)   guard
    let barButtonItem = sender as? UIBarButtonItem,
    let rootRecord = self.recordToShare else 
    return
  

  let cloudSharingController = UICloudSharingController  [weak self] (controller, completion: @escaping (CKShare?, CKContainer?, Error?) -> Void) in
    guard let `self` = self else 
      return
    
    self.share(rootRecord: rootRecord, completion: completion)
  

  if let popover = cloudSharingController.popoverPresentationController 
    popover.barButtonItem = barButtonItem
  
  self.present(cloudSharingController, animated: true) 


func share(rootRecord: CKRecord, completion: @escaping (CKShare?, CKContainer?, Error?) -> Void) 
  let shareRecord = CKShare(rootRecord: rootRecord)
  let recordsToSave = [rootRecord, shareRecord];
  let container = CKContainer.default()
  let privateDatabase = container.privateCloudDatabase
  let operation = CKModifyRecordsOperation(recordsToSave: recordsToSave, recordIDsToDelete: []) 
  operation.perRecordCompletionBlock =  (record, error) in
    if let error = error 
      print("CloudKit error: \(error.localizedDescription)")
    
  

  operation.modifyRecordsCompletionBlock =  (savedRecords, deletedRecordIDs, error) in
    if let error = error 
      completion(nil, nil, error)
     else 
      completion(shareRecord, container, nil)
    
  

  privateDatabase.add(operation)

下次你想展示它时,它会更加严格:

 let share = // yourCKShare
 let container = // yourCKContainer
 let viewController = // yourViewController
 let shareController = UICloudSharingController(share: share, container: container)
 viewController.present(shareController, animated: true)

最后回答您问题的 URL 部分,UICloudSharingController 用于所有内容共享 - 当控制器呈现共享时,所有者(可能还有用户)可以使用“复制链接”按钮,如果你允许公共访问 - 我没有看过,因为我的东西是私人的):

【讨论】:

太棒了,非常感谢!总的来说,我对 swift 和编码词汇还比较陌生(swift 是我的第一语言,我还在上高中,还没有任何专业指导)。我仍然无法弄清楚的问题是如何准确地从 iCloud 服务器获取现有的 CKShare 对象,以便我可以将其放入 UISharingController。我知道如何使用便利 api 和 CKoperations 获取正常的 CK 记录,但它们没有附加 CKShare 对象。如何获取 CKShare 对象? 不幸的是,这可能有点超出评论的范围 - 我建议做更多的研究和/或开始一个不同的问题。关于 CloudKit 的 WWDC 视频非常适合一些基本的高级概念内容(WWDC 2016 的视频在我看来很突出)。我发现 CloudKit 背后的概念也很难理解! 嗯实际上我猜这在问题标题中是正确的,所以也许不要开始一个新问题。您是否尝试过使用 CKQueryOperation 或 CKFetchRecordZoneChangesOperation 来获取共享? 如果一个 CKRecord 已经被共享,难道不应该简单地向下转换,像这样:let share = rootRecord as? CKShare rootRecord 不是 CKShare 记录——它们是两个不同的记录。

以上是关于如何获取已存在共享的 CKShare 对象(获取共享 URL)的主要内容,如果未能解决你的问题,请参考以下文章