WWDC Quick Look 💓 By SwiftGGTeam
App essentials in SwiftUI

App essentials in SwiftUI

元の動画を見る

ハイライト

SwiftUI 2020 では、AppSceneWindowGroupSceneStorageDocumentGroupSettings、および Commands が追加され、アプリケーションのエントリ、マルチウィンドウ、状態復元、ドキュメント ウィンドウ、環境設定、およびメニュー コマンドを宣言型コードで整理できるようになります。

主要内容

(00:35) 1 年目の SwiftUI の焦点は View です。開発者は宣言構文を使用してテキスト、画像、リスト、ナビゲーションを組み合わせることができますが、完成したアプリはポータル、ウィンドウ、プラットフォームのライフサイクル、メニュー、設定、ドキュメント管理にも対応します。このセッションのエントリ ポイントは、ビューの外側のレイヤーを埋めることです。アプリがシーンを所有し、シーンがビューをホストし、プラットフォームがシーンの表示方法を決定します。

(03:57) 「App」プロトコルは、エントリを「@main」の型として書き込みます。このエントリの「body」はシーンを返し、それがビューをホストします。最も一般的なシーンは「WindowGroup」です。これは同じインターフェイス定義をシステムに渡し、iPadOS と macOS がプラットフォーム ルールに従ってウィンドウ、タブ、および新しいインスタンスを作成できるようにします。

(07:48) 複数のウィンドウによってもたらされる最初の実際的な問題は、状態の境界です。 BookClub の例では、各ウィンドウで選択される書籍は異なります。 1 つのウィンドウの選択を変更しても、他のウィンドウには影響しません。共有データ モデルはアプリ層に配置され、ウィンドウ内のビュー ステートは独立したままになります。これは SwiftUI シーン アーキテクチャの中核です。

(12:08) プラットフォームがシーンの作成と破棄を担当する場合、開発者は各ウィンドウのローカル状態を復元する必要もあります。 SceneStorage はビューステートを一意のキーで保存し、SwiftUI はそれを自動的に保存し、適切なタイミングで復元します。後半では、3 つの展開方向も示しています。DocumentGroup はドキュメント ウィンドウを管理し、Settings は macOS 環境設定を管理し、commands 修飾子はメニュー コマンドを管理します。

##詳細

アプリ、シーン、ビューのレベル

(03:57) サンプル アプリにはエントリ タイプが 1 つだけあります。 BookClubAppApp プロトコルに準拠し、bodyWindowGroup を返し、WindowGroupReadingListViewer を作成します。

@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
}

キーポイント:

  • @mainBookClubApp をプログラム エントリ ポイントにし、セッションはこれが Swift 5.3 の新しい属性であることを明確に述べています。
  • アプリのコンテンツはシーンで構成されているため、「BookClubApp」の「body」は「何らかのシーン」を返します。
  • WindowGroup はメインインターフェイスのシーン定義です。システムは、プラットフォームの機能に基づいてウィンドウまたはタブを作成します。
  • @StateObject はアプリ層で ReadingListStore を所有し、ReadingListViewer@ObservedObject を通じてこのモデルを使用します。
  • .navigationTitle("Currently Reading") はナビゲーション バーのタイトルを表示するだけではありません。セッション デモは、App Switcher と macOS ウィンドウのタイトルにも影響します。

WindowGroup のマルチウィンドウ ステータス

(10:21) 「WindowGroup」ビュー定義は、プラットフォームによって複数回インスタンス化できます。 macOS および iPadOS では、ユーザーがメニュー項目、ショートカット キー、またはマルチタスク ジェスチャを通じて新しいウィンドウを作成すると、各シーンに独自のビュー ステートが存在します。

@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
}

キーポイント:

  • store は共有モデルであり、各ウィンドウは同じ書籍リストを読み取ることができます。
  • ReadingListViewer はウィンドウ コンテンツのテンプレートであり、プラットフォームが新しいウィンドウを必要とするときに新しい Scene インスタンスが作成されます。
  • ビュー ステートはそれぞれのシーンに属し、セッションは「異なるウィンドウで選択された異なるブック」を使用してこの境界を示します。
  • WindowGroup は、macOS の [ファイル] メニューに新しいウィンドウ エントリを提供し、標準の Command-N ショートカット キーをサポートします。

SceneStorage は各ウィンドウの選択内容を復元します

(12:07) プラットフォームがシーンのライフサイクルを管理した後、ローカルのビュー状態には回復メカニズムが必要になります。この例では、選択した書籍を文字列として保存し、それを Binding<UUID?> に変換して NavigationLink に渡します。

@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
}

キーポイント:

  • @SceneStorage("selectedItem") 一意のキーを使用して、保存する状態を識別します。
  • SceneStorage は単純な回復可能な値を格納するため、selectedItem には文字列が格納されます。
  • selectedID は文字列を UUID に変換し、それを Binding にラップします。
  • NavigationLink は、tagselection を使用して選択された項目をバインドし、復元後に対応するブックに戻ることができます。
  • SwiftUI は、適切なタイミングでこの状態を保存および復元する役割を担うため、開発者はウィンドウのライフサイクル ロジックを手動で記述する必要がありません。

DocumentGroup はドキュメント ウィンドウを処理します

