【问题标题】:Using UIApplicationDelegateAdaptor to get callbacks from userDidAcceptCloudKitShareWith not working使用 UIApplicationDelegateAdaptor 从 userDidAcceptCloudKitShareWith 获取回调不起作用
【发布时间】:2021-08-05 01:09:17
【问题描述】:

我试图在userDidAcceptCloudKitShareWith 被调用时得到通知。传统上这是在 App Delegate 中调用的,但由于我正在使用 App 作为我的根对象构建 iOS 14+。至于如何将userDidAcceptCloudKitShareWith 添加到我的App 类,我还找不到任何文档,所以我使用UIApplicationDelegateAdaptor 来使用App Delegate 类,但似乎userDidAcceptCloudKitShareWith 从来没有被叫了?

import SwiftUI
import CloudKit

// Our observable object class
class ShareDataStore: ObservableObject {
    
    static let shared = ShareDataStore() 
    @Published var didRecieveShare = false
    @Published var shareInfo = ""
}

@main
struct SocialTestAppApp: App {
    
    
    @StateObject var shareDataStore = ShareDataStore.shared 
    
    
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    
    var body: some Scene {
        WindowGroup {
            ContentView().environmentObject(shareDataStore)
        }
    }
}

class AppDelegate: NSObject, UIApplicationDelegate {
    
    let container = CKContainer(identifier: "iCloud.com.TestApp")
    
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        print("did finish launching called")
        return true
    }
    
    
    func application(_ application: UIApplication, userDidAcceptCloudKitShareWith cloudKitShareMetadata: CKShare.Metadata) {
        print("delegate callback called!! ")
        acceptShare(metadata: cloudKitShareMetadata) { result in
            switch result {
            case .success(let recordID):
                print("successful share!")
                ShareDataStore.shared.didRecieveShare = true
                ShareDataStore.shared.shareInfo = recordID.recordName
            case .failure(let error):
                print("failure in share = \(error)")
            }
        }    }
    
    func acceptShare(metadata: CKShare.Metadata,
                     completion: @escaping (Result<CKRecord.ID, Error>) -> Void) {
        
        // Create a reference to the share's container so the operation
        // executes in the correct context.
        let container = CKContainer(identifier: metadata.containerIdentifier)
        
        // Create the operation using the metadata the caller provides.
        let operation = CKAcceptSharesOperation(shareMetadatas: [metadata])
        
        var rootRecordID: CKRecord.ID!
        // If CloudKit accepts the share, cache the root record's ID.
        // The completion closure handles any errors.
        operation.perShareCompletionBlock = { metadata, share, error in
            if let _ = share, error == nil {
                rootRecordID = metadata.rootRecordID
            }
        }
        
        // If the operation fails, return the error to the caller.
        // Otherwise, return the record ID of the share's root record.
        operation.acceptSharesCompletionBlock = { error in
            if let error = error {
                completion(.failure(error))
            } else {
                completion(.success(rootRecordID))
            }
        }
        
        // Set an appropriate QoS and add the operation to the
        // container's queue to execute it.
        operation.qualityOfService = .utility
        container.add(operation)
    }
    
    
}

根据 Asperi 的回答更新:

import SwiftUI
import CloudKit

class ShareDataStore: ObservableObject {
    
    static let shared = ShareDataStore() 
    
    @Published var didRecieveShare = false
    @Published var shareInfo = ""
}

@main
struct athlyticSocialTestAppApp: App {
    
    
    @StateObject var shareDataStore = ShareDataStore.shared 
    
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    
    let sceneDelegate = MySceneDelegate()
    
    var body: some Scene {
        WindowGroup {
            ContentView().environmentObject(shareDataStore)
                 .withHostingWindow { window in
                 sceneDelegate.originalDelegate = window.windowScene.delegate
                 window.windowScene.delegate = sceneDelegate
              }
        }
        
    }
    
}


class MySceneDelegate: NSObject, UIWindowSceneDelegate {

    let container = CKContainer(identifier: "iCloud.com...")
    
     var originalDelegate: UIWindowSceneDelegate?

        var window: UIWindow?

        func sceneWillEnterForeground(_ scene: UIScene) {
            print("scene is active")
        }

        func sceneWillResignActive(_ scene: UIScene) {
            print("scene will resign active")
        }
        
    
      // forward all other UIWindowSceneDelegate/UISceneDelegate callbacks to original, like
   func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    originalDelegate?.scene!(scene, willConnectTo: session, options: connectionOptions)
   }

        func windowScene(_ windowScene: UIWindowScene, userDidAcceptCloudKitShareWith cloudKitShareMetadata: CKShare.Metadata) {

            print("delegate callback called!! ")
            acceptShare(metadata: cloudKitShareMetadata) { result in
                switch result {
                case .success(let recordID):
                    print("successful share!")
                    ShareDataStore.shared.didRecieveShare = true
                    ShareDataStore.shared.shareInfo = recordID.recordName
                case .failure(let error):
                    print("failure in share = \(error)")
                }
            }

        }

}


