我可以将 Snapchat SDK (SnapKit) 与 SwiftUI 一起使用吗?

Can I use the Snapchat SDK (SnapKit) with SwiftUI?

我正在尝试将 Snapkit with an iOS app but I want to use SwiftUI instead of UIKit. I've already done the required setup with Snapkit and now I'm trying to get the snapchat login button to display in my app. I know the Snapkit SDK is made for UIKit and not SwiftUI, but SwiftUI has a way to wrap UIViews into SwiftUI 与 UIViewRepresentable 协议集成。我试过实现这个,但登录按钮仍然没有显示。

这是我的代码:

import SwiftUI
import UIKit
import SCSDKLoginKit

struct ContentView: View {
    var body: some View {
        SnapchatLoginButtonView()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}


struct SnapchatLoginButtonView: UIViewRepresentable {

    func makeCoordinator() -> Coordinator {
        Coordinator()
    }

    func makeUIView(context: Context) -> SCSDKLoginButton {
        let s = SCSDKLoginButton()
        s.delegate = context.coordinator

        return s
    }

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

    class Coordinator: NSObject, SCSDKLoginButtonDelegate {
        func loginButtonDidTap() {
        }
    }
}

我感觉我在 SCSDKLoginButton 中遗漏了一些东西,但不确定是什么所以这里是文件 SCSDKLoginButton.h 以供参考。任何帮助将不胜感激!

//
//  SCSDKLoginButton.h
//  SCSDKLoginKit
//
//  Copyright © 2018 Snap, Inc. All rights reserved.
//

#import <UIKit/UIKit.h>

@protocol SCSDKLoginButtonDelegate
- (void)loginButtonDidTap;
@end

@interface SCSDKLoginButton : UIView

@property (nonatomic, weak, nullable) id<SCSDKLoginButtonDelegate> delegate;

- (instancetype)initWithCompletion:(nullable void (^)(BOOL success, NSError *error))completion NS_DESIGNATED_INITIALIZER;

@end

巧合的是,在您发布 Issue 大约 3 天后,我尝试在独家 SwiftUI/ iOS13 项目中实施 SnapKit SDK。

不幸的是,我无法直接解决您的问题,因为 Snapchat 在适合使用 iOS 13 中介绍的 SceneDelegate 和 AppDelegate 范例进行开发之前,必须通过其 SDK 解决一些关键问题。但我希望我能阐明您的问题,并将我的发现展示给其他处于类似困境中的人。

这些是我在寻求在 SwiftUI 中实现 SCSDKLoginKit 和 SCSDKBitmojiKit 时提出的以下问题/观察:

  • 最基本的问题是 SCSDKLoginKit 模块已过时,正如您正确意识到的那样。 SCSDKLoginClient.login() 要求调用视图符合 (UIKIT) UIViewController class。所以我们必须使用 UIViewControllerRepresentable 的解决方法来充当我们的 SwiftUI <-> UIKit 中介。

  • 然而,根本问题在于 SnapKit SDK 文档尚未更新,无法为开发人员提供 Snapchat Auth 和您的应用程序逻辑之间的 SceneDelegate link。所以即使你正确地实现了你的 SCSDKLoginButton 也不是一帆风顺的!

现在直接回答你的问题,你正试图将 SCSDKLoginButton 包装在 UIViewControllerRepresentable 中,这是可以做到的,我相信比我更了解协调器等的人可以帮助你。然而,我只是想表明,在 snapchat 提供更新的 SDK 之前,您目前的努力可能是徒劳的。

这是我的设置:

[ContentView.swift]

import SwiftUI

struct ContentView: View {
    @State private var isPresented = false

    var body: some View {

        Button("Snapchat Login Button") { self.isPresented = true} 
            .sheet(isPresented: $isPresented) {
                  LoginCVWrapper()
        }
    }
}

[LoginCVWrapper.swift]

import SwiftUI
import UIKit
import SCSDKLoginKit

struct LoginCVWrapper: UIViewControllerRepresentable {

