Использование ASWebAuthentication в SwiftUI
Возникли некоторые проблемы с аутентификацией для работы из представления SwiftUI. Я использую ASWebAuthentication и при каждом запуске получаю сообщение об ошибке:
Невозможно запустить ASWebAuthenticationSession без предоставления контекста представления. Установите presentationContextProvider перед вызовом -start.
Я создаю ViewController и передаю ссылку на окно делегата сцены на основе этого сообщения о переполнении стека, но этот ответ, похоже, не работает для меня. Я также нашел этот пост на Reddit, но мне немного неясно, как они смогли инициализировать представление с окном до того, как окно делегата сцены будет настроено.
Это код, который я использую для представления 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
}
Есть идеи, как я могу заставить эту работу?
4 ответа
Что касается сообщения на 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()
}
}
}
Я сталкивался с чем-то подобным раньше при реализации ASWebAuthenticationSession
. Одна вещь, которую я не осознавал, это то, что у вас должна быть сильная ссылка на переменную сеанса. Я бы сделал тебяsession
измените свойство вашего класса и посмотрите, решит ли это проблему. Коротко о том, что я имею в виду:
// 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()
}
Ронни: я столкнулся с той же проблемой, но, наконец, заставил ShimController() работать и избежать предупреждения. Я втянулся в решение, но забыл создать экземпляр класса. Ищите мои комментарии "<<" ниже. Теперь авторизация работает, и обратный вызов срабатывает как часы. Единственное предостережение: я разрешаю что-то еще, а не Spotify.
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()
}
С помощью .webAuthenticationSession(isPresented:content)
модификатора в BetterSafariView, вы можете легко запустить сеанс веб-аутентификации в SwiftUI. Не нужно зацеплять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
}
}
}
}