【问题标题】:Using ASWebAuthentication in SwiftUI在 SwiftUI 中使用 ASWebAuthentication
【发布时间】:2020-05-25 11:18:09
【问题描述】:

在 SwiftUI 视图中进行身份验证时遇到了一些麻烦。我正在使用 ASWebAuthentication,每次运行时都会出现错误:

在不提供演示上下文的情况下无法启动 ASWebAuthenticationSession。在调用 -start 之前设置presentationContextProvider。

我正在创建一个 ViewController 并传入一个基于 this stack overflow post 的 Scene Delegate 窗口的引用,但这个答案似乎对我不起作用。我也有found this reddit post,但我有点不清楚他们是如何在设置场景委托的窗口之前用窗口初始化视图的。

这是我用于 SwiftUI 视图的代码:

import SwiftUI
import AuthenticationServices

struct Spotify: View {
  var body: some View {
    Button(action: {
        self.authWithSpotify()
    }) {
        Text("Authorize Spotify")
    }
  }

  func authWithSpotify() {

    let authUrlString = "https://accounts.spotify.com/authorize?client_id=\(spotifyID)&response_type=code&redirect_uri=http://redirectexample.com/callback&scope=user-read-private%20user-read-email"
    guard let url = URL(string: authUrlString) else { return }

    let session = ASWebAuthenticationSession(
        url: url,
        callbackURLScheme: "http://redirectexample.com/callback",
        completionHandler: { callback, error in

            guard error == nil, let success = callback else { return }

            let code = NSURLComponents(string: (success.absoluteString))?.queryItems?.filter({ $0.name == "code" }).first

            self.getSpotifyAuthToken(code)
    })

    session.presentationContextProvider = ShimViewController()
    session.start()
  }

  func getSpotifyAuthToken(_ code: URLQueryItem?) {
    // Get Token
  }

}

struct Spotify_Previews: PreviewProvider {
  static var previews: some View {
    Spotify()
  }
}

class ShimViewController: UIViewController, ASWebAuthenticationPresentationContextProviding {
  func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
    return globalPresentationAnchor ?? ASPresentationAnchor()
  }
}

在 SceneDelegate 中:

var globalPresentationAnchor: ASPresentationAnchor? = nil

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

var window: UIWindow?

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
    // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
    // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).

    // Use a UIHostingController as window root view controller
    if let windowScene = scene as? UIWindowScene {
        let window = UIWindow(windowScene: windowScene)
        window.rootViewController = UIHostingController(rootView: Spotify())
        self.window = window
        window.makeKeyAndVisible()
    }

    globalPresentationAnchor =  window
}

知道我该如何完成这项工作吗?

【问题讨论】:

  • presentationAnchor(for session:) 被调用时,globalPresentationAnchor 是否为零?
  • presentationAnchor(for session:) 被调用时,globalPresentationAnchor 不为零
  • 只是一个简单的问题,是否可以将带有按钮项目、取消、重新加载图标等出现的导航栏自定义到我们自己的某些控件?在此先感谢:)

标签: swift xcode swiftui aswebauthenticationsession


【解决方案1】:

关于 reddit 帖子,我让它按原样工作。我的误解是 AuthView 没有被用作“界面”视图。我为我的身份验证视图创建了一个普通的 SwiftUI 视图,并且我有一个按钮,其中包含创建 AuthView 实例并调用处理会话的函数的操作。我将 globalPositionAnchor 存储在 @EnvironmentObject 中,但您也应该能够从全局变量中使用它。希望这会有所帮助!

struct SignedOutView: View {
    @EnvironmentObject var contentManager: ContentManager

    var body: some View {
        VStack {
            Text("Title")
            .font(.largeTitle)

            Spacer()

            Button(action: {AuthProviderView(window: self.contentManager.globalPresentationAnchor!).signIn()}) {
                Text("Sign In")
                    .padding()
                    .foregroundColor(.white)
                    .background(Color.orange)
                    .cornerRadius(CGFloat(5))
                    .font(.headline)
            }.padding()
        }
    }
}

【讨论】:

  • 谢谢,这是让我点击的那个!
【解决方案2】:

我之前在实现ASWebAuthenticationSession 时遇到过类似的情况。我没有意识到的一件事是,您必须对会话变量有一个强引用。所以我会让你 session 变量成为你类的属性,看看是否能解决问题。我的意思的简短 sn-p:

