ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
SwiftUIにおけるAppの重要事項
新しいAppプロトコルのおかげで、SwiftUIでApp全体を構築できるようになりました。App、SceneおよびViewがいかに相互につながり合うかをご確認ください。短時間で、複雑な作業を経ることなく、最高クラスの製品に期待される機能を容易に実装する方法を学びましょう。 新しいコマンドモディファイアを用いて、インターフェースに機能を簡単に追加する方法や、新しいWindowGroup APIの詳細をご紹介します。このセッションを有効活用するには、ある程度のSwiftUI経験が求められます。未経験の方は、 "Introduction to SwiftUI"をご覧ください。SwiftUIをさらに追求する場合は、 "What's new in SwiftUI"、 "Data essentials in Swift UI"、 "Stacks, grids, and outlines in SwiftUI"、 "Build document-based apps in SwiftUI"も合わせてご覧ください。
リソース
関連ビデオ
WWDC20
-
ダウンロード
こんにちは WWDCへようこそ “SwiftUIにおける Appの重要事項” SwiftUIチームのマット・リケットソンです 後で同僚のジェフも紹介します 去年 SwiftUIを紹介しました パワフルな新手法で 偉大なユーザーインターフェイスを構築 Appleの全プラットフォームで使います ビューでユーザーインターフェイスを作り SwiftUI はビューを変更するため API一式を提供 そしてその2つを合わせます
今年はフレームワークを拡張しました シーンとAppを 発表するための新しいAPIで SwiftUIを使って作れるものを 更に拡大しました つまり今では App全部を SwiftUIさえあれば構築できます このセッションでは 新しいAPIを紹介し ビュー シーン そしてAppが どのように機能するか説明します 次にSwiftUIの シーンアーキテクチャについて説明し ご自身のAppでの カスタマイズ方法をお見せします 最後に 簡単な概略で Appをカスタマイズできる その他のAPIの 詳細の入手場所を説明します それでは まずはビューから始めましょう SwiftUIを使用されたことがある場合は ご存じの通り ビューは重要です なぜならそれぞれのビューが UIの一部を定義するからです Appを見ている時 目に見える全てがビューです 各画像や文字の並びはビューです それを収納するコンテナもまたビューです スクリーン上に見える全てのピクセルが ビューに何らかの形で定義されています
ただし全ビューが同じ Appのものではありません Appはスクリーン全体を完全に コントロールしてはいないからです その代わり プラットフォームが Appの表示をコントロールします 異なるregionで Appの一部を表示します
SwiftUIでは これらの異なる regionを“シーン”と呼びます
シーンの内容をスクリーンに表示する 最も一般的な方法はウインドウです iPadOSなどのプラットフォームは 複数のウインドウを並べて表示できます iOS watchOS そしてtvOS等の プラットフォームは 各Appを1つのフルスクリーン ウインドウで表示することを好みます macOSがいい例です シーンコンテンツが様々な方法で表示されます この場合 関連ウインドウの コレクションが見えます 各ウインドウが異なるシーンコンテンツの デモンストレーションになります
macOSでも関連ウインドウを 集めることができます 1つのタブ付きウインドウになります その場合 シーンが代わりに 個別タブを表します
それ自身のシーンも この共有ウインドウを表します 各タブに関連する子シーンの コンテナとして機能します
これらのシーンの塊が Appのコンテンツを作ります App シーン ビューを合わせて 所有権の統一された階層を作ります
先ほど言いましたように ビューは基本的な構成要素で スクリーンに見えるものすべてを レンダリングします またはより複雑な ユーザーインターフェイスにもなります
ビューはシーンコンテンツを作成します そしてプラットフォームでシーンを 個別に表示することもできます
ビューと同じように これらのシーンを合わせると より複雑なシーンが作れます さっき見たタブ付きウインドウの 例がそうです
最後に これらのシーンは Appのコンテンツを作成します
App シーンおよびビューが どのように機能するか分かったので それがSwiftUIコードに どう影響するかを見てみましょう
これは私がSwiftUIに 書いたAppです Book Clubの本をどれだけ 読んだかが分かります ご覧の通り SwiftUIのAppには 簡潔な宣言が見られます つまり このような ベーシックなAppは ほんの一握りのコード列にしか 当てはまりません 余分なボイラープレートがなければ App固有のコードに 直ちにフォーカスできます
この場合 実際のApp インターフェイスの定義に ReadingListViewerと呼ばれる ビューを使います
ReadingListViewerは 私が作ったカスタムビューで これによってリーディングリストを ブラウズできます
弊社のReadingListViewerは WindowGroupというシーンを内蔵しています
WindowGroupシーンが管理するウインドウは ReadingListViewがレンダリングする ウインドウです 追加ウインドウを作成したり 同じウインドウ内に新しいタブを作ったり これらの機能をサポートする プラットフォームで行います この後ジェフがWindowGroupについて 詳細を説明します
WindowGroupシーンは App内に含まれ Appプロトコルに 準拠したカスタムstructで表されます
コードの構造が 先ほど見た所有者の階層にマッチすることに 気が付くでしょう Appにはシーンが含まれ シーンにはビューが含まれています
またApp宣言に関しては カスタムビュー宣言に似ていることにも 気が付くでしょう 例えば ビューとAppは両方共 データの依存関係を宣言できます ReadingListViewerがここで ReadingListStoreオブジェクトを監視します
このAppはReadingListStore オブジェクトにも依存しますが それ自身オブジェクトの所有者で あることも宣言します StateObjectプロパティラッパーを利用します SwiftUIの今年の新機能です
ビューとAppはまた両方共 “ボディ”プロパティを宣言し ユーザーインターフェイス コンテンツを定義します
ビューがその他のビューを含むことに ついては先ほどお話しました つまり そのためにビューのボディが ビューを返すのです ところがAppは シーンを使って作成されます そのためボディプロパティは 代わりにシーンを返します
最後にAppをデコレートしている @main属性に気付かれたと思います これはSwift 5.3の新属性です あるタイプをエントリーポイントとして プログラムを実行します
通常 Swiftプログラムの実行には main.swiftファイルが必要です @main属性で その責任をアブストラクトに 委任することができます アブストラクトは起動に必要な 全設定を自動的に行い スクリーンに Appを表示します
コードのレビューをしたところで 少し前の話に戻ります
ここに見えているのはSwiftUIの ベーシックApp向けの完全な宣言です ほんの一握りのコード列にだけ 当てはまります でも騙されてはいけません 実際には自動でインテリジェントな 動作がたくさんあり このシンプルな宣言に詰め込まれています このAppの仕組みを 本当に理解するには WindowGroupシーンについて もっと語る必要があります それが私たちの ユーザーインターフェイスを管理します そのため ここからジェフに交代します ありがとう マット こんにちは
まずお見せしたいのは マットが概要をお見せしたコンセプトの デモンストレーションです それからWindowGroupの細かい点について いくつか話します
私はかなり熱心な読者なので あるAppに取り組んできました 現在読んでいる全ての本の 進捗を記録するためです ご覧の通り 私のAppはiPadOSの 初期ウインドウで起動します コンテンツとして特定したビューを 使用しています 見る限りかなりの本が読みかけのようですね 進捗を確認するために新しいウインドウを 開けてみましょう App Exposeを開けると 新規をここに簡単に作成できます そして違う本にナビゲートします WindowGroupはこの機能を 私のiPadOS上のAppに 自動的に提供します ウインドウそれぞれが インターフェイスのそれぞれの状態に 反映していることが分かります 選択された本はそれぞれで異なり 1つの変更はその他の本に影響しません これはSwiftUIシーンの重要な側面です Appは各モデルに それぞれのシーンを提供しますが 各シーンのビューの状態は独立しています また App Switcherについて いくつかお話しします 各ウインドウは私のAppの 名前を表示しています 選択された本のタイトルも見えます これは今年発売の ビューモディファイアによって行われます “navigation title”といって iOSではナビゲーションバーに タイトルを表示するのに使われ App Switcherでも使われます ビューモディファイアの例です これは親シーンの状態に影響を与えます
MacではAppが複数のウインドウを サポートするのは一般的です WindowGroupをAppで使うことで SwiftUIはファイルメニューに メニューアイテムを提供し 新しいシーン例の作成をサポートします
このアイテムは呼びだすこともできます スタンダードCommand-N キーボードショートカットを経由します
macOSでナビゲーションタイトルが どのように適用されるかが分かります 本のタイトルが表示されます ウインドウのタイトルバーの辺りです
ウインドウメニューでも使用されます
良いタイトルを作ることは重要です ユーザーに多くのコンテクストを与え ウインドウを選択する助けにもなります オープンウインドウのリストから選択します 複数のウインドウをサポートするのに加え macOSはウインドウの グループ化もサポートします ウインドウメニューを経由します オープンウインドウを1つのタブ付き インターフェイスにマージできます
それぞれのタブは個別シーンを表しています
この機能ではコードを書く必要がありません SwiftUIが自動的に行います
実演をいくつかお見せしました AppのWindowGroupシーンを 使用しています ここでもう少し詳細を見てみましょう 今お見せしたことを要約すると WindowGroupはシーンです Appの主要インターフェイスを 表現できます
提供したビューは インターフェイスの定義として使われます
これは私たちの全プラットフォームで 期待した通りに機能します 例えば iOSとwatchOSでは あなたのビューが表示されるのは デバイスのスクリーン全体を閉める ウインドウです macOSでは このウインドウはビューの定義によって サイズが決まります シーンのライフサイクルは 実行中のプラットフォームにより 管理されます
macOSを例としましょう プラットフォームがAppに ウインドウを作る必要がある場合 WindowGroupは新しい子シーンを インスタンス化します デフォルトでウインドウ内に コンテンツを表示します
複数のウインドウをサポートする プラットフォームでは 例えばmacOSやiPadOSですが WindowGroupは複数の子シーンを インスタンス化します
これはユーザーアクションに 対応する形で起こります 例えば メニューアイテムをクリックしたり マルチタスクジェスチャーを呼び出すことなど
各シーンはそのユーザーインターフェイスの 定義を共有する一方で この定義を作るビューは すべてそれぞれ独立した状態を持っています
つまり1つのウインドウで ビュー状態を変えても その他の状態に 影響を与えることはありません
この機能により テンプレートの提供が可能です インターフェイスに使用できます 一方でユーザーがこのインターフェイスを カスタマイズすることもできます 提供された状態を通して行います
プラットフォームはシーンの ライフサイクルを担当するため 私たちは今年 新しいプロパティラッパーを紹介しました ビュー状態の修復管理をサポートします SceneStorageプロパティラッパーを使うと ビュー状態を維持できます
保存状態識別のための 一意のキーが必要です この状態はその後自動的に 保存および修復されます SwiftUIが適宜行います
ここまでシーンについてお見せしました 特にWindowGroupの仕組みについてです ここでマットにバトンタッチします マットはAppのカスタマイズ方法について 更に詳しく説明します ありがとう ジェフ 最後にお見せしたいのは 今年 利用可能となる その他のApp関連の新機能です
先ほど見たBook Clubは データ駆動型Appでした 共有のデータモデルに支えられています その他の種類のAppもあります 例えばドキュメントベース Appです このShapeEdit Appがその例です
今年新しいのは DocumentGroupシーンタイプで 自動的に管理してオープン 編集 そしてドキュメントベースシーンを保存する点 詳細は今年の “Build Document-Based Apps in SwiftUI”を 視聴してください
それではBook Clubの例に戻りましょう macOS Appの共通項は プリファレンスウインドウです
今年私たちは新しい“設定”シーンタイプを 発表します macOSで使用でき スタンダードプリファレンスウインドウに 自動的に設定します 設定シーンはプリファレンスコマンドを Appメニューに 自動的に設定します また ウインドウに 正しいスタイルの処理を施します
メニューコマンドと言えば SwiftUIもAPIを提供します そのためカスタムコマンドを シーンに追加できます それには新コマンドモディファイアを 使用します BookCommandsは 私が定義したカスタムタイプです さっと見てみましょう
コマンドAPIはパワフルで柔軟です 同じ宣言を使用した 状態駆動プログラムモデルで ビュー シーン Appで使用します コマンドをカスタムタイプに カプセル化できます ユーザーフォーカスに基づく ターゲットアクションで AppKitまたはUIKitの レスポンダーチェーンに似ています 通常のビューを利用して コマンド自身を構築します 参照ドキュメントをご確認ください 詳細やコマンドの使用方法が 説明されています 今年SwiftUIで利用可能な 新しいApp関連APIを ほんの一部ご紹介しました その他のSwiftUIトークの ご視聴もお勧めします ご自身のAppの コンテンツ構築に役立ちます “Data Essentials in SwiftUI”で 知識を深めることができます App シーン およびビューの間での 適切なデータ通信が学べます “What's New in Swift”でお見せするのは 言語における最新の改良について あなたのSwiftUIコードを改良します
皆さんが すばらしいSwiftUIを 構築されるのを楽しみにしています 作品をフォーラムでシェアしてください フィードバックも次のビルドも お待ちしています 引き続き WWDCをお楽しみください
-
-
3:57 - Book club app
@main struct BookClubApp: App { @StateObject private var store = ReadingListStore() var body: some Scene { WindowGroup { ReadingListViewer(store: store) } } } struct ReadingListViewer: View { @ObservedObject var store: ReadingListStore var body: some View { NavigationView { List(store.books) { book in Text(book.title) } .navigationTitle("Currently Reading") } } } class ReadingListStore: ObservableObject { init() {} var books = [ Book(title: "Book #1", author: "Author #1"), Book(title: "Book #2", author: "Author #2"), Book(title: "Book #3", author: "Author #3") ] } struct Book: Identifiable { let id = UUID() let title: String let author: String }
-
10:21 - Window groups
@main struct BookClubApp: App { @StateObject private var store = ReadingListStore() var body: some Scene { WindowGroup { ReadingListViewer(store: store) } } } struct ReadingListViewer: View { @ObservedObject var store: ReadingListStore var body: some View { NavigationView { List(store.books) { book in Text(book.title) } .navigationTitle("Currently Reading") } } } class ReadingListStore: ObservableObject { init() {} var books = [ Book(title: "Book #1", author: "Author #1"), Book(title: "Book #2", author: "Author #2"), Book(title: "Book #3", author: "Author #3") ] } struct Book: Identifiable { let id = UUID() let title: String let author: String }
-
12:07 - Scene storage
@main struct BookClubApp: App { @StateObject private var store = ReadingListStore() var body: some Scene { WindowGroup { ReadingListViewer(store: store) } } } struct ReadingListViewer: View { @ObservedObject var store: ReadingListStore @SceneStorage("selectedItem") private var selectedItem: String? var selectedID: Binding<UUID?> { Binding<UUID?>( get: { selectedItem.flatMap { UUID(uuidString: $0) } }, set: { selectedItem = $0?.uuidString } ) } var body: some View { NavigationView { List(store.books) { book in NavigationLink( destination: Text(book.title), tag: book.id, selection: selectedID ) { Text(book.title) } } .navigationTitle("Currently Reading") } } } class ReadingListStore: ObservableObject { init() {} var books = [ Book(title: "Book #1", author: "Author #1"), Book(title: "Book #2", author: "Author #2"), Book(title: "Book #3", author: "Author #3") ] } struct Book: Identifiable { let id = UUID() let title: String let author: String }
-
12:59 - Document groups
import SwiftUI import UniformTypeIdentifiers @main struct ShapeEditApp: App { var body: some Scene { DocumentGroup(newDocument: ShapeDocument()) { file in DocumentView(document: file.$document) } } } struct DocumentView: View { @Binding var document: ShapeDocument var body: some View { Text(document.title) .frame(width: 300, height: 200) } } struct ShapeDocument: Codable { var title: String = "Untitled" } extension UTType { static let shapeEditDocument = UTType(exportedAs: "com.example.ShapeEdit.shapes") } extension ShapeDocument: FileDocument { static var readableContentTypes: [UTType] { [.shapeEditDocument] } init(fileWrapper: FileWrapper, contentType: UTType) throws { let data = fileWrapper.regularFileContents! self = try JSONDecoder().decode(Self.self, from: data) } func write(to fileWrapper: inout FileWrapper, contentType: UTType) throws { let data = try JSONEncoder().encode(self) fileWrapper = FileWrapper(regularFileWithContents: data) } }
-
13:27 - Settings scene
@main struct BookClubApp: App { @StateObject private var store = ReadingListStore() @SceneBuilder var body: some Scene { WindowGroup { ReadingListViewer(store: store) } #if os(macOS) Settings { BookClubSettingsView() } #endif } } struct BookClubSettingsView: View { var body: some View { Text("Add your settings UI here.") .padding() } } struct ReadingListViewer: View { @ObservedObject var store: ReadingListStore var body: some View { NavigationView { List(store.books) { book in Text(book.title) } .navigationTitle("Currently Reading") } } } class ReadingListStore: ObservableObject { init() {} var books = [ Book2(title: "Book #1", author: "Author #1"), Book2(title: "Book #2", author: "Author #2"), Book2(title: "Book #3", author: "Author #3") ] } struct Book: Identifiable { let id = UUID() let title: String let author: String }
-
14:07 - BookCommands
struct BookCommands: Commands { @FocusedBinding(\.selectedBook) private var selectedBook: Book? var body: some Commands { CommandMenu("Book") { Section { Button("Update Progress...", action: updateProgress) .keyboardShortcut("u") Button("Mark Completed", action: markCompleted) .keyboardShortcut("C") } .disabled(selectedBook == nil) } } private func updateProgress() { selectedBook?.updateProgress() } private func markCompleted() { selectedBook?.markCompleted() } }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。