创建watchOS应用

这篇教程让我们可以应用之前所学到的SwiftUI知识,把Landmarks应用从iOS平台迁移到watchOS平台上。在拷贝可以共用的数据和视图文件之前,需要先给项目中添加一个对应watchOSTarget编译目标,assets资源保持原状,只需要调整SwiftUI视图以适应在watchOS平台上展示就可以了。

按照下面的步骤构建工程,或者下载完成后的项目文件学习。


第一节 添加一个watchOS编译目标

要创建一个watchOS应用,第一步是给项目添加一个对应watchOS平台的编译目标。Xcode在新增Target的同时,添加一个文件组和相应的文件到工程中,同时还会新增编译运行方案,指定应用要运行的平台或模拟器。

section 1

步骤1 选择菜单File->New->Target。当模板列表出现后,在watchOS选项卡下选择Watch App for iOS App模板,并点击下一步(Next)。

section 1 step 1 1

section 1 step 1 2

选择这个模板会添加一个新的watchOS应用到工程中,嵌入到iOS应用平级。

步骤2 在模板创建表中,输入WatchLandmarks作为产品名称,设置语言为Swift,用户界面为SwiftUI实现方式,并勾选通知应用场景,最后点击完成(Finish)。

section 1 step 2 1

section 2 step 2 2

步骤3 如果Xcode提示激活watchOS平台编译运行方案,点击激活(Activate)。这会把编译运行方案从iOS平台切换到watchOS平台上来。

步骤4 在扩展TargetWatchLandmarks Extensions的通用(General)选项卡下勾选Supports Running Without iOS App Installation,尽量创建一个可以独立于iOS宿主运行的watchOS应用。

section 2 step 4

第二节 在Target间共享文件

现在watchOS平台的编译目标(Target)已经创建好,为了避免重复工作,可以复用一些之前在iOS项目中的资源。地标的数据模型文件可以复用,一些资源文件以及一些两个平台下不需要修改就可以展示的视图文件也可复用。

section 2

步骤1 在项目导航器中,按下Command+鼠标左键的同时,选中文件:LandmarkRow.swiftLandmark.swiftUserData.swiftData.swiftProfile.swiftHike.swiftCircleImage.swift

section 2 step 1

其中Landmark.swiftUserData.swiftData.swiftProfile.swiftHike.swift这几个文件定义了应用的数据模型。我们不会使用这些文件中的所有内容,但为了编译通过需要这些文件。LandmarkRow.swiftCircleImage.swift这两个文件是不需要任何修改就可以在watchOS平台上展示的视图。要注意Data.swift文件中是否导入ImageIO模块,如果没有import ImageIO这一句,编译时会有报错。

步骤2 上一步选中需要文件的情况下,在文件检查器中,勾选WatchLandmarks Extension,让之前选中的文件也变成WatchLandmarks Extension编译时用到的文件。

section 2 step 2

步骤3 在项目导航器中,选中Landmarks文件组下的Assets.xcassets文件,并在文件检查器中把它添加到WatchLandmarks编译运行目标中。这里的编译运行目标和上一步选中的编译运行目标不相同。WatchLandmarks Extension是用来放置应用的代码,WatchLandmarks是用来管理storyboard、图标以及相关资源的。

section 2 step 3

步骤4 在项目导航器中,选择Resources文件夹下的所有文件,并在文件检查器中把它们添加到WatchLandmarks Extension编译目标下。

section 2 step 4

第三节 创建详情视图

iOS编译目标下的资源可以在手表应用下使用,但我们需要创建一个专门适配手表尺寸的地标详情页来展示地标的具体信息。为了测试视图是否能适配手表展示,需要分别为最大尺寸和最小尺寸手表创建预览视图,并根据情况适当的调整圆形视图的布局来适应手表的界面大小。

section 3

步骤1 在项目导航器中,点击WatchLandmarks Extension文件夹左边的三角形展开箭头,查看文件夹下的具体内容,并添加一个名为WatchLandmarkDetailSwiftUI视图。

section 3 step 1

步骤2 在结构体WatchLandmarkDetail结构体中添加userDatalandmarklandmarkIndex属性。这些新增和属性与在处理用户输入时在LandmarkDetail中添加的属性是对等的。

section 3 step 2

添加完属性后发现,Xcode会报缺少参数的错误。要修复这个错误有两种方式,一种是为属性提供一个默认值,一种是给视图的属性传入对应的值。

步骤3 在预览视图中,创建一个用户数据的实例,并给WatchLandmarkDetail结构体的初始化器中传入一个地标对象作为参数。这里需要把用户数据设置为视图的环境对象。

section 3 step 3

步骤4WatchLandmarkDetail.swift文件中,body()方法中返回一个CircleImage视图。这里的CircleImage是复用iOS项目中的视图,因为创建的图片是可缩放的,可以调用.scaleToFill()属性修改器让圆的大小添满整个手表显示屏。

