Build SwiftUI apps for tvOS
Swift & UI 进阶 22m

用 SwiftUI 构建 tvOS App

Build SwiftUI apps for tvOS

2020年6月26日

在 Apple 官方观看视频

一句话判断

tvOS 终于支持 SwiftUI——但电视的交互模型(遥控器聚焦、无触摸、无多任务)意味着你需要重新思考 SwiftUI 视图的交互设计,不是简单地把 iOS App 搬过去。

这场 Session 讲了什么

SwiftUI 在 tvOS 14 上首次可用,覆盖了大部分核心组件:Text、Image、Button、List、NavigationView、TabView 等。但 tvOS 有独特的交互模型,需要针对性适配。

核心差异在于焦点系统。Apple TV 用遥控器操作,通过方向键在可交互元素之间移动焦点。SwiftUI 的 focusable() 修饰符控制视图是否可获焦,prefersDefaultFocus() 控制初始焦点位置。.onPlayPauseCommand() 处理遥控器的播放/暂停按钮。

另一个重要特性是 tvOS 的全屏卡片布局。电视屏幕大、观看距离远,UI 元素需要更大、更清晰。Session 展示了如何用 ButtonNavigationLink 构建适合电视的卡片式导航界面。

值得深挖的点

焦点管理的挑战。 iOS 上的触摸交互是”所见即所得”,但 tvOS 上的焦点导航是线性的。复杂的布局可能导致焦点跳转不符合预期。SwiftUI 提供了 focusScope()prefersDefaultFocus() 来控制焦点区域和初始位置,但大面积使用时仍然需要仔细测试。

tvOS 的视觉设计原则。 电视屏幕大、观看距离 2-3 米,文字和控件需要比 iOS 大得多。Session 建议 minimum hit target 为 120pt(对比 iOS 的 44pt),文字最小 29pt。间距也要加大,避免视觉拥挤。

代码片段

import SwiftUI

// tvOS 基本的卡片布局
struct MovieCatalogView: View {
    let movies: [Movie]
    
    var body: some View {
        ScrollView(.horizontal) {
            LazyHStack(spacing: 40) {  // tvOS 需要更大的间距
                ForEach(movies) { movie in
                    NavigationLink(destination: MovieDetailView(movie: movie)) {
                        MovieCard(movie: movie)
                            .frame(width: 300, height: 450)  // 电视上的卡片尺寸
                    }
                    .buttonStyle(PlainButtonStyle())
                }
            }
            .padding(60)  // 更大的内边距
        }
    }
}

struct MovieCard: View {
    let movie: Movie
    
    var body: some View {
        VStack {
            movie.posterImage
                .resizable()
                .aspectRatio(contentMode: .fill)
                .frame(height: 380)
                .cornerRadius(12)
            
            Text(movie.title)
                .font(.system(size: 29))  // tvOS 推荐的最小字号
                .lineLimit(2)
        }
    }
}
// 焦点控制
struct VideoPlayerView: View {
    @FocusState private var isPlayerFocused: Bool
    
    var body: some View {
        VStack {
            VideoPlayerView(video: video)
                .focusable()  // 可以接收遥控器焦点
                .focused($isPlayerFocused)
            
            HStack(spacing: 40) {
                Button("倒退 10 秒") { seekBackward() }
                Button("播放/暂停") { togglePlayback() }
                    .onPlayPauseCommand { togglePlayback() }  // 遥控器播放键
                Button("前进 10 秒") { seekForward() }
            }
            .prefersDefaultFocus(in: controlsScope)  // 初始焦点
        }
    }
}
// tvOS 的 TabView 侧边导航
struct MainTabView: View {
    var body: some View {
        TabView {
            Tab("首页", systemImage: "house") {
                HomeView()
            }
            Tab("搜索", systemImage: "magnifyingglass") {
                SearchView()
            }
            Tab("我的", systemImage: "person") {
                ProfileView()
            }
        }
    }
}

最佳实践

  • 最小触控区域 120pt。 遥控器的焦点移动需要足够大的目标区域。
  • 文字最小 29pt。 电视观看距离远,小文字不可读。
  • 大幅增加间距和内边距。 iOS 上合适的间距在电视上会显得拥挤。
  • 优先测试焦点导航路径。 用遥控器模拟器遍历所有界面,确保焦点跳转合理。

还有什么值得关注

  • tvOS 的 onSwipeCommand 可以处理遥控器的滑动手势
  • .focusEffectDisabled() 可以禁用某个视图的焦点高亮效果
  • tvOS SwiftUI 支持暗色模式的系统默认主题
  • Top Shelf Extension 可以配合 SwiftUI 展示 tvOS 主屏推荐内容
WWDC 2020