ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
tvOS向けのSwiftUI Appを構築する
SwiftUIでtvOS Appに新たな側面をもたらしましょう。ここでは、SwiftUIによってレイアウトを構築し、カスタムボタンでインターフェイスをカスタマイズし、コンテキストメニューでAppの機能性を高め、ビューのフォーカスを確認し、デフォルトのフォーカスを管理する方法をご紹介します。このセッションを有効活用するには、SwiftUIを使いこなせることが望まれます。まずは"Introducing SwiftUI: Building Your First App"と"SwiftUI On All Devices"をご覧ください。
リソース
- CardButtonStyle
- Human Interface Guidelines: Designing for tvOS
- isFocused
- Learn to Make Apps with SwiftUI
- prefersDefaultFocus(_:in:)
- Supporting Multiple Users in Your tvOS App
- SwiftUI
関連ビデオ
WWDC20
WWDC19
-
ダウンロード
こんにちは WWDCにようこそ “tvOS向けのSwiftUI Appを構築する” 私はタヌ・シンガル tvOSチームのエンジニアです Apple TV向けSwiftUI Appの 構築についてお話します 新規APIを紹介し ベストプラクティスを説明した後 TVユーザーにとって馴染みやすい体験を作り出す 一助となる事例を見ていきます
最初にtvOS 14の新機能 ボタンスタイルと コンテクストメニューについて説明し
次にApp上でフォーカスを 管理する方法をお話します
最後に家庭の大画面で美しく見える レイアウトの作成方法を学びましょう TVには固有のボタンスタイルがあります 例をご覧ください Apple TVで音楽をストリーミングする Appを構築するとします これはデザインチームから受け取った モックアップです Siri Remoteでドラッグした時にボタンが動く 効果をつけてほしいという指定がありました この“アルバム”の下の ボタンのような感じです
SwiftUIでこのようなボタンを作成するには 新しいCardButtonStyleが使えます
CardButtonでフォーカスした時に浮き上がり 強調されたように見えるベースが作れます さらにSiri Remoteでドラッグした方向に動く 効果も加わります
CardButtonの作成方法は ボタンにボタンスタイルモディファイアをつけ CardButtonStyleを設定するだけです
CardButtonStyleがあれば ボタンのアピアランスが各段に向上します しかし 既存のボタンスタイルに 設定してあるような 強調やフォーカスの効果を 使いたくない場合もあるでしょう そのような時は独自に ボタンスタイルを作ることもできます この場合 プレスやフォーカスの状態に 既存の効果はついていません カスタムボタンスタイルを使えば 設定やカスタマイズがとても簡単です
独自のボタンスタイルを作るにはまず ButtonStyleプロトコルに準拠させてください
makeBodyメソッド内で設定を活用し あらゆるビューを呼び出せます
設定したボタンスタイルはボタンスタイル モディファイアを使ったボタンに追加できます これでAppのボタンの設定ができましたが 他にも追加したい機能があります アルバムのボタンを長押しした時に この表示のような クイックアクションを起こしたいのです
コンテクストメニューを使えば 簡単にSwiftUIに実装できます コンテクストメニューをボタンやビューに追加し 長押しの操作をした時に 呼び出すことができます コンテクストメニューに通常のボタンを使った アクションを足すこともできます
これはコンテクストメニューの作り方を示す コードの例です コンテクストメニューモディファイアをつけ その中にボタンを追加するだけです ご覧のように新しいボタンスタイルと コンテクストメニューは非常に簡単に使えます きっとApp上で映えるでしょう 次にフォーカスについて説明します これはTV Appとやり取りをする際の要です ビューにフォーカスできるだけでなく ビューがフォーカスされているか 判断できることは非常に重要です 先ほどの音楽 Appを例に フォーカスについて詳しく見ていきましょう
“再生中”の画面で 現在再生中の楽曲にフォーカスしましょう フォーカスしているこの楽曲の アーティスト名のほか アルバム名と音符の絵文字も 表示させたいと思います
フォーカスしていない楽曲はアルバム名を除いて アーティスト名だけを表示します
この動きを実装するには まずSongViewにフォーカスする必要があります フォーカス可能なモディファイアを使うのが 1つの方法です フォーカス可能なモディファイアで既存のビューの トップにフォーカス可能なラッパーを作ります
このモディファイアは元来フォーカス可能な ビュー用ではないことに注意してください ですからUIKitでフォーカスを管理するボタンや リスト UIKitビューがある場合 そこではフォーカス可能なモディファイアを 使わない方がよいでしょう このモディファイアは既存のビューのトップに別の フォーカス可能なラッパーを追加するからです tvOS 13でフォーカス可能および フォーカス不可能な状態を管理する時は フォーカス可能なモディファイアにonFocusChange コールバック関数を使う必要がありました
しかしtvOS 14では新たに isFocused環境変数を導入しています
これによって特定のビューが フォーカスされているかチェックできます ビュー自体は フォーカス不可であっても構いません isFocused環境変数はビュー内の 直近のフォーカス可能な祖先が フォーカスされている場合にtrueを返します
では前に示したSongViewのコードに戻りましょう これはテキストラベルがついた 画像が1点あります テキストラベルをDetailsViewに リファクタリングします
DetailsView内で環境変数を使えば isFocusedはこのビューが フォーカスされているか否かをチェックします DetailsViewの親である SongViewがフォーカスされている場合 isFocused変数はtrueです
このisFocused変数を使ってアーティスト名や アルバム名を絵文字と共に表示したり 表示がフォーカスされていない時 アーティスト名だけ表示したりするのも可能です SongViewではボタンを使うので フォーカス可能なモディファイアを使う必要は 一切ありません もともとボタンはフォーカス可能だからです ボタンを使うことで選択とアクセシビリティを 無料で活用できるのもメリットです
既存のボタンスタイルについている既定の強調と フォーカスの効果を使いたくないので ここではオリジナルのボタンスタイルを 追加しました
以上でAppの設定はほぼ完了し いつでも音楽の ストリーミングを開始できるようになりました
ただユーザーがプレミアムコンテンツを聴けるよう このAppの有料版も追加したいです 有料版を作成するには 最初にサインイン画面を設定しましょう
ユーザー名とパスワード欄に ログインボタンが並んだシンプルな画面です 初期設定でユーザー名欄が フォーカスされていることが分かります tvOSはローディング時にフォーカスすべき ビューを幾何学的に計算します 一般的には画面の最上段や 先頭のフォーカス可能なビューにあたります この場合 ユーザー名が フォーカスされているのは当然でしょう でも既にユーザー名やパスワードを知っていて 代わりにログインボタンに フォーカスしたい場合もあります
この動作をSwiftUIに実装するには 新たな初期設定のフォーカスAPIを使います
prefersDefaultFocusモディファイアを紹介しましょう 初期設定でフォーカスしたいビューを 指定することができます 小さなカスタムビューを設定している時に 全体のグローバルビューの階層を 誤って変更しないように注意してください この懸念を払しょくするために focusScopeモディファイアを用意しました これによってフォーカスの初期設定変更を 特定のビューに制限することができます 先ほど見たログイン画面のコードを 見てみましょう VStack TextField SecureFieldと ボタンがシンプルに並んでいます ここにフォーカスを管理するコードを追加します すぐに読解できなくても心配ありません 1行ずつ見ていきましょう 認証情報が入力されているかを確認する 状態変数があります ここでユーザー名のTextFieldに prefersDefaultFocusモディファイアを足します モディファイアの最初のパラメータはブーリアン関数で このビューをフォーカスの初期設定にした場合に trueを返します この場合 認証情報が未入力の時は 初期設定よりユーザー名を優先しフォーカスします 同じモディファイアをログインボタンにも追加します 認証情報が入力されている時は 初期設定のフォーカスを優先します 次にフォーカスの優先を設定中の VStackに制限してみましょう そのためにはネームスペースを作成します ネームスペースは動的プロパティで ビューの特定に用いる固有の IDが使えるようになります このネームスペースをVStackに追加した focusScopeモディファイアに足しましょう
次に同じネームスペースを 2つ目のパラメータとして prefersDefaultFocusモディファイアに渡します これで初期設定の変更が VStackのみに適用されるようになりました ですからフォーカスをVStack内のみに設定すると 認証情報が入力されているか否か次第で それぞれユーザー名や ログインボタンがフォーカスされます しかしフォーカスがビュー階層の 別のどこかに当たるべき場合は 意向どおりモディファイアは グローバルのフォーカスには影響しません フォーカスの初期設定を指定する他に フォーカスをリセットしたい場合もあるでしょう それも新しいresetFocus環境アクションを使えば 対応可能です この環境アクションは フォーカスを初期設定にリセットします この場合もフォーカスの更新は 与えたネームスペースの範囲内にとどまります 先ほど見たサインイン画面の例で やり直しボタンも追加したいとしましょう ユーザー名とパスワードを消去し フォーカスをユーザー名に戻すボタンです 実装するには resetFocus環境アクションを使います やり直しボタンが発動したら focusScopeにあるのと同じネームスペースに resetFocusを呼び出します この時点で認証情報は消去されているので ユーザー名をフォーカスするようリセットされます このセクションではisFocus環境変数に加え フォーカスの初期設定を操作できる新規モディファイアや 環境アクションについて説明しました SwiftUI Appを構築する際に とても役立つと思います 最後にApple TVで一般的にみられる レイアウトの構築方法を知ってください これは先ほど見た 音楽ストリーミングAppのビューです さまざまなApple TV Appで 同様のレイアウトを見たことがあるでしょう これは水平にスクロールするシェルフで 構成されています この画面を実装する際は 新しいLazy Gridを使います
これは垂直または水平にスクロールするグリッド コンテナ内に子ビューを配置するものです グリッドアイテムでサイズや配置 間隔など レイアウトの構築に役立つ プロパティを指定できます グリッドの詳細は次の2つの セッションをチェックしてください このセッションではLazy Gridを活用し 先ほどのようなレイアウトの構築法を学びます ShelfViewの中に水平にスクロールする ScrollViewを作成し 水平のScrollViewの中に LazyHGridを追加しました このLazyHGridには多数のアイテムを追加でき 一度にすべてが初期化されることはありません スクロール中 必要な時にローディングされます
Lazy GridにGridItemも設定します GridItemサイズはフレキシブルに固定でき GridItem間のスペースもカスタマイズ可能です
Lazy Gridの中にセル内で提示したい コンテンツを追加するだけです シェルフが整ったので 複数のシェルフをVStack内で積み重ねます テキストラベルもつけて 先ほど見たようなレイアウトを作ります Apple TVに美しいグリッドを表示させるのは こんなに簡単です 以上を要約しますと tvOS 14の新しいCardButtonStyleと コンテクストメニューをぜひお試しください とても簡単に使えてApp上できれいに映えます
新たなフォーカスAPIを使うと App内のフォーカスを管理しやすくなり 非常に便利です
最後にLazy Gridを活用すれば 短時間でtvOSのレイアウトを構築できます これから皆さんがSwiftUIで 構築するプロダクトを楽しみにしています それではWWDCが 素晴らしい体験になりますように
-
-
1:42 - CardButtonStyle
Button(albumLabel, action: playAlbum) .buttonStyle(CardButtonStyle())
-
2:24 - Custom Button Styles
struct MyNewButtonStyle: ButtonStyle { func makeBody(configuration: Configuration) -> some View { configuration.label .background(configuration.isPressed ? … : …) // Custom styling } } Button(albumLabel, action: playAlbum) .buttonStyle(MyNewButtonStyle())
-
3:19 - Context Menus
AlbumView() .contextMenu { Button("Add to Favorites", action: addAlbumToFavorites) Button("View Artist", action: viewArtistPage) Button("Discover Similar Albums", action: viewSimilarAlbums) }
-
5:47 - isFocused Environment Variable
struct SongView: View { var body: some View { Button(action: playSong) { VStack { Image(albumArt) DetailsView(...) } }.buttonStyle(MyCustomButtonStyle()) } } struct DetailsView: View { ... @Environment(\.isFocused) var isFocused: Bool var body: some View { VStack { Text(songName) Text(isFocused ? artistAndAlbum : artistName) } } }
-
8:42 - Login Screen (Default Focus)
var body: some View { VStack { TextField("Username", text: $username) SecureField("Password", text: $password) Button("Log In", action: logIn) } }
-
8:51 - Default Focus
@Namespace private var namespace @State private var areCredentialsFilled: Bool var body: some View { VStack { TextField("Username", text: $username) .prefersDefaultFocus(!areCredentialsFilled, in: namespace) SecureField("Password", text: $password) Button("Log In", action: logIn) .prefersDefaultFocus(areCredentialsFilled, in: namespace) } .focusScope(namespace) }
-
11:12 - Reset Focus
@Namespace private var namespace @State private var areCredentialsFilled: Bool @Environment(\.resetFocus) var resetFocus var body: some View { VStack { TextField("Username", text: $username) .prefersDefaultFocus(!areCredentialsFilled, in: namespace) SecureField("Password", text: $password) Button("Log In", action: logIn) .prefersDefaultFocus(areCredentialsFilled, in: namespace) Button("Clear", action: { username = ""; password = "" areCredentialsFilled = false resetFocus(in: namespace) }) } .focusScope(namespace) }
-
12:45 - Lazy Grids
struct ShelfView: View { var body: some View { ScrollView([.horizontal]) { LazyHGrid(rows: [GridItem()]) { ForEach(playlists, id: \.self) { playlist in Button(action: goToPlaylist) { Image(playlist.coverImage) .resizable() .frame(…) } .buttonStyle(CardButtonStyle()) } } } } }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。