section 3 step 4

步骤5 创建最大尺寸(44mm)和最小尺寸(38mm)的手表预览视图。通过测试在最大最小尺寸上的展示情况,查看应用UI是否有问题,通常情况下,需要测试所有不同尺寸显示屏上UI展示情况是否符合预期。

section 3 step 5

CircleImage缩放到高度完全填充显示器高度,但这种情况下适配了高度,宽度却被截断了。为了修复这个截断问题,需要把CircleImage视图嵌入到一个VStack视图容器中,并作一些调整,让CircleImage可以在所有尺寸的手表显示屏上正常展示。

步骤6CircleImage嵌入到VStack中,并在图片下方显示地标名称信息。

section 3 step 6

很明显,此时的信息一屏展示不下,所以需要把视图内容放入到ScrollView中,以获取滚动查看的功能。

步骤7VStack整体嵌入到一个ScrollView中,这就让视图获取了滚动查看的能力,但同时也引入了另一个问题:CircleImage现在扩展到完全尺寸,把其它元素挤到没有地方显示。所以需要缩放CircleImage,让圆形图片和地标名称可以在一屏内同时显示出来。

section 3 step 7

步骤8 改变scaleToFill()scaleToFit(),这就让图片缩放按照显示器的宽度进行适配。

section 3 step 8

步骤9

为了让地标名称在地标图片的下面展示了来,添加padding量来解决。

section 3 step 9

步骤10 给返回按钮添加一个标题文本。这个返回按钮是看不见的,只有把LandmarksList视图加上后,才会出现返回按钮。

section 3 step 10

第四节 添加一个watchOS地图视图

既然已经创建了详情页,现在就可以添加一个地图视图用来显示地标的地理位置了。不像CircleImage,这里就不能复用iOS应用的MapView了。需要创建一个WKInterfaceObjectRepresentable结构体来包装一个WatchKit地图。

section 4

步骤1WatchKit Extension中添加一个名为WatchMapView的视图。

section 4 step 1

步骤2WatchMapView遵循WKInterfaceObjectRepresentable协议而不是View协议。

section 4 step 2

目前,因为还没有实现协议WKInterfaceObjectRepresentable协议的方法,所以Xcode会报错。

步骤3 删除body()方法,并用属性landmark替换它。当创建一个地图视图时,需要给这个landmark属性传入一个值。像在预览视图中传入一个地标实例数据一样。

section 4 step 3

步骤4 实例协议WKInterfaceObjectRepresentable方法makeWKInterfaceObject(context:)。这个协议方法创建WatchKit地图视图WatchMapView

section 4 step 4

步骤5 实现协议方法updateWKInterfaceObject:(_:, context:),根据地标的坐标设置地图展示区域,现在项目可以编译通过了。

section 4 step 5

步骤6WatchLandmarkDetail.swift文件中把WatchMapView添加在VStack的底部。在地图上面添加一上分割器。使用.scaledToFit().paddding()来修改地图尺寸,让它更适合屏幕。

section 4 step 6

第五节 创建跨平台列表视图

对于地标列表,可以复用iOS应用中列表的行元素视图,但不同的平台下需要展示适合平台的详情页。这就需要把LandmarkList视图转换成一个通用的列表视图。

section 5

步骤1 在工具条中,选择Landmarks方案,让Xcode现在的编译运行方案指向iOS平台。这样做是为了确保对LandmarkList视图的重构不会影响原来在iOS平台时的表现,这样就可以在不影响iOS平台的条件下,把LandmarkList重构后应用到watchOS应用中。

section 5 step 1

步骤2 切换到文件LandmarkList.swift,把类型声明为范型。改造为范型后,当要创建一个LandmarkList结构的实例时,会报范型参数类型无法推断的错误。这将在下面的步骤中解决。

section 5 step 2

步骤3 添加一个闭包属性,用来创建详情视图。

section 5 step 3

步骤4 使用detailViewProducer属性为地标创建详情视图。当需要创建一个LandmarkList时,需要提供一个闭包来创建对应地标的详情视图

section 5 step 4

步骤5 切换到Home.swift文件,在CategoryHome结构体的body属性中添加一个闭包用来创建详情视图。Xcode会根据闭包的返回值类型推断出LandmarkList结构体的范型参数类型。

section 5 step 5

步骤6LandmarkList.swift文件中,给预览视图添加相似的代码。为了适配不同的设备平台,这里需要使用条件编译,为不同平台编译不同的代码。

section 5 step 6

第六节 添加地标列表

目前已经将LandmarkList视图改造为可以同时兼容watchOSiOS两个平台,现在就可以把它应用在watchOS平台下的应用中了。

section 6

步骤1 在文件检查器中,将LandmarkList.swift添加为WatchLandmark Extension编译目标的成员。现在就可以在watchOS的应用中使用LandmarkList.swift这份代码文件了。

section 6 step 1

这一步其实在第五节步骤六已经做过了。

