Understanding iOS 13 Scene Delegate

Published by donnywals on October 28, 2019October 28, 2019

Xcode 11 で新しいプロジェクトを作成すると、これまで目にしなかったものに気付くことがあるかもしれません。 AppDelegate.swift ファイル、ViewController.swift、ストーリーボード、およびその他のファイルを作成するだけでなく、Xcode は新しいファイル、SceneDelegate.swift ファイルを作成するようになったのです。 このファイルを見たことがない場合、それが何であるか、そして、アプリでこの新しいシーン デリゲートをどのように使用することになっているかを理解するのはかなり難しいでしょう。

今週のブログ投稿の終わりまでに、次のことがわかるようになります。

  • なぜシーン デリゲートが iOS 13 の重要な部分なのか。
  • さっそく始めましょう。

    Examining the new Xcode project template

    新しい Xcode プロジェクトを作成すると、SwiftUI または Storyboards を使用するかどうかを選択する選択肢が常にあります。 ここでのあなたの選択にかかわらず、Xcode はあなたが構築するための新しい種類のプロジェクトテンプレートを生成します。 次のセクションで SceneDelegate.swiftAppDelegate.swift ファイルを詳しく見ますが、今重要なのは、Xcode があなたのためにこれらのファイルを作成したことを理解することです。

    これら2つのデリゲートファイルに加えて、Xcode はもう少し微妙なことをします。 あなたの Info.plist ファイルをよく見てください。 次の画像のような内容を持つ Application Scene Manifest という新しいキーがあるはずです。

    Screenshot of the Info.plist file's scene manifest

    Screenshot of the Info.plist file's scene manifest

    The scene manifest はシーンに対して名前と委譲クラスが特定されています。 これらのプロパティは配列 (Application Session Role) に属しており、Info.plist に複数の構成を持つことができることを示唆していることに注意してください。 上のスクリーンショットですでに見つけたかもしれませんが、より重要なキーは Enable Multiple Windows です。 このプロパティは、デフォルトでNOに設定されています。 このプロパティをYESに設定すると、iPadOS(あるいはmacOS)上でアプリケーションの複数のウィンドウを開くことができるようになります。 iOS アプリケーションの複数のウィンドウを並べて実行できることは、これまで取り組んできたシングル ウィンドウ環境とは大きな違いです。また、アプリのライフサイクルが 1 箇所ではなく 2 箇所で管理されるようになったのは、複数のウィンドウを持つことができるようになったことが、すべての理由です。

    AppDelegate と SceneDelegate の役割を理解する

    iOS 13 より前にアプリを構築したことがあるなら、AppDelegate はアプリケーションの起動、前景化、背景化などに関するほぼすべてを行う場所として知っていることでしょう。 iOS 13 では、Apple は AppDelegate の責任の一部を SceneDelegate に移しました。

    AppDelegate の責務

    AppDelegate は iOS 13 でもアプリケーションの主要なエントリ ポイントです。 Apple は、いくつかのアプリケーション レベルのライフサイクル イベントで AppDelegate メソッドを呼び出します。 Apple のデフォルト テンプレートには、Apple が使用することが重要であると考える 3 つのメソッドがあります。

    • func application(_:didFinishLaunchingWithOptions:) -> Bool
    • func application(_:configurationForConnecting:options:) -> UISceneConfiguration
    • func application(_:didDiscardSceneSessions:)

    これらのメソッドには、実際に何をするかを理解するのに十分な詳細を説明した解説がいくつか入っています。

    アプリケーションが起動されたばかりのとき、func application(_:didFinishLaunchingWithOptions:) -> Bool が呼び出されます。 このメソッドは、アプリケーションのセットアップを行うために使用されます。 iOS 12 以前では、このメソッドを使用して UIWindow オブジェクトを作成および構成し、UIViewController インスタンスをウィンドウに割り当てて表示させていたかもしれません。

    アプリケーションがシーンを使用している場合、AppDelegate がこれを行う責任はもはやありません。 アプリケーションが複数のウィンドウ、または UISceneSession をアクティブにできるようになったので、AppDelegate で単一のウィンドウ オブジェクトを管理することはあまり意味を持ちません。 このメソッドは、アプリが最初に起動するときには呼び出されないことに注意してください、それは新しいシーンを取得し、作成するためにのみ呼び出されます。 複数のシーンの作成と管理については、後のブログ記事で詳しく説明します。

    AppDelegate テンプレート内の最後のメソッドは func application(_:didDiscardSceneSessions:) です。 このメソッドは、ユーザーがシーンを破棄するたびに呼び出されます。たとえば、マルチタスク ウィンドウでシーンをスワイプするか、プログラム的にそうする場合などです。 これらのデフォルトのメソッドに加えて、AppDelegate は URL を開く、メモリの警告をキャッチする、アプリがいつ終了するか、デバイスのクロックが大幅に変更されたかどうか、ユーザーがリモート通知に登録されているかどうかなどを検出するために使用することも可能です。

    ヒント:
    現在、AppDelegate を使用してアプリのステータス バーの外観を管理している場合、iOS 13 ではいくつかの変更を行う必要があるかもしれないことに注意することが重要です。 iOS 13 では、いくつかのステータス バー関連のメソッドが非推奨になりました。

    ここで、AppDelegate の新しい責務が何であるかをよりよく理解できたので、新しい SceneDelegate を見てみましょう。

    SceneDelegate の責務

    AppDelegate がアプリケーションのライフサイクルに責任を持つオブジェクトであると考えるとき、SceneDelegate は画面に表示されるもの、つまりシーンまたはウィンドウに責任を持ちます。 シーンを扱うとき、ユーザーにはウィンドウのように見えるものは、実際には UIScene と呼ばれ、UISceneSession によって管理されています。 したがって、ウィンドウを参照するとき、実際にはUISceneSessionオブジェクトを参照しています。

    ここで、私たちが同じページにいることを確認し、プロジェクトを作成したときに Xcode が作成した SceneDelegate.swift ファイルを見てみましょう。

    デフォルトでは、SceneDelegate.swift ファイルにいくつかのメソッドがあります:

    • scene(_:willConnectTo:options:)
    • sceneDidDisconnect(_:)
    • sceneDidBecomeActive(_:)
    • sceneWillResignActive(_:)
    • sceneWillEnterForeground(_:)
    • sceneDidEnterBackground(_:)

    iOS 13 以前の AppDelegate に精通していればこれらのメソッドも非常に見覚えがあるはずです。 最初に scene(_:willConnectTo:options:) を見てみましょう。このメソッドは、おそらく最もなじみがなく、UISceneSession のライフサイクルで最初に呼び出されるメソッドです。

    scene(_:willConnectTo:options:) のデフォルトの実装は、最初のコンテンツ ビュー (SwiftUI を使用している場合は ContentView) を作成し、新しい UIWindow を作成し、ウィンドウの rootViewController を設定し、このウィンドウがキー ウィンドウになります。 このウィンドウはユーザーが見るウィンドウと考えるかもしれません。 これは、残念ながら、そうではありません。 ウィンドウは iOS 13 よりも前から存在し、アプリが動作するビューポートを表します。 つまり、UISceneSession はユーザーが見る可視ウィンドウを制御し、作成する UIWindow はアプリケーションのコンテナー ビューです。

    初期ビューの設定に加えて、scene(_:willConnectTo:options:) を使用して、シーンが過去に切断された場合に、シーン UI を復元することができます。 たとえば、バックグラウンドに送信されたためです。 また、connectionOptionsオブジェクトを読み込んで、シーンがHandOffリクエストによって作成されたかどうか、あるいはURLを開くために作成されたかを確認することもできます。 この方法は、このブログ記事の後半で紹介します。

    シーンが接続されると、シーンのライフサイクルの次のメソッドは sceneWillEnterForeground(_:) です。 このメソッドは、シーンがステージに上がるときに呼び出されます。 これは、アプリケーションがバックグラウンドからフォアグラウンドに遷移するとき、または初めてアクティブになるときです。 次に、sceneDidBecomeActive(_:)が呼び出される。

    アプリがバックグラウンドに移行すると、sceneWillResignActive(_:)sceneDidEnterBackground(_:) が呼び出されます。 これらのメソッドの目的はアプリケーションごとに異なり、これらのメソッドが呼び出されるときの説明については、Xcode テンプレートのコメントがかなり良い仕事をしているので、今ここで説明することはありません。 実際、これらのメソッドが呼び出されるタイミングは、あなた自身で把握することができると思います。 シーンがバックグラウンドに送信されるたびに、iOS はリソースを解放するためにシーンの切断とクリアを決定する場合があります。

    セッション自体も必ずしも破棄されるわけではないことに注意してください。 これは、ディスクやネットワークから簡単にロードできるデータ、あるいは簡単に再作成できるその他のデータでもかまいません。 また、簡単に再作成できないデータ、たとえば、ユーザーがシーンで提供した入力で、ユーザーがシーンに戻ったときにまだそこにあると期待されるものを確実に保持することも重要です。

    複数のシーンをサポートするテキスト処理アプリを考えてみてください。 ユーザーが 1 つのシーンで作業している場合、Safari で調べ物をしたり Spotify で音楽を変更したりするためにシーンをバックグラウンドにすると、iOS がしばらくの間テキスト処理アプリのシーンを切断していたとしても、すべての作業がテキスト処理アプリにまだ存在していると絶対に期待するはずです。 これを実現するには、アプリは必要なデータを保持する必要があり、現在のアプリの状態を NSUserActivity オブジェクトにエンコードして、シーンが再接続されたときに scene(_:willConnectTo:options:) で後で読み取ることができるようにする必要があります。

    シーンの接続、切断、および再接続というこのワークフローは、優れたアプリと偉大なアプリを分けることになるので、アプリで状態の復元を実装する方法を見てみましょう。 URL を開く、Handoff リクエストを処理する、または状態を復元する必要がある場合があります。 このセクションでは、おそらく最も複雑なシナリオを処理する必要があるため、状態の復元に主に焦点を当てます。

    状態の復元は、シーンが切断され、sceneDidDisconnect(_:) が呼び出されると開始されます。 この時点で、アプリケーションに後で復元できる状態がすでに設定されていることが重要です。 これを行う最良の方法は、アプリケーションでNSUserActivityを使用することです。 Handoff、Siri ショートカット、Spotlight インデックスなどをサポートするために NSUserActivity を使用している場合、余分な作業はあまりありません。 まだ NSUserActivity を使用していない方もご安心ください。 簡単なユーザー アクティビティは次のようになります。

    let activity = NSUserActivity(activityType: "com.donnywals.DocumentEdit")activity.userInfo = 

    このユーザー アクティビティは Apple が推奨する構造ではなく、状態の復元を説明するための非常に単純な例であることに注意してください。 NSUserActivity に関する完全なガイドについては、このトピックに関する Apple のドキュメントを参照することをお勧めします。

    後で復元できるユーザー活動を提供する時が来たら、システムはあなたの SceneDelegatestateRestorationActivity(for:) メソッドを呼び出します。 このメソッドはデフォルトのテンプレートの一部ではないことに注意してください

    func stateRestorationActivity(for scene: UIScene) -> NSUserActivity? { return scene.userActivity}

    これを行うと、シーンの現在アクティブなユーザー アクティビティをシーン セッションと関連付けることができます。 シーンが切断されるときはいつでも、セッションがシーンに再接続できるように、UISceneを所有するUISceneSessionが破棄されないことを忘れないでください。 このような場合、scene(_:willConnectTo:options:)が再び呼び出されます。 このメソッドでは、UIScene を所有する UISceneSession にアクセスできるので、必要に応じてセッションの stateRestorationActivity を読み取り、アプリケーションの状態を復元できます:

    if let activity = session.stateRestorationActivity, activity.activityType == "com.donnywals.DocumentEdit", let documentId = activity.userInfo as? String { // find document by ID // create document viewcontroller and present it}

    もちろん、このコードの細かい部分はアプリケーションによって異なりますが、一般的なアイデアは明確であるべきです。

    URL を処理することが期待されている場合、connectionOptions オブジェクトの urlContexts を調べて、シーンが開くべき URL と、アプリケーションがこれを行う方法に関する情報を見つけることができます。

    for urlContext in connectionOptions.urlContexts { let url = urlContext.url let options = urlContext.options // handle url and options as needed}

    options オブジェクトには、シーンがその場で URL を開くべきかどうか、どのアプリケーションがこの URL を開くように要求したか、および要求に関する他のメタデータについての情報が含まれています。

    iOS 13 の SceneDelegate による状態復元の基本は驚くほど簡単で、特に NSUserActivity に基づいて構築されているので、多くのアプリケーションはシーンの状態復元のサポートを始めるためにあまり多くの作業を行う必要がないことを意味しています。 特に、ユーザーがシーン内のオブジェクトを作成または操作できるアプリケーションの場合、ユーザーはシーンを一瞬バックグラウンドに移動しただけでその作業が失われるとは思わないでしょう。 iOS 13 で AppDelegateSceneDelegate がどのような役割を果たし、それらのライフサイクルがどのようなものであるかを学びました。 AppDelegate は、たとえばアプリの起動など、アプリケーション レベルのイベントに反応する役割を担っていることがおわかりいただけたと思います。 SceneDelegate は、シーンのライフサイクルに関連するイベントを担当します。 例えば、UISceneSessionのシーンの生成、破壊、状態の復元などです。 言い換えれば、Apple が iOS 13 に UISceneDelegate を追加した主な理由は、マルチウィンドウ アプリケーションのための優れたエントリ ポイントを作成することでした。

    UISceneDelegate の基本について学んだ後、UISceneSessionUIScene による iOS 13 の状態復元がどのように見えるかの非常に簡単な例をご覧になりました。 もちろん、ユーザーがアプリに対して複数の UISceneSession を生成したときにアプリがどのように動作するか、また、これらのシーンが同期を維持したりデータを共有したりする必要があるかについて、さらに学ぶべきことがたくさんあります。 お読みいただきありがとうございます。ご質問やご意見がありましたら、遠慮なく Twitter でご連絡ください。

    Stay up to date with my weekly newsletter

    Practical Combine

    Learn everything you need to know about Combine and how you can use it in your projects with my new book Practical Combine. 13の章、プレイグラウンド、そしてサンプルプロジェクトが用意されており、Combineをできるだけ早く使い始めることができます。

    この本は、デジタルダウンロードとしてわずか29.99ドルで入手できます!

    Get Practical Combine

    コメントを残す

    メールアドレスが公開されることはありません。