由于 API 变动,此文章部分内容已失效,最新完整中文教程及代码请查看 https://github.com/WillieWangWei/SwiftUI-Tutorials
微信技术群
SwiftUI
代表未来构建 App 的方向,欢迎加群一起交流技术,解决问题。
加群现在需要申请了,可以先加我微信,备注 “SwiftUI”,我会拉你进群。
创建 watchOS App
本教程为你提供一个将你已经学到的关于
SwiftUI
的知识应用到自己的产品上的机会,并且不费吹灰之力就可以将 Landmarks app 迁移到 watchOS 上。首先,给项目添加一个 watchOS target,然后复制为 iOS app 中创建的共享数据和视图。当所有资源都准备好后,你就可以通过自定义 SwiftUI 视图,在 watchOS 上显示详细信息和列表视图。
下载项目文件并按照以下步骤操作,也可以打开已完成的项目自行浏览代码。
- 预计完成时间:25 分钟
- 项目文件:下载
1. 添加一个 watchOS Target
要创建 watchOS app,首先要给项目添加一个 watchOS target。
Xcode 会将 watchOS app 的组和文件,以及构建和运行 app 所需的 scheme 添加到项目中。
1.1 选择 File
> New
> Target
,当模版表单显示后,选择 watchOS
标签,选择 Watch App for iOS App
模版后点击 Next
。
这个模版会给项目添加一个新的 watchOS app,将 iOS app 与它配对。
1.2 在表单的 Product Name
中输入 WatchLandmarks
,将 Language
设置成 Swift
,将 User Interface
设置成 SwiftUI
。勾选 Include Notification Scene
复选框,然后点击 Finish
。
1.3 Xcode 弹出提示,点击 Activate
。
这样选择 WatchLandmarks
scheme 后,就可以构建和运行你的 watchOS app 了。
Whenever possible, create an independent watchOS app. Independent watchOS apps don’t require an iOS companion app.
1.4 在 WatchLandmarks Extension
的 General
标签中,勾选 Supports Running Without iOS App Installation
复选框。
尽可能创建一个独立的 watchOS app。独立的 watchOS app 不需要与 iOS app 配套使用。
2. 在多个 Target 中共享文件
设置了 watchOS target 后,你需要从 iOS target 中共享一些资源。比如重用 Landmark app 中的数据模型,一些资源文件,以及任何不需要修改就可以跨平台显示的视图。
2.1 在项目导航器中,按住 Command
键然后点击选中以下文件:LandmarkRow.swift
, Landmark.swift
, UserData.swift
, Data.swift
, Profile.swift
, Hike.swift
, CircleImage.swift
。
Landmark.swift
, UserData.swift
, Data.swift
, Profile.swift
, Hike.swift
定义了 app 的数据模型。虽然你不会用到所有这些模型,但是需要保证这些文件都编译到了 app 中。 LandmarkRow.swift
和 CircleImage.swift
是两个不修改就可以显示在 watchOS 中的视图。
2.2 打开 File
检查器,勾选 Target Membership
中的 WatchLandmarks Extension
复选框。
这会让你在上一步中选择的文件在 watchOS app 中可用。
2.3 打开项目导航器,在 Landmark
组中选择 Assets.xcassets
,然后在 File
检查器的 Target Membership
中将它添加到 WatchLandmarks
target。
这与你上一步选择到 target 不一样, WatchLandmarks Extension
target 包含你的 app 的代码,而 WatchLandmarks
target 则管理你的故事板,图标和相关资源。
2.4 在项目导航器中,选择 Resources
文件夹中的所有文件,然后在 File
检查器的 Target Membership
中将它们添加到 WatchLandmarks Extension
target。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qhg4zWC8-1571757358455)(https://upload-images.jianshu.io/upload_images/4518631-210ebb2a6e00d7c5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
3. 创建详情视图
现在 iOS target 的资源在 watch app 上已经可用了,你需要创建一个 watch 独有的视图来显示地标详情。为了测试这个视图,你需要给最大和最小 watch 尺寸创建自定义预览,然后给圆形视图做一些修改来适配 watch 显示。
3.1 在项目导航器中,单击 WatchLandmarks Extension
文件夹旁边的显示三角形来显示其内容,然后添加一个新 SwiftUI
视图,命名为 WatchLandmarkDetail
。
3.2 给 WatchLandmarkDetail
结构体添加 userData
, landmark
和 landmarkIndex
属性。
这些和你在 处理用户输入 中添加到 LandmarkDetail
结构体中的属性是一样的。
WatchLandmarkDetail.swift
import SwiftUI
struct WatchLandmarkDetail: View {
//
@EnvironmentObject var userData: UserData
var landmark: Landmark
var landmarkIndex: Int {
userData.landmarks.firstIndex(where: {
$0.id == landmark.id })!
}
//
var body: some View {
Text("Hello World!")
}
}
struct WatchLandmarkDetail_Previews: PreviewProvider {
static var previews: some View {
WatchLandmarkDetail()
}
}
在上一步添加属性后,你会在 Xcode 中得到一个缺少参数的错误。为了修复这个错误,你需要二选一:提供属性的默认值,或传递参数来设置视图的属性。
3.3 在预览中,创建一个用户数据的实例,然后用它给 WatchLandmarkView
结构体的初始化传递一个地标对象。另外还需要将这个用户数据设置成视图的环境对象。
WatchLandmarkDetail.swift
import SwiftUI
struct WatchLandmarkDetail: View {
@EnvironmentObject var userData: UserData
var landmark: Landmark
var landmarkIndex: Int {
userData.landmarks.firstIndex(where: {
$0.id == landmark.id })!
}
var body: some View {
Text("Hello World!")
}
}
struct WatchLandmarkDetail_Previews: PreviewProvider {
static var previews: some View {
//
let userData = UserData()
return WatchLandmarkDetail(landmark: userData.landmarks[0])
.environmentObject(userData)
//
}
}
3.4 在 WatchLandmarkDetail.swift
中,从 body()
方法里返回一个 CircleImage
视图。
这就是你从 iOS 项目中复用 CircleImage
视图的地方。因为创建了可调整大小的图片, .scaledToFill()
的调用会让圆形的尺寸自动适配显示。
WatchLandmarkDetail.swift
import SwiftUI
struct WatchLandmarkDetail: View {
@EnvironmentObject var userData: UserData
var landmark: Landmark
var landmarkIndex: Int {
userData.landmarks.firstIndex(where: {
$0.id == landmark.id })!
}
var body: some View {
//
CircleImage(image: self.landmark.image.resizable())
.scaledToFill()
//
}
}
struct WatchLandmarkDetail_Previews: PreviewProvider {
static var previews: some View {
let userData = UserData()
return WatchLandmarkDetail(landmark: userData.landmarks[0])
.environmentObject(userData)
}
}
3.5 给最大 (44mm) 和最小 (38mm) 表盘创建预览。
通过针对最大和最小表盘的测试,你可以看到你的 app 是如何缩放来适配显示的。与往常一样,你应该在所有支持的设备尺寸上测试用户界面。
WatchLandmarkDetail.swift
import SwiftUI
struct WatchLandmarkDetail: View {
@EnvironmentObject var userData: UserData
var landmark: Landmark
var landmarkIndex: Int {
userData.landmarks.firstIndex(where: {
$0.id == landmark.id })!
}
var body: some View {
CircleImage(image: self.landmark.image.resizable())
.scaledToFill()
}
}
struct WatchLandmarkDetail_Previews: PreviewProvider {
static var previews: some View {
let userData = UserData()
//
return Group {
WatchLandmarkDetail(landmark: userData.landmarks[0]).environmentObject(userData)
.previewDevice("Apple Watch Series 4 - 44mm")
WatchLandmarkDetail(landmark: userData.landmarks[1]).environmentObject(userData)
.previewDevice("Apple Watch Series 2 - 38mm")
}
//
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y9ikqOvT-1571757358457)(https://upload-images.jianshu.io/upload_images/4518631-1519d175447e7144.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]
圆形图片重新调整大小来适配显示的高度。但不幸,这依然裁剪了圆形的宽度。为了修复这个裁剪问题,你需要把图片嵌入到一个 VStack
中,并且做一些额外的布局修改来让圆形图片适配任何 watch 的宽度。
3.6 把图片嵌入到一个 VStack
中,在图片下面显示地标的名字和它的信息。
如你所见,信息并没有完全适配 watch 的屏幕,但是你可以通过将这个 VStack
放在一个滚动视图中来修复这个问题。
WatchLandmarkDetail.swift
import SwiftUI
struct WatchLandmarkDetail: View {
@EnvironmentObject var userData: UserData
var landmark: Landmark
var landmarkIndex: Int {
userData.landmarks.firstIndex(where: {
$0.id == landmark.id })!
}
var body: some View {
//
VStack {
CircleImage(image: self.landmark.image.resizable())
.scaledToFill()
Text(self.landmark.name)
.font(.headline)
.lineLimit(0)
Toggle(isOn:
$userData.landmarks[self.landmarkIndex].isFavorite) {
Text("Favorite")
}
Divider()
Text(self.landmark.park)
.font(.caption)
.bold()
.lineLimit(0)
Text(self.landmark.state)
.font(.caption)
}
//
}
}
struct WatchLandmarkDetail_Previews: PreviewProvider {
static var previews: some View {
let userData = UserData()
return Group {
WatchLandmarkDetail(landmark: userData.landmarks[0]).environmentObject(userData)
.previewDevice("Apple Watch Series 4 - 44mm")
WatchLandmarkDetail(landmark: userData.landmarks[1]).environmentObject(userData)
.previewDevice("Apple Watch Series 2 - 38mm")
}
}
}
3.7 将竖直 stack 包装中一个滚动视图中。
这让视图可以滚动,但是带来了另外一个问题:圆形图片展开到了全屏,并且调整了其他 UI 元素来匹配这个图片。你需要调整这个圆形图片的大小来让它和地标名字显示在屏幕上。
WatchLandmarkDetail.swift
import SwiftUI
struct WatchLandmarkDetail: View {
@EnvironmentObject var userData: UserData
var landmark: Landmark
var landmarkIndex: Int {
userData.landmarks.firstIndex(where: {
$0.id == landmark.id })!
}
var body: some View {
//
ScrollView {
VStack {
CircleImage(image: self.landmark.image.resizable())
.scaledToFill()