步骤2 在工具条上,切换编译方案为Watch Landmarks

section 6 step 2

步骤3 打开LandmarkList.swift文件,并对该视图进行预览. Command + Option + Enter打开预览画布,Command + Option + P启动预览。因为现在编译方案已经切换为watchOS平台,所以现在预览视图里显示的是手表预览。watchOS平台的应用根视图是ContentView,目前显示的是Hello, World文字。

section 6 step 3

步骤4 修改ContentView让它显示地标列表

section 6 step 4

步骤5 在模拟器上构建并运行watchOS应用。滚动地标列表,点击查看地标详情,标记地标为收藏状态,点击返回按钮从地标详情页返回到地标列表页,打开收藏开关,只查看补收藏的地标。测试一下watchOS应用的功能是否正常。

section 6 step 5

第七节 创建自定义通知界面

watchOS平台的Landmarks应用已经接近完成了。在最后一节中,会创建一个通知界面,当用户的地理位置靠近自己收藏过的地标位置时会收到通知提示用户,通知界面展示当前正在接近的地标相关信息。本节只讲当用户收到通知时怎样显示通知界面,不涉及怎样设置和发送通知给用户的内容。

section 7

步骤1 打开NotificationView.swift文件并创建一个显示地标信息、标题及消息的视图。由于任何通知都可能为nil,预览视图会展示两种不同的通知视图。第一个展示在没有数据时按默认值显示的视图,第二个展示有标题、消息及位置数据时的视图。

section 7 step 1

步骤2 打开NotificationController添加landmarktitlemessage属性。这些属性值存储用户收到的通知值。

section 7 step 2

步骤3 更新body()方法,在其内部使用这些属性。在body方法内初始化之前创建的NotificationView

section 7 step 3

步骤4NatificationController中定义LandmarkIndex键,使用这个键从通知中提取地标的下标值。

section 7 step 4

步骤5 使用didReceive(_:)方法从收到的通知中解析相关数据值。这个方法会更新控件器的属性值。调用这个方法后,系统会让控制器的body属性失效,引起导航视图更新,之后系统会把通知界面显示在Apple Watch上。

section 7 step 5

Apple Watch接收到通知时,它会创建NotificationController并把它与通知的类型关联起来。为了给NotificationController设置类型,必须打开并编辑应用的storyboard

步骤6 在项目导航器中,选择WatchLandmarks文件夹,并打开Interface.storyboard文件,然后选中指向静态通知界面控制器(static notification interface controller)的箭头。

section 7 step 6

步骤7 在属性检查器中,设置通知类别的名称为LandmarkNear

section 7 step 7

使用LandmarkNear类别配置测试数据(payload),并把它传给NotificationController

步骤8 项目导航器中选中WatchLandmarks Extensions文件夹,打开Push NotificationPayload.apns文件,更新titlebodycategorylandmarkIndex属性值。确保设置categoryLandmarkNear。除此之外还需要删除所有在本教程中不需要的其它无关字段,例如subtitleWatchKit Simulator ActionscustomKey。Payload文件是用来模拟从服务端发送的远程推送的数据。

section 7 step 8

步骤9 选择编译方案WatchLandmarks(Notificaton),编译并运行应用。第一次运行通知方案时,系统会请求发送通知的权限,选择允许(Allow)获取发送通知的权限。模拟器会展示一个可滚动的通知:一个标识应用身份的横幅、通知视图以及一个点击按钮用来响应通知被点击后的动作。

section 7 step 9 1

section 7 step 9 2

检查是否理解

问题1 当要在iOS工程中添加一个watchOS编译目标时,应该选用哪个应用模板?

problem 1

  • App
  • Watch App for iOS App
  • Game App

问题2 下面哪一个watchOS应用元素可以通过使用SwiftUI来实现?

  • 只用watchOS应用的视图可以使用SwiftUI实现
  • watchOS应用的视图以及自定义通知界面
  • watchOS应用的视图、自定义通知界面以及complications
  • watchOS应用的视图、自定义通知界面、complicatons以及Siri卡片

问题3 为什么不能在watchOS平台上复用LandmarkDetail视图?

  • 因为LandmarkDetail视图是为大屏设备设计的
  • 因为watchOS的用户界面应该只展示最重要的信息,并提供访问额外详情的快速访问方式
  • MapView不能在watchOS上使用,因为它遵循UIViewRepresentable协议,这个协议在watchOS平台上不可用
  • 以上所有

问题4 为什么需要针对watchOS平台来修改LandmarkList视图?

  • 当用户点击列表中的某一项时,需要改变列表展示的详情视图
  • LandmarkList不包含任何平台相关的代码,所以要针对不同平台进行不同的布局
  • 要支持多平台,必须使用范型
  • 必须为每个平台重新设计视图

问题5 哪一个通知界面可以使用SwiftUI开发?

problem 5

  • 只有动态交互界面可以
  • 静态和动态交互界面都可以