(12:59) Document Apps は、ルート シーンを DocumentGroup に置き換えることができます。ドキュメント シーンを開いて編集し、保存する役割を果たします。この例では、ShapeDocument を使用して、FileDocument の読み取りおよび書き込みエントリを示します。

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)
    }
}

キーポイント:

  • DocumentGroup(newDocument:) は、新しいドキュメントのデフォルト値を定義します。
  • file.$documentDocumentView にドキュメント バインディングを提供し、インターフェイスはドキュメントのコンテンツを直接編集できます。
  • ShapeDocumentCodableに準拠しており、例ではJSONDecoderJSONEncoderを使用して読み書きを完了します。
  • UTType(exportedAs:) はカスタム ドキュメント タイプを宣言します。
  • readableContentTypes、初期化メソッドおよびFileDocumentwriteメソッドは、ドキュメントの読み取りと書き込みの境界を構成します。

設定とコマンドはデスクトップ アプリのエクスペリエンスを補完します

(13:27) macOS の共通環境設定ウィンドウは、「設定」シーンとして記述することができます。 session は、Preferences コマンドを自動的に設定し、ウィンドウに正しいスタイルを与えることを示します。

@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()
    }
}

キーポイント:

  • @SceneBuilder を使用すると、アプリの body で複数のシーンを同時に宣言できます。
  • WindowGroup は依然としてメインインターフェイスです。
  • 「設定」は macOS ブランチの下にのみ表示されます。
  • BookClubSettingsView は設定ウィンドウのコンテンツです。

(14:07) メニュー コマンドは「Commands」タイプを使用してカプセル化され、コマンド モディファイアを通じてシーンに追加されます。この例のコマンドは、現在のフォーカスに基づいて有効または無効になります。

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()
    }
}

キーポイント:

  • BookCommandsCommandsに準拠しており、メニューロジックをタイプに分けることができます。
  • CommandMenu("Book") はカスタム メニューを作成します。
  • 「ボタン」はアクションやキーボード ショートカットにバインドできます。
  • .disabled(selectedBook == nil) は、フォーカス状態に基づいてコマンドのセット全体を無効にします。
  • @FocusedBinding(\.selectedBook) は、コマンドを現在のユーザー フォーカスに向けさせ、セッションはそれを AppKit または UIKit のレスポンダー チェーンと比較します。

重要ポイント

  • 対処方法: SwiftUI App のエントリ ポイントを @main + App プロトコルに変更します。 実行する価値がある理由: セッションに表示される BookClubApp は、1 つのタイプを使用して入り口、共有モデル、メイン シーンを同時に宣言するため、スタートアップ コードがインターフェイス構造に近くなります。 開始方法: まず struct YourApp:App を作成し、bodyWindowGroup を配置し、元の最初の画面 View を WindowGroup のコンテンツとして使用します。

  • 内容: iPadOS および macOS ユーザーにマルチウィンドウ機能を追加します。 実行する価値がある理由: WindowGroup はプラットフォームのルールに従って複数のウィンドウまたはタブを作成します。BookClub の例は、各ウィンドウが独立した選択ステータスを保持できることを証明しています。 開始方法: メイン インターフェイスを WindowGroup に配置し、共有データを App レイヤーの @StateObject に配置し、ウィンドウ内の選択、スクロール、またはナビゲーション状態を View レイヤーに残します。

  • 内容: SceneStorage を使用して、各ウィンドウのローカル状態を復元します。 実行する価値がある理由: セッションは、シーンのライフサイクルがプラットフォームによって管理され、「SceneStorage」がキーを使用してビュー状態を自動的に保存および復元することを明確に示しています。 開始方法: 復元する必要がある単純な値に @SceneStorage("key") を追加し、必要に応じて、例に示すように文字列とビジネス ID の間でバインド変換を実行します。

  • 対処方法: ドキュメントベースのツールには、代わりに DocumentGroup を使用してください。 実行する価値がある理由: ShapeEdit の例では、ドキュメントの作成、開く、編集、保存をシーン定義に置き、ドキュメントの読み取りと書き込みは「FileDocument」に集中します。 開始方法: FileDocument に準拠するドキュメント タイプを定義し、UTType を宣言してから、DocumentGroup(newDocument:) を使用してルート シーンを作成します。

  • 対処方法: 環境設定ウィンドウとメニュー コマンドを macOS バージョンに追加します。 実行する価値がある理由: セッションの「設定」と「コマンド」を使用すると、環境設定、メニュー項目、ショートカット キー、フォーカス状態をすべて SwiftUI の宣言モデルに従うことができます。 開始方法: @SceneBuilderSettings シーンを追加し、Commands に準拠したタイプを作成し、CommandMenu@FocusedBinding を使用してコマンドを整理します。

関連セッション

  • SwiftUI の概要 - まず SwiftUI の宣言型 View モデルを理解してから、このセッションの App、Scene、WindowGroup レベルを読みます。
  • SwiftUI でドキュメントベースのアプリを構築する — このセッションの ShapeEdit の例を結び付けながら、DocumentGroupFileDocument、およびドキュメント管理について詳しく説明します。
  • SwiftUI のデータの要点 — 引き続き「@State」、「@Binding」、「ObservableObject」を学習して、App、Scene、View 間のデータ転送を完了します。
  • SwiftUI の新機能 — 2020 SwiftUI グローバル アップデートからのアプリ、シーン、ウィジェット、コンプリケーションなどの新機能を見てください。

コメント

GitHub Issues · utterances