WWDC Quick Look 💓 By SwiftGGTeam
What's new in SwiftUI

What's new in SwiftUI

元の動画を見る

ハイライト

SwiftUI 2020 は、AppSceneDocumentGroupWidgetList(children:)LazyVGridToolbarItemmatchedGeometryEffectLinkopenURLUTType、および SignInWithAppleButton を追加します 同じ宣言型モデルのセットに接続することで、アプリケーション portals, windows, documents, widgets, complex lists, toolbars, animations, and system services can all be implemented directly using SwiftUI.

主要内容

SwiftUI の初年度の焦点は View でした。開発者は宣言型インターフェイスを作成できますが、完成したアプリはポータル、ウィンドウ、メニュー、ドキュメント、リスト機能、ツールバー、アクセシビリティ、およびシステム サービスも処理します。 The task of this session is to take these daily tasks back into SwiftUI’s declarative model.

(01:14) 最初に、完全なアプリが指定されています。@main が入り口をマークし、AppbodyScene を返し、WindowGroup がビューをホストします。システムは、プラットフォームのルールに従って、このステートメントを iOS の全画面ウィンドウ、iPadOS のマルチウィンドウ、macOS のマルチウィンドウおよびメニュー コマンドにマッピングします。

(09:18) The second problem is content scale. Ordinary lists and handwritten navigation will soon encounter hierarchical data, grid content, and scrolling performance. SwiftUI 2020 では、アウトライン、遅延グリッド、遅延スタックが追加されているため、ギャラリー、サイドバー、再帰リストを引き続きデータ駆動型の記述で表現できます。

(12:19) 最後の部分はプラットフォーム エクスペリエンスに戻ります。 SwiftUI のエントリには、ツールバー、ラベル、ヘルプ ヒント、キーボード ショートカット、進行状況コントロール、ゲージ、一致したジオメトリ、動的フォント スケーリング、リンク、openURL、Uniform Type Identifiers、および Apple でサインインがあります。 Developers can access these system capabilities without leaving SwiftUI.

##詳細

1. App and Scene become the entrance to SwiftUI

(01:26) The first clip officially displayed is a complete App. The App protocol and WindowGroup write the application entry into a declarative structure similar to View.

@main
struct HelloWorld: App {
    var body: some Scene {
        WindowGroup {
            Text("Hello, world!").padding()
        }
    }
}

キーポイント:

  • @main marks the application entry, replacing the manual life cycle entry in traditional templates.
  • HelloWorld complies with App, and body returns Scene.
  • WindowGroup はここではシーンです。 the transcript indicates that it will provide corresponding window behavior on different platforms.
  • Text("Hello, world!").padding() is the SwiftUI View in the window.

(04:46) It is also possible to combine Settings Scenes on macOS. The system sets up the standard preference menu and gives the window the correct style.

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

キーポイント:

  • @StateObject はリーディング リストをアプリ層に保存し、ウィンドウ内のビューによって監視されます。
  • @SceneBuilder を使用すると、アプリで複数のシーンを宣言できます。
  • WindowGroup はメイン ウィンドウを担当します。
  • 「設定」は macOS 上でのみコンパイルされ、環境設定ウィンドウを担当します。

2. DocumentGroup がドキュメントのオープン、編集、保存を引き継ぎます。

(05:10) ドキュメントベースのアプリは「DocumentGroup」を使用します。トランスクリプトは、ドキュメントのオープン、編集、保存のシナリオを自動的に管理し、iOS、iPadOS、macOS で利用できると説明しています。

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.$document は、ドキュメントのコンテンツをバインディングとして DocumentView に渡します。
  • UTType(exportedAs:) はアプリケーション独自のファイル タイプを宣言します。
  • FileDocumentinitwrite はドキュメント データの読み取りと書き込みを担当します。

(05:49) Document App ではメニュー コマンドを追加することもできます。 commands はプラットフォームの適切な場所にカスタム メニューを配置し、keyboardShortcut はショートカット キーを表示します。

DocumentGroup(newDocument: ShapeDocument()) { file in
    DocumentView(document: file.$document)
}
.commands {
    CommandMenu("Shapes") {
        Button("Add Shape...", action: addShape)
            .keyboardShortcut("N")
        Button("Add Text", action: addText)
            .keyboardShortcut("T")
    }
}

