注:本記事は Xcode 11 beta 6
(2019/08/20)時点での変更内容での話になります
iOS13でログイン画面が開かない
開発中のアプリでASWebAuthenticationSession
を利用してOAuthログインする機能があり、iOS13(beta)で動作確認をしたところログインボタンを押してログイン画面を出そうとしても出てくれない状態でした。
ログを見てみると以下のエラーの出力がありました。
Cannot start ASWebAuthenticationSession without providing presentation context. Set presentationContextProvider before calling -start.
どうやらiOS13からはstart()
を呼び出す前にpresentationContextProvider
をセットする必要があるようです。
ASWebAuthenticationPresentationContextProviding
presentationContextProvider
はASWebAuthenticationPresentationContextProviding
(document)というprotocolに適合したNSObject
のサブクラスのインスタンスのようです。
定義は以下のようになっています。
/** @abstract Provides context to target where in an application's UI the authorization view should be shown.
*/
@available(iOS 13.0, *)
public protocol ASWebAuthenticationPresentationContextProviding : NSObjectProtocol {
/** @abstract Return the ASPresentationAnchor in the closest proximity to where a user interacted with your app to trigger
authentication. If starting an ASWebAuthenticationSession on first launch, use the application's main window.
@param session The session requesting a presentation anchor.
@result The ASPresentationAnchor most closely associated with the UI used to trigger authentication.
*/
func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor
}
ASPresentationAnchor
なるほど、そのprotocolのインスタンスを用意すればいいことはわかったけど、ASPresentationAnchor
ってなんだ?と思ったらUIWindow(iOS, Mac Catalyst, tvOS)
or NSWindow(macOS)
のtypealiasでした。
直してみる
シンプルに書くと多分こんな感じに実装すれば良さそう
import AuthenticationServices
import UIKit
class ViewController: UIViewController {
private var authenticationSession: ASWebAuthenticationSession?
// ログインボタンを押した後に呼ばれる
func loginStart() {
let authenticationSession = ASWebAuthenticationSession(
url: URL(string: "https://...")!,
callbackURLScheme: "<url-scheme>",
completionHandler: loginCompletionHandler(url:error:))
if #available(iOS 13.0, *) {
//
authenticationSession.presentationContextProvider = self
}
authenticationSession.start()
// authenticationSessionが解放されないようにプロパティに入れておく
self.authenticationSession = authenticationSession
}
// ログインの結果のハンドリング
func loginCompletionHandler(url: URL?, error: Error?) {
authenticationSession = nil
...
}
...
}
@available(iOS 13.0, *)
extension ViewController: ASWebAuthenticationPresentationContextProviding {
func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
return view.window!
}
}
ちなみに今開発中のアプリではiOS11も対応しており、ASWebAuthenticationSession
とSFAuthenticationSession
をラップしたクラスを定義していたため、ASWebAuthenticationPresentationContextProviding
を適合させたprivateなクラスを用意して実装しました。
このように実装する場合、presentationContextProvider
は弱参照なので解放されてしまいます。そのためASWebAuthenticationPresentationContextProviding
を適合したオブジェクトのインスタンスもプロパティとして保持しないといけないことに注意してください。
class AuthenticationSession {
...
private var authenticationSession: Any?
private var presentationContextProvider: Any?
func loginStart(from viewController: UIViewController) {
if #available(iOS 12.0, *) {
let authenticationSession = ASWebAuthenticationSession(
url: loginURL,
callbackURLScheme: "<url-scheme>",
completionHandler: loginCompletionHandler(url:error:))
if #available(iOS 13.0, *) {
let presentationContextProvider = AuthPresentationContextProver(viewController: viewController)
authenticationSession.presentationContextProvider = presentationContextProvider
self.presentationContextProvider = presentationContextProvider
}
authenticationSession.start()
self.authenticationSession = authenticationSession
} else { // iOS 11.x
let authenticationSession = SFAuthenticationSession(
url: loginURL,
callbackURLScheme: "<url-scheme>",
completionHandler: loginCompletionHandler(url:error:))
authenticationSession.start()
self.authenticationSession = authenticationSession
}
}
func loginCompletionHandler(url: URL?, error: Error?) {
authenticationSession = nil
presentationContextProvider = nil
...
}
...
@available(iOS 13.0, *)
private class AuthPresentationContextProver: NSObject, ASWebAuthenticationPresentationContextProviding {
private weak var viewController: UIViewController!
init(viewController: UIViewController) {
self.viewController = viewController
super.init()
}
func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
return viewController?.view.window!
}
}
}
おわり
ASWebAuthenticationSession
を使っているアプリはiOS13が出るまでに対応しないとログインが出来なくなってしまう可能性があるので注意しましょう。