    func makeUIViewController(context: Context) -> UIViewController {
        return LoginViewController()
    }

    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
        //Unused in demonstration
    }
}

[LoginViewController.swift]

import UIKit
import SCSDKLoginKit

class LoginViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        performLogin() //Attempt Snap Login Here
    }

    //Snapchat Credential Retrieval Fails Here
    private func performLogin() {
        //SCSDKLoginClient.login() never completes once scene becomes active again after Snapchat redirect back to this app.
        SCSDKLoginClient.login(from: self, completion: { success, error in
            if let error = error {
                print("***ERROR LOC: manualTrigger() \(error.localizedDescription)***")
                return
            }
            if success {
                self.fetchSnapUserInfo({ (userEntity, error) in
                    print("***SUCCESS LOC: manualTrigger()***")
                    if let userEntity = userEntity {
                        DispatchQueue.main.async {
                            print("SUCCESS:\(userEntity)")
                        }
                    }
                })
            }
        })
    }

    private func fetchSnapUserInfo(_ completion: @escaping ((UserEntity?, Error?) -> ())){
        let graphQLQuery = "{me{displayName, bitmoji{avatar}}}"
        SCSDKLoginClient
            .fetchUserData(
                withQuery: graphQLQuery,
                variables: nil,
                success: { userInfo in

                    if let userInfo = userInfo,
                        let data = try? JSONSerialization.data(withJSONObject: userInfo, options: .prettyPrinted),
                        let userEntity = try? JSONDecoder().decode(UserEntity.self, from: data) {
                        completion(userEntity, nil)
                    }
            }) { (error, isUserLoggedOut) in
                completion(nil, error)
        }
    }
}

[运行如下]GIF: Code Running On Device

有关 SceneDelegate 界面的更多信息 link 问题: 当您不可避免地执行 SCSDKLoginClient.login() 调用时(大概是在按下 SCSDKLoginButton 时),Snapchat 将打开,假设您的应用是 link,它将正确显示 "grant access" sheet在 Snapchat Dev Portal 中编辑。

当您接受这些权限后,Snapchat 会重定向到您的应用程序。然而,这是您的应用程序和检索 snapchat 用户名/bitmoji 之间的 link 崩溃的地方。这是因为在新的 iOS 13 应用程序中,当您的应用程序状态更改时,SceneDelegate 会处理,而不是 iOS13 之前版本中的 AppDelegate。因此 Snapchat returns 用户数据,但您的应用程序永远不会检索它。

[前进]

  • SnapKitSDK(当前版本为 1.4.3)需要与文档一起更新。
  • 我刚刚向 Snapchat 提交了一个支持问题,询问此更新何时到来,所以如果我听到更多消息,我会更新此问题。如果您正在寻找 SCSDKLoginButton() 问题的直接解决方案,我深表歉意,我只是想让您知道目前还有哪些挑战超出了它。

[进一步阅读]

@Stephen2697 正确地指出 snap sdk 不是为 iOS 13 构建的,但由于 SceneDelegate 现在处理 oauth 重定向而不是 AppDelegate。我想出了一个解决方法,可以在场景委托中使用 SCSDKLoginClient.application() 方法(为 appdelegate 制作)。这是代码,将其添加到您的场景委托中,传递给您的 Snapchat 登录的完成处理程序将 运行:

func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
    for urlContext in URLContexts {
        let url = urlContext.url
        var options: [UIApplication.OpenURLOptionsKey : Any] = [:]
        options[.openInPlace] = urlContext.options.openInPlace
        options[.sourceApplication] = urlContext.options.sourceApplication
        options[.annotation] = urlContext.options.annotation
        SCSDKLoginClient.application(UIApplication.shared, open: url, options: options)
    }
}

至于登录按钮没有出现,请确保将完成处理程序添加到 SCSDKLoginButton 实例,否则它将无法工作。像这样:

let s = SCSDKLoginButton { (success, error) in
        //handle login
    }