キーポイント:

  • 「コマンド」は単一のビューではなくシーンにハングされます。
  • CommandMenu("Shapes") はカスタム メニューを作成します。
  • 2 つの Button はそれぞれアクションにバインドされています。
  • keyboardShortcut は、メニュー項目のキーボード ショートカットを宣言します。

3. リスト、グリッド、遅延スタックは大量のコンテンツを処理します

(09:22) リストには、再帰的な階層データを直接表示できる新しい子キー パスが追加されます。この機能により、コンテンツベースのアプリで頻繁に行われるプッシュ アンド ポップ ナビゲーションが軽減されます。

struct OutlineContentView: View {
    var graphics: [Graphic]

    var body: some View {
        List(graphics, children: \.children) { graphic in
            GraphicRow(graphic)
        }
        .listStyle(SidebarListStyle())
    }
}

struct Graphic: Identifiable {
    var id: String
    var name: String
    var icon: Image
    var children: [Graphic]?
}

キーポイント:

  • List(graphics, Children: \.children) は、各ノードの子を読み取ります。
  • Graphicchildren は、展開可能なノードまたはリーフ ノードに対応するオプションの配列です。
  • SidebarListStyle() はサイドバー スタイルを使用します。
  • 各ノードは引き続き通常の SwiftUI View でレンダリングされます。

(10:09) グリッドは遅延読み込みレイアウトを使用します。 GridItem(.adaptive(minimum: 176)) は列数を利用可能な幅に調整します。

struct ContentView: View {
    var items: [Item]

    var body: some View {
        ScrollView {
            LazyVGrid(columns: [GridItem(.adaptive(minimum: 176))]) {
                ForEach(items) { item in
                    ItemView(item: item)
                }
            }
            .padding()
        }
    }
}

キーポイント:

  • ScrollView はスクロール コンテナを提供します。
  • LazyVGrid は、オンデマンドでグリッド コンテンツのみを読み込みます。
  • GridItem(.adaptive(minimum: 176)) は、列の数を空間の変化に追従させます。
  • ForEach(items) はデータ配列をセルにマップします。

(10:58) カスタムの長いリストは「LazyVStack」で使用できます。公式ギャラリーの例では、ビュー ビルダーが同じスタック内の複数のレイアウトを切り替えることができる「switch」をサポートしていることも示しています。

struct WildlifeList: View {
    var rows: [ImageRow]

    var body: some View {
        ScrollView {
            LazyVStack(spacing: 2) {
                ForEach(rows) { row in
                    switch row.content {
                    case let .singleImage(image):
                        SingleImageLayout(image: image)
                    case let .imageGroup(images):
                        ImageGroupLayout(images: images)
                    case let .imageRow(images):
                        ImageRowLayout(images: images)
                    }
                }
            }
        }
    }
}

キーポイント:

  • カスタムスクロールレイアウトに適した「LazyVStack」。
  • switch row.content は、データ型ごとに異なるビューを選択します。
  • SingleImageLayoutImageGroupLayout、および ImageRowLayout が一緒になって不規則なギャラリーを形成します。
  • トランスクリプトには、これらのコンテンツが結合されてシームレスなギャラリーを形成していることが明確に記載されています。

4. ツールバー、ラベル、ヘルプ プロンプト、および新しいコントロールによりプラットフォームの相互作用が統合されます

(12:24) SwiftUI に新しい「ツールバー」修飾子が追加されました。ツールバー項目は通常のビューにすることができ、デフォルトでプラットフォームの優先場所に配置されます。

struct ContentView: View {
    var body: some View {
        List {
            Text("Book List")
        }
        .toolbar {
            Button(action: recordProgress) {
                Label("Record Progress", systemImage: "book.circle")
            }
        }
    }

    private func recordProgress() {}
}

キーポイント:

  • toolbar はビューにハングされ、ツールバーのコンテンツはその中に配置されます。
  • Button は通常の SwiftUI コントロールです。
  • Label はタイトルとアイコンの両方を提供します。
  • トランスクリプトの説明ツールバーは、プラットフォームのデフォルトの場所に表示されます。