extension View {
    func withHostingWindow(_ callback: @escaping (UIWindow?) -> Void) -> some View {
        self.background(HostingWindowFinder(callback: callback))
    }
}

struct HostingWindowFinder: UIViewRepresentable {
    var callback: (UIWindow?) -> ()

    func makeUIView(context: Context) -> UIView {
        let view = UIView()
        DispatchQueue.main.async { [weak view] in
            self.callback(view?.window)
        }
        return view
    }

    func updateUIView(_ uiView: UIView, context: Context) {
    }
}

【问题讨论】:

  • 查看docs,如果您使用场景,它似乎不会被调用,我认为SwiftUI 就是这种情况,但我找不到设置窗口场景委托的方法。

标签: swift swiftui icloud cloudkit cloudkit-sharing


【解决方案1】:

Scene-based 应用程序中,userDidAcceptCloudKitShareWith 回调被发布到 Scene 委托,但在 SwiftUI 2.0 App-based 应用程序中,SwiftUI 本身使用场景委托来提供 scenePhase 事件,但不提供本地处理方式主题回调。

解决此问题的可能方法是找到一个窗口并注入自己的场景委托包装器,它将处理 userDidAcceptCloudKitShareWith 并将其他人转发给原始 SwiftUI 委托(以保持标准 SwiftUI 事件正常工作)。

这里有几个基于https://stackoverflow.com/a/63276688/12299030窗口访问的演示快照(但是您可以使用任何其他更好的方式来获取窗口)

@main
struct athlyticSocialTestAppApp: App {
    @StateObject var shareDataStore = ShareDataStore.shared 
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    let sceneDelegate = MySceneDelegate()
    
    var body: some Scene {
        WindowGroup {
            ContentView().environmentObject(shareDataStore)
              .withHostingWindow { window in
                 sceneDelegate.originalDelegate = window?.windowScene.delegate
                 window?.windowScene.delegate = sceneDelegate
              }
        }
    }
}

class MySceneDelegate : NSObject, UIWindowSceneDelegate {
   var originalDelegate: UISceneDelegate?

   func windowScene(_ windowScene: UIWindowScene, userDidAcceptCloudKitShareWith cloudKitShareMetadata: CKShareMetadata) {

       // your code here
   }

   // forward all other UIWindowSceneDelegate/UISceneDelegate callbacks to original, like
   func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
       originalDelegate?.scene(scene, willConnectTo: session, options: connectionOptions)
   }
}

【讨论】:

  • 谢谢!我对编译器说的Cannot assign value of type 'UISceneDelegate?' to type 'UIWindowSceneDelegate?'window.windowScene.delegate = sceneDelegate 的行有问题,编译器说可选类型'UIWindow?'的值?必须解包以引用已封装基类型“UIWindow”的成员“windowScene”?我根据您上面的建议对我的问题进行了编辑。
  • 很明显,只需更改编译器要求的内容即可。已更新。
【解决方案2】:

看看这个问题,它有很多有用的东西要检查几个可能的答案:

CloudKit CKShare userDidAcceptCloudKitShareWith Never Fires on Mac App

请务必将CKSharingSupported 键添加到您的 info.plist,然后尝试使用上述链接中的答案将userDidAcceptCloudKitShareWith 放在不同的位置(放置位置取决于您使用哪种应用程序)重建)。

【讨论】:

  • 谢谢,是的,我确实有 CKSharingSupported 并且也遇到过这个问题。我的应用程序是一个 iOS 应用程序,专为 iOS 14 构建,所以我没有 appDelegatesceneDelegate 开始,但在我的问题中,我尝试使用 appDelegateAdapter ,但 userDidAcceptCloudKitShareWith 没有被叫。我还尝试了stackoverflow.com/questions/57104025/… 此处建议的所有步骤来添加sceneDelegate,但它也永远不会在那里被调用。
  • 不管怎样,我发现CKShare 真的很难使用。您可以考虑使用应用程序的Public 数据库进行自己的共享。您可以将用户想要共享的记录放入其中,然后使用您的应用程序的其他人可以立即使用它们,而无需管理CKShare 权限的麻烦。只是一个想法。
猜你喜欢
  • 1970-01-01
  • 2019-02-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多