Help us understand the problem. What is going on with this article?

iOS13(beta)でASWebAuthenticationSessionの変更点

More than 1 year has passed since last update.

注:本記事は 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

presentationContextProviderASWebAuthenticationPresentationContextProviding(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も対応しており、ASWebAuthenticationSessionSFAuthenticationSessionをラップしたクラスを定義していたため、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が出るまでに対応しないとログインが出来なくなってしまう可能性があるので注意しましょう。

simorgh3196
モバイルアプリ開発やってます Swiftが好きです
https://github.com/simorgh3196
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away