(12:40) 位置を制御する必要がある場合は、「ToolbarItem」を使用します。主な操作、確認操作、キャンセル操作、主な位置などのセマンティック配置を宣言できます。

struct ContentView: View {
    var body: some View {
        List {
            Text("Book List")
        }
        .toolbar {
            ToolbarItem(placement: .primaryAction) {
                Button(action: recordProgress) {
                    Label("Record Progress", systemImage: "book.circle")
                }
            }
        }
    }

    private func recordProgress() {}
}

キーポイント:

  • ToolbarItem はツールバー コントロールをラップします。
  • .primaryAction はコントロールの役割を記述します。
  • SwiftUI はプラットフォームに基づいて特定の場所を決定します。
  • Label はデフォルトでツールバーにアイコンを表示し、タイトルはアクセシビリティのために使用されます。

(15:28) 「help」修飾子は macOS ではツールチップになり、アクセシビリティのヒントも提供します。

struct ContentView: View {
    var body: some View {
        Button(action: recordProgress) {
            Label("Progress", systemImage: "book.circle")
        }
        .help("Record new progress entry")
    }

    private func recordProgress() {}
}

キーポイント:

  • helpでは制御効果について説明します。
  • macOS はこれをツールチップとして表示します。
  • トランスクリプト 説明 すべてのプラットフォームで VoiceOver ヒントも提供します。

(17:08) 新しいコントロールは、進行状況と容量の表現をカバーします。 「ProgressView」は明確または不確実な進捗状況を表し、「Gauge」は容量に対する特定の値の位置を表します。

struct ContentView: View {
    var percentComplete: Double

    var body: some View {
        ProgressView("Downloading Photo", value: percentComplete)
    }
}

キーポイント:

  • percentComplete は決定された進行状況の値です。
  • ProgressView はタイトルと進行状況の両方を受け取ります。
  • トランスクリプトは、直線と円形の 2 つのスタイルがあると説明しています。

5. アニメーション、応答性の高いビジュアル、およびシステム サービスには直接 SwiftUI API があります

(19:17) matchedGeometryEffect は 2 つの領域の同じ要素を接続します。この例では、アルバムがグリッドから選択された行に移動すると、SwiftUI はフレームを補間して連続トランジションを形成します。

struct ContentView: View {
    @Namespace private var namespace
    @State private var selectedAlbumIDs: Set<Album.ID> = []

    var body: some View {
        VStack(spacing: 0) {
            ScrollView {
                albumGrid.padding(.horizontal)
            }

            Divider().zIndex(-1)

            selectedAlbumRow
                .frame(height: AlbumCell.albumSize)
                .padding(.top, 8)
        }
        .buttonStyle(PlainButtonStyle())
    }

    private var albumGrid: some View {
        LazyVGrid(columns: [GridItem(.adaptive(minimum: AlbumCell.albumSize))], spacing: 8) {
           ForEach(unselectedAlbums) { album in
              Button(action: { select(album) }) {
                 AlbumCell(album)
              }
              .matchedGeometryEffect(id: album.id, in: namespace)
           }
        }
    }

    private var selectedAlbumRow: some View {
        HStack {
            ForEach(selectedAlbums) { album in
                AlbumCell(album)
                .matchedGeometryEffect(id: album.id, in: namespace)
            }
        }
    }
}

キーポイント:

  • @Namespace は、幾何学的マッチングのための名前空間を提供します。
  • 2 つの matchedGeometryEffect は同じ album.id を使用します。
  • グリッドと選択された行は、同じ識別子のセットを共有します。
  • withAnimation(.spring(response: 0.5)) は公式の完全なフラグメントに表示され、選択アニメーションをトリガーするために使用されます。

(20:34) レスポンシブ タイポグラフィも強化されました。カスタム フォントはダイナミック タイプでスケールされ、非テキスト メトリクスは @ScaledMetric を使用できます。

struct ContentView: View {
    var album: Album
    @ScaledMetric private var padding: CGFloat = 10

    var body: some View {
        VStack {
            Text(album.name)
                .font(.custom("AvenirNext-Bold", size: 30))

            Text("\(Image(systemName: "music.mic")) \(album.artist)")
                .font(.custom("AvenirNext-Bold", size: 17))

        }
        .padding(padding)
        .background(RoundedRectangle(cornerRadius: 16, style: .continuous).fill(Color.purple))
    }
}

