[SwiftUI] Scene まとめ(WWDC22)

SwiftUI2021

     
Scene をまとめます。

環境&対象

以下の環境で動作確認を行なっています。

  • macOS Monterey 13 Beta3
  • Xcode 14.0 Beta3
  • iOS 16.0 beta

WWDCビデオ

参考にしている WWDC ビデオは、こちら

App / Scene / View

SwiftUI アプリの 要素は、以下のような関係性になっています。

$$ App \ni Scene \ni View $$

つまり、App は複数の Scene で構成されて、Scene は 複数の(階層的な) View で構成されます。

# Scene を使って Scene を構成することはできないので、Scene を階層的に構成することはできません

macOS 上では、Scene が Window に対応している要素です。
iPadOS でも Multi-Window をサポートするので Scene が Window に対応します。

# iOS 上では、Single-Window 相当なので、異なります。

Window を1のみを持つアプリであれば、 App = Scene で App とも1対1で対応することになります。

SwiftUI 2022 では、以下の Scene が用意されています。

・WindowGroup
・DocumentGroup
・Settings
・Window (NEW!)
・MenuBarExtra (NEW!)

Scene の利用

Scene は、App に含まれる要素なので、以下のようにして定義/利用することになります。
# サンプルは、Apple のビデオで説明されているものです。


@main
struct MultiSceneApp: App {
  var body: some Scene {
    WindowGroup {
      ContentView()
    }

    DocumentGroup(viewing: CustomImeageDocument.self) { file in 
      ImageViewer(file.document)
    }

    Settings {
      SettingsView()
    }
  }
}

View が複数の View を配下に持つ記述ができるように ViewBuilder がありますが、同様に SceneBuilder というもので 複数の Scene を記述することができるようになっています。

なお、ViewBuilder と同じように 最大10という制限があります。

WindowGroup

Non-Document based なアプリのメイン Window を作成する WindowGroup です。

単一のデータを複数の Window で表示することも、Window 毎に異なるデータを対象とすることも可能です。

いずれにしても、どこでデータを生成して、どのように共有する/しない という設計が必要となります。

複数 Window をそれぞれ 異なるデータを対象に使うアプリであれば、WindowGroup で使用される View 内で、データを生成します。

単一 データを複数 Window で使うのであれば、App でデータを定義して、WindowGroup 内の View にデータを渡すことになります。

単一 Window なアプリ

Window 毎に データを持つアプリであれば、以下のような使い方になります。

特に気をつける点はありませんが、複数 Window を許す環境 (macOS, iPadOS) であって、複数 Window を許したくないならば SwiftUI が自動で作成してくれる "NewWindow" メニューを非表示にした方が良いかもしれません。


@main
struct SceneApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView() // - ContentView の中で、@State/@StateObject 等を使いデータ生成
        }
    }
}

"NewWindow" メニューは、WindowGroup という Scene を毎回 作成することになるので、
この使い方は、1つのアプリ内で、異なるデータに対して それぞれ 1つの Window を持つことができるアプリ になっているとも考えられます。

単一データに対して複数 Window

単一のデータに対して Window を複数持つアプリであれば、以下のような使い方になります。

複数の WindowGroup で共有したいデータは、App で定義して、WindowGroup に渡す必要があります。


@main
struct SceneApp: App {
    @StateObject private var appDate = AppDate() // App でデータ生成
    var body: some Scene {
        WindowGroup {
            ContentView(appData) // - ContentView に、データを渡す
        }
    }
}

生成された 複数の WindowGroup/ContentView でデータを編集することがあるのであれば、変更も共有したいハズなので、上記のように StateObject 等で class 等を使った運用になることが多いです。

データ競合を避けるのであれば、actor を使って データを定義することを検討することになります。

id 指定と value 指定

WindowGroup には、value: 指定できる新しい initializer が追加されました。

これまでは、id: 指定でした。つまり、Window の種類を指定して開くものでした。