// initialize as a property of the class
var session: ASWebAuthenticationSession?

func authWithSpotify() {
    let authUrlString = "https://accounts.spotify.com/authorize?client_id=\(spotifyID)&response_type=code&redirect_uri=http://redirectexample.com/callback&scope=user-read-private%20user-read-email"
    guard let url = URL(string: authUrlString) else { return }

    // assign session here
    session = ASWebAuthenticationSession(url: url, callbackURLScheme: "http://redirectexample.com/callback", completionHandler: { callback, error in

            guard error == nil, let success = callback else { return }

            let code = NSURLComponents(string: (success.absoluteString))?.queryItems?.filter({ $0.name == "code" }).first

            self.getSpotifyAuthToken(code)
    })

    session.presentationContextProvider = ShimViewController()
    session.start()
}

【讨论】:

  • 谢谢!似乎确实有 Instance will be immediately deallocated because property 'presentationContextProvider' is 'weak’ 警告,但我已将 @State var session: ASWebAuthenticationSession? 添加为结构的属性,它仍然存在(而且我仍然收到错误)。当我在没有@State 的情况下将属性添加到结构时,我得到Cannot assign to property: 'self' is immutable 似乎可以解决的错误。
  • 该错误是因为您使用的是Struct 而不是Class。我会尝试将其切换到课堂,看看是否有帮助(在这种情况下不需要@State),除非您使用Struct 有特定原因。
  • 我猜具体原因是认为 SwiftUI 视图必须是符合 View 协议的结构?我现在不确定是否将其切换到课程中,并且无法从使用final class 的OP 中找到除this reddit answer 以外的任何示例,但我无法使其正常工作(并且想知道在这种情况下预览是否仍然有效?)。
  • 是的,我也看到了那个 reddit 帖子。似乎如果这不起作用,您唯一的选择是将 UIKit 与 SwiftUI 混合使用。我没有 ton 的 SwiftUI 经验,但如果 reddit 的答案没有为您解决问题,这似乎是唯一的选择。我确实知道对于 UIKit,您需要将会话作为类的属性保留,否则它将无法工作。
【解决方案3】:

罗尼 - 我遇到了同样的问题,但最终让 ShimController() 工作并避免了警告。我陷入了解决方案,但忘记实例化类。在下面查找我的“

var session: ASWebAuthenticationSession?
var shimController = ShimViewController() // << instantiate your object here

func authWithSpotify() {
    let authUrlString = "https://accounts.spotify.com/authorize?client_id=\(spotifyID)&response_type=code&redirect_uri=http://redirectexample.com/callback&scope=user-read-private%20user-read-email"
    guard let url = URL(string: authUrlString) else { return }

    // assign session here
    session = ASWebAuthenticationSession(url: url, callbackURLScheme: "http://redirectexample.com/callback", completionHandler: { callback, error in

            guard error == nil, let success = callback else { return }

            let code = NSURLComponents(string: (success.absoluteString))?.queryItems?.filter({ $0.name == "code" }).first

            self.getSpotifyAuthToken(code)
    })

    session.presentationContextProvider = shimController // << then reference it here
    session.start()
}

【讨论】:

    【解决方案4】:

    BetterSafariView 中使用 .webAuthenticationSession(isPresented:content) 修饰符,您可以在 SwiftUI 中轻松启动 Web 身份验证会话。不需要挂钩SceneDelegate

    import SwiftUI
    import BetterSafariView
    
    struct SpotifyLoginView: View {
        
        @State private var showingSession = false
        
        var body: some View {
            Button("Authorize Spotify") {
                self.showingSession = true
            }
            .webAuthenticationSession(isPresented: $showingSession) {
                WebAuthenticationSession(
                    url: URL(string: "https://accounts.spotify.com/authorize")!,
                    callbackURLScheme: "myapp"
                ) { callbackURL, error in
                    // Handle callbackURL
                }
            }
        }
    }
    

    【讨论】:

    • 只是一个简单的问题,是否可以将带有按钮项目、取消、重新加载图标等出现的导航栏自定义到我们自己的某些控件?在此先感谢:)
    猜你喜欢
    • 2019-10-30
    • 1970-01-01
    • 1970-01-01
    • 2019-11-01
    • 2020-06-16
    • 2021-06-12
    • 1970-01-01
    • 1970-01-01
    • 2021-02-26
    相关资源
    最近更新 更多