大多数浏览器和
Developer App 均支持流媒体播放。
-
为 tvOS 构建 SwiftUI app
使用 SwiftUI 为你的 tvOS app 添加新的维度。我们将向你展示如何构建由 SwiftUI 驱动的布局以及如何使用自定义按钮自定义界面,如何使用环境菜单在应用程序中提供更多功能,检查视图是否是焦点并管理默认焦点。 为了充分利用本视频,你需要先适应 SwiftUI。有关入门知识,请观看“ SwiftUI 简介:构建你的第一个 app”和“在所有设备上使用 SwiftUI”。
资源
- 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
-
下载
(你好) (2020 全球开发者大会) 你好 欢迎来到全球开发者大会 大家好 我叫 Tanu Singhal 是 Apple tvOS 团队的一位工程师 (为 tvOS 构建 SwiftUI app) 今天我们要讨论的是 为 Apple TV 构建 SwiftUI apps 我们将引入新的 API、探讨最佳做法 接着 会通过一些实例来帮大家创建出 TV 用户所熟悉的界面和外观体验 首先 我们会讨论 Apple tvOS 14 上 新的按钮样式和右键菜单
接着 我们会谈到 apps 的聚焦管理
最后 我们会学习如何创建布局 即便放在家里最大的屏幕上也依旧美观 有一些按钮样式是 TV 特有的 我们来看一个例子 比如说我们要在 Apple TV 上 构建一个音乐流媒体 app
这是设计团队发来的一个模型 (热门专辑) 他们让我们创建的这些按钮 需要在拖动 Siri Remote 时 产生挪动的效果 如“专辑”下方的按钮所示 在 SwiftUI 中 我们可以使用 新的“卡片按钮样式”创建出这样的按钮
一个卡片按钮会创建一个盘 在被选中聚焦时 会升起并显示高亮效果 当你拖动 Siri Remote 时 它还会使按钮向不同方向移动
要想创建一个卡片按钮 你只要将按钮样式修饰符添加到任意按钮 然后设置成 CardButtonStyle
CardButtonStyle 会给按钮外观带来显著提升 不过 也许在某些情况下 你并不想要预设按钮样式中 默认的高亮和聚焦效果 这种情况下 你可以创建自定义按钮样式 它不会给按选和聚焦状态添加预设效果 而且自定义按钮样式的用户化设定 十分简单易上手
要想创建自定义按钮样式 你需要先与 ButtonStyle 协议相匹配…
接着在 makeBody 方法中 通过调整配置来返回任何视图
按钮样式设定好之后 就可以通过按钮样式修饰符 添加到任意按钮上了 现在我们设置好了 app 的按钮 而这时又收到了另一项功能请求 当长按专辑按钮时 我们想要唤出一些快捷操作 如屏幕中所示 (添加到收藏夹 查看艺术家 类似专辑) 这些都能在 SwiftUI 中 运用右键菜单轻松实现 右键菜单可以被添加进任何按钮或视图中 并通过长按手势唤出
我们可以用常规按钮 在右键菜单中加入操作
这里的代码展示了如何创建一个右键菜单 只要加入右键菜单修饰符 我们就能在其中添加一些按钮了 如你所见 新的按钮样式 和右键菜单都非常易于使用 我们认为以上这些功能 会给你的 apps 带来不错的效果
接下来 我们谈谈聚焦 聚焦是我们与 TV app 交互的主要方式 因此 能够聚焦于视图 以及判断聚焦是否在某个视图上 就显得极为重要
为了进一步了解聚焦 我们来看音乐 app 的另一个例子
现在屏幕上显示的是“正在播放” 我们想要聚焦在正在播放的歌曲上 对于聚焦中的歌曲 我们希望显示艺术家的名字 和专辑名称 以及一些音乐表情符号 对于非聚焦状态的歌曲 我们不显示专辑名称 而只显示艺术家的名字
要想实现这点 首先 我们要能够聚焦在歌曲视图上 一种方法是使用可聚焦修饰符 可聚焦修饰符 会在已有视图上创建一层可聚焦包装
要注意的是 这个修饰符 并不适用于本身就可聚焦的视图 也就是说 如果你有一个按钮或是菜单 或者是一个 UIKit 视图 可以在 UIKit 中管理聚焦 那最好不要在那之上添加可聚焦修饰符 因为这个修饰符会在已有视图上 再添加一层可聚焦包装 为了在 Apple tvOS 13 上管理聚焦状态 你必须通过聚焦变更回调 来使用可聚焦修饰符
但在全新的 Apple tvOS 14上 我们引进了 isFocused 环境变量
即使在一个视图不可聚焦的情况下 也能帮你检测它是否处于聚焦状态
如果视图的最近可聚焦祖先处于聚焦状态 那么isFocused环境变量将返回真值
现在我们来看之前歌曲视图的代码
里面只包含一张图片 以及后面的一些文本标签
我们将文本标签重构成了 DetailsView
在 DetailsView 内 我们就可以使用 isFocused 环境变量 来检测这个视图是否处于聚焦状态
所以 如果歌曲视图 也就是 DetailsView 的父视图是聚焦的 那我们的 isFocused 变量就为真
我们会根据这个 isFocused 变量 来显示艺术家姓名 和专辑名 以及表情符号 而在视图处于非聚焦状态时 只显示艺术家姓名
要注意 在歌曲视图下 我们使用的是一个按钮 所以完全不需要用到可聚焦修饰符 因为按钮本身已经是可聚焦的了
使用按钮还有另外一个优势 就是附带选择和辅助功能
之前我们已经添加了自定义的按钮样式 因为我们不想要预设按钮样式中 默认的高亮和聚焦效果
到了这一步 我们的 app 已经基本设置好了 可以开始使用音乐流媒体服务了
但我们还想添加一个付费版本的 app 使用户能够收听高端内容
要想创建付费版本 我们首先要设置一个登录页面 (我的音乐 用户名 登录) 这个页面只有用户名和密码输入区 以及下方的登录按钮 可以看到 用户名输入区是默认聚焦的 Apple tvOS 会运用几何学 计算出视图加载后应该聚焦的区域 一般是最顶端区域 或者是页面上主要的可聚焦视图
这个案例中 用户名输入区处于聚焦状态是合理的
但有时 用户名和密码已经填写好了 这时我们就会希望登录按钮是聚焦的 (登录) 要在 SwiftUI 中实现这点 我们可以使用全新的默认聚焦 API
这里我们引入 prefersDefaultFocus 修饰符 它使你能够指定 默认状态下应该获取聚焦的视图 此外 我们还要确保 当你在小自定义视图上操作时 不会意外地改变 全局视图层级的聚焦 为了支持这点 我们创建了聚焦范围修饰符 它能将你的默认聚焦偏好 限制在一个特定的视图内 我们现在来看之前登录页面的代码 这是一个 VStack 函数 包含一个文本区 一个安全区和一个按钮 现在我要加入一些聚焦管理的代码
一下子看不过来没关系 我们会逐条进行讲解
我们用一个状态变量 来识别凭证是否输入完成 现在我们在文本区 加入偏好默认聚焦修饰符 这个修饰符的第一个参数是一个Boolean 如果视图偏好为默认聚焦 则它应该是真值 在这个例子中 输入凭证前 用户名偏好为默认聚焦 我们在登录按钮上也添加同样的修饰符 但只有在输入凭证后 它的偏好才会是默认聚焦
接着 我们想把聚焦偏好限制在 我们当前所处的 VStack 函数上 为此 我们要创建一个命名空间 命名空间是一种动态属性 它能为我们提供一个独一无二的 ID 可以用来识别任意视图
我们将这个命名空间 添加到之前已经被我们放入 VStack 函数中的聚焦范围修饰符
接着 我们将同样的命名空间 传递给偏好默认聚焦修饰符 来作为第二个参数 这样一来 默认聚焦偏好就设置好了 而且只应用于这个 VStack 函数 所以如果只需要在 VStack 函数内聚焦 那么根据凭证的输入状态 用户名或者登录按钮其一就会获取聚焦 但如果聚焦应该在视图层级的其它区域 我们的修饰符就不会影响到全局的聚焦 这正是我们想要的 除了设置默认聚焦 我们有时候还需要重置聚焦 这可以通过 全新的重置聚焦环境操作来完成 这个环境操作会将聚焦重置为默认状态 同理 聚焦的变更 只会在你提供的命名空间范围内 再回到之前登录页面的例子上 假设我们还想添加一个清除按钮 用来清除用户名和密码 并且将聚焦重置到用户名上 要实现这点 我们就会用到重置聚焦环境操作
接着 在点击清除按钮后 我们在原本的聚焦范围内 对同一个命名空间调用重置聚焦
由于凭证在这个时候已经被清除了 聚焦就会被重置回用户名上 通过以上这部分内容 我们学习了 isFocused 环境变量 以及新的修饰符和环境操作 它们都可以用来控制默认聚焦 我们认为这些内容对于你创建 自己的 SwiftUI apps 会非常有帮助
最后 我们来学习 如何构建 Apple TV 上常见的布局 这是我们之前看过的 音乐流媒体 app 的视图 你或许在各种 Apple TV 的 apps 上 见过类似的布局
它由一排排水平滚动的框架组成
我们可以用 新的 Lazy Grids 实现这个界面
Lazy Grids 将子视图 整理在一个网格容器内 可以水平或垂直滚动
网格单元可以指定 像是尺寸、对齐方式和间距之类的属性 有助于构建布局 你可以从以下两场讲座中 了解到更多关于网格的信息 (《SwiftUI 新增功能》 《SwiftUI 的叠放、网格与轮廓》) 不过本期内容中 我们会学习如何借助 Lazy Grid 来构建我们之前看到的布局
在“框架视图”内 我们创建了一个水平滚动的“滚动视图”
在水平滚动视图中 我们加入了一个 LazyHGrid
我们可以往 LazyHGrid 里 添加许多单元 它们并不会全部同时初始化 而是在我们滚动过程中 需要它们的时候 才会加载
我们还需要设置 Lazy Grid 的 GridItem GridItem 的尺寸 可以是灵活的 也可以是固定的 并且我们可以自定义 GridItem 的间距
在 Lazy Grid 中 我们只需添加想要显示在单元格里的内容
这样 我们的框架就完成了 然后 我们可以将多组框架 在 VStack 函数中堆叠 再添加一些文本标签 就能创建出我们之前看到的布局
在 Apple TV 中 创建美观的网格布局就是如此轻而易举 总而言之 我们鼓励大家尝试 Apple tvOS 14 中 全新的卡片按钮样式和右键菜单
它们易于使用 而且能给你的 apps 带来非常棒的效果
新的聚焦 API 可以帮助你 更好地进行 apps 的聚焦管理 我们相信这些会对你非常有帮助
最后 我们希望你可以借助 Lazy Grids 在 Apple tvOS 中快速构建布局 我们迫不及待地想要看到 你用 SwiftUI 构建的下一个作品了 谢谢大家 希望你们度过一场精彩的全球开发者大会 (2020 全球开发者大会) (你好) (2020全球开发者大会)
-
-
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()) } } } } }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。