キーポイント:

  • @ScaledMetric は、padding を Dynamic Type に従います。
  • カスタム フォントは引き続き .font(.custom(...)) を使用します。
  • Image(systemName:)Text に埋め込むことができ、テキストの一部としてフォント サイズの変更に応答します。

(23:15) システム統合パートでは、まず「Link」と「openURL」について説明します。 「Link」は URL またはユニバーサル リンクを開きます。「openURL」は、View 環境のコードから開くことをトリガーするために使用されます。

let customPublisher = NotificationCenter.default.publisher(for: .init("CustomURLRequestNotification"))
let apple = URL(string: "https://developer.apple.com/tutorials/swiftui/")!

struct ContentView: View {
    @Environment(\.openURL) private var openURL

    var body: some View {
        Text("OpenURL Environment Action")
            .onReceive(customPublisher) { output in
                if output.userInfo!["shouldOpenURL"] as! Bool {
                    openURL(apple)
                }
            }
    }
}

キーポイント:

  • @Environment(\.openURL) は、環境内の URL を開くアクションを読み取ります。
  • onReceive はパブリッシャーをリッスンします。
  • 条件が満たされた後、openURL(apple) を呼び出します。
  • トランスクリプト 説明 SwiftUI は、それを含むウィンドウに関連する URL を開きます。

(25:16) Apple でのサインインには SwiftUI コントロールもあります。 AuthenticationServicesSwiftUIの両方をインポートするだけで使用できます。

import AuthenticationServices
import SwiftUI

struct ContentView: View {
    var body: some View {
        SignInWithAppleButton(
            .signUp,
            onRequest: handleRequest,
            onCompletion: handleCompletion
        )
        .signInWithAppleButtonStyle(.black)
    }

    private func handleRequest(request: ASAuthorizationAppleIDRequest) {}
    private func handleCompletion(result: Result<ASAuthorization, Error>) {}
}

キーポイント:

  • AuthenticationServices と SwiftUI の組み合わせからの SignInWithAppleButton
  • .signUp はボタンの目的を指定します。
  • onRequestonCompletion は認可リクエストと結果を処理します。
  • .signInWithAppleButtonStyle(.black) は正式なスタイルを設定します。

重要ポイント

  • マルチウィンドウの読み取りまたはデータ管理アプリを作成する: AppWindowGroup、および @StateObject を使用して共有モデルを配置し、同じ SwiftUI 入口セットで iPhone、iPad、Mac、Apple Watch、Apple TV をカバーできるようにします。まず、BookClub の構造に従ってエントリを構築し、「設定」シーンを macOS に追加します。
  • 単純なドキュメント エディタを作成します: DocumentGroup を使用して開く、編集および保存を管理し、FileDocument を使用してモデルを読み書きし、UTType(exportedAs:) を使用してファイル タイプを宣言します。最初のバージョンでは、タイトル フィールドと DocumentView のみが必要で、その後、commands を使用してメニュー アクションを追加します。
  • 大量のコンテンツを保持できるギャラリーを作成します: LazyVGrid を使用してアダプティブ グリッドを作成し、LazyVStack を使用して不規則な長いリストを処理し、matchedGeometryEffect を使用してアイテムの動きに連続アニメーションを追加します。まず、公式のアルバム セレクター構造を再利用し、データを画像またはビデオのサムネイルに置き換えることができます。
  • 運用アプリの完全なプラットフォーム操作の詳細: 「toolbar」、「ToolbarItem」、「Label」、「help」、および「keyboardShortcut」を使用して、共通の操作をプラットフォーム上の適切な場所に配置します。詳細ページから開始して、保存、キャンセル、共有、および主な操作を対応するプレースメントにマップします。
  • システム機能を SwiftUI ページに統合: Link を使用してアプリ内のチュートリアル、ニュース、またはユニバーサル リンクを処理し、openURL を使用して非同期イベントに応答し、SignInWithAppleButton を使用してログイン プロセスを組み込みます。最初のステップは、外部リンクとログイン ボタンを独立した SwiftUI ビューに作成し、それらを既存のページに埋め込むことです。

関連セッション

コメント

GitHub Issues · utterances