在SwiftUI中使用ASWebAuthentication

在SwiftUI中使用ASWebAuthentication,swift,xcode,swiftui,aswebauthenticationsession,Swift,Xcode,Swiftui,Aswebauthenticationsession,从SwiftUI视图中获取身份验证时遇到一些问题。我正在使用ASWebAuthentication,每次运行时都会出现错误: 如果不提供演示文稿上下文,则无法启动ASWebAuthenticationSession。在调用-start之前设置presentationContextProvider 我正在创建一个ViewController,并基于它传入对场景代理窗口的引用,但这个答案似乎对我不起作用。我也说过,但我不太清楚他们是如何在设置场景代理的窗口之前使用窗口初始化视图的 这是我在Swift

从SwiftUI视图中获取身份验证时遇到一些问题。我正在使用ASWebAuthentication,每次运行时都会出现错误:

如果不提供演示文稿上下文,则无法启动ASWebAuthenticationSession。在调用-start之前设置presentationContextProvider

我正在创建一个ViewController,并基于它传入对场景代理窗口的引用,但这个答案似乎对我不起作用。我也说过,但我不太清楚他们是如何在设置场景代理的窗口之前使用窗口初始化视图的

这是我在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
}

你知道我怎样才能做到这一点吗?

我以前在实现
ASWebAuthenticationSession
时遇到过类似的情况。有一件事我没有意识到,你必须有一个对会话变量的强引用。因此,我会将您的
会话
变量设置为类的一个属性,看看这是否解决了问题。我的意思的一小段:

//初始化为类的属性
var会话:ASWebAuthenticationSession?
func authWithSpotify(){
让authUrlString=”https://accounts.spotify.com/authorize?client_id=\(spotifyID)&response\u type=code&redirect\u uri=http://redirectexample.com/callback&scope=user-读取私人%20用户读取电子邮件“
guard let url=url(字符串:authUrlString)else{return}
//在此处分配会话
会话=ASWebAuthenticationSession(url:url,callbackURLScheme:http://redirectexample.com/callback,completionHandler:{回调,中出现错误
guard error==nil,let success=callback else{return}
让code=nsurlpomponents(string:(success.absoluteString))?.queryItems?.filter({$0.name==“code”})。首先
self.getSpotifyAuthToken(代码)
})
session.presentationContextProvider=ShimViewController()
session.start()
}

关于reddit帖子,我让它按原样工作。我的误解是AuthView没有被用作“接口”视图。我为authentication视图创建了一个普通的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()
        }
    }
}

使用中的
.webAuthenticationSession(isPresented:content)
修饰符,您可以在SwiftUI中轻松启动web验证会话。它不需要钩住
SceneDelegate

导入快捷界面
导入BetterSafariView
结构SpotifyLoginView:视图{
@状态私有变量showingSession=false
var body:一些观点{
按钮(“授权Spotify”){
self.showingSession=true
}
.webAuthenticationSession(显示:$showingSession){
WebAuthenticationSession(
url:url(字符串:https://accounts.spotify.com/authorize")!,
callbackURLScheme:“myapp”
){callbackURL,中出现错误
//句柄回调URL
}
}
}
}
罗尼-
我遇到了同样的问题,但最终使ShimController()工作并避免了警告。我被这个解决方案吸引住了,但忘了实例化这个类。调用
presentationAnchor(对于会话:)
时,查找我的“Is
globalPresentationAnchor
nil?只需加倍检查,并且
presentationAnchor(对于会话:)时,
globalPresentationAnchor
不是nil
被调用请回答一个简短的问题,是否可以将显示按钮项、取消、重新加载图标等的导航栏自定义为我们自己的一些控件?提前感谢:)谢谢!似乎确实有一个
实例将被立即释放,因为属性“presentationContextProvider”是“弱的”
warning但我已经添加了
@State var session:ASWebAuthenticationSession?
作为结构的属性,它仍然存在(我仍然得到错误)。当我在没有
@State
的情况下将属性添加到结构时,我得到
无法分配给属性:“self”是不可变的
错误,我似乎可以解决。该错误是因为您使用的是
结构
而不是
。我会尝试将其切换到类,看看这是否有帮助(在这种情况下不需要
@State
)除非你使用
Struct
有特定的原因。我想具体的原因是认为SwiftUI视图必须是符合视图协议的结构?我现在真的不确定是否要将其切换到一个类,并且除了我的OP中使用
final class
,b之外,还无法找到任何其他示例但是我没能让它工作(我想知道预览在那个场景中是否仍然有效?).是的,我也看到了reddit的帖子。似乎如果这不起作用,你唯一的选择就是将UIKit与SwiftUI混合使用。我没有很多SwiftUI经验,但如果reddit的答案不能为你解决问题,这似乎是唯一的选择。我确实知道,对于UIKit,你需要将会话作为类的一个属性保留下来,否则它会不起作用。谢谢,正是这一点让它为我点击了!只是一个简单的问题,是否可以自定义一些我们自己的控件的导航栏(显示有按钮项、取消、重新加载图标等)?提前谢谢:)
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()
}