これに対して、value: 指定は、表示するデータに応じた Window を開くことができます。
つまり同じ種類の Window に対して、異なるデータを渡すことで異なる Window が開かれます。
(言葉にすると普通ですが、id: 指定の時は、少し工夫が必要でした)

# 記事後半で紹介する openWindow と関連しています。

DocumentGroup

ドキュメントベースのアプリで、ドキュメントを表示する Window を開く Scene です。

editor 向けの initializer と viewer 向けの initializer が用意されています。

詳細は 以下の記事で説明しています。
SwiftUI2021[SwiftUI]DocumentGroup / FileDocument / ReferenceFileDocument について

Settings

App の設定画面向けの Scene です。

(この後に出てくる) Window と異なる点は、アプリの Setgings... メニュー で開くようになっている点です。

デフォルトで、メニュー/キーボードショートカット と紐づけられている Window です。

Window

アプリ全体で共有する1つの Window です。

アプリ全体で共有する Window ですので、initializer は、id: を使ったものしか用意されていません。

なお、Window Scene を定義すると、アプリの Window メニューにそのタイトルがメニューとして追加され、メニューからも開くことができるようになります。

MenuBarExtra

macOS 向けの Scene で メニューバーに追加することができます。

これ以前は、SwiftUI アプリであっても、AppDelegate を使用して、追加することが必要でしたが、この Scene が追加されたことにより、AppDelegate が必要となるケースが減りました。

Scene関連要素

Scene に関連する Environment を紹介します。

@Environment(\.openWindow)

@Environment(\.openWindow) は、WindowGroup/ Window 向けに用意されている Environment です。

openWindow(id: XX) とするとマッチするWindowGroup/Windowを開くことができます。

openWindow(value: XX) とするとマッチする WindowGroup を開く

value を使うことで、データを表示する Window を新規で開くか、既存の Window を使うかを SwiftUI 側で判断してくれます。そのために、value 指定ができる 新しい WindowGroup の initializer が追加されました。

同じ値の value を指定された時に、すでに既存の Window があればその Window が最前面に移動されます。
なお、value の同値確認をするために、value の型は、Hashable に conform している必要があります。

ちなみに、同じ value type に複数の WindowGroup を定義しておくことも可能ですが、おそらく動作は未定義だと思いますが、実際の動作としては、最初に定義されていたものが使われました。(想定していない使い方だと思います。)

なお、value で Window が開けないのは、Window は、アプリ中には、その id を持つ Window は ただ1つ のハズなので、異なる value が与えられることを想定していないからです。

言い方を変えると、value によって異なる Window が必要ならば、WindowGroup で定義されているべきものです。

@Environment(\.newDocument)

@Environment(\.newDocument) は、DocumentGroup 向けに用意されている Environment です。

DocumentGroup のうち、editor で定義されているケース向けです。

Document (FileDocument/ ReferenceFileDocument) を渡すことで、該当する DocumentGroup を開くことができます。

@Environment(\.openDocument)

@Environment(\.openDocument) も、DocumentGroup 向けに用意されている Environment です。

こちらは、DocumentGroup のうち、viewer で定義されているケース向けです。
URL を渡して、該当ドキュメントを使って、DocumentGroup を開くことができます。

まとめ

Scene の使い分け

Scene の使い分け
  • データを表示する Window を開くには、WindowGroup
  • ドキュメントを表示する Window を開くには、DocumentGroup
  • アプリに 1つだけの Window を開くには、Window
  • 設定 Window は、Settings (macOS 専用)
  • メニューバーに表示するには、MenuBarExtra (macOS 専用)

説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。

SwiftUI おすすめ本

SwiftUI を理解するには、以下の本がおすすめです。

SwiftUI で開発していくときに、ViewやLayoutのための適切なmodifierを探すのが大変です。
以下の”SwiftUI Views Mastery Bundle"という本がビジュアル的に確認して探せるので、超便利です。

SwiftUIViewsMastery

コメントを残す

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