SwiftUI 官方教程 (十)

这篇教程介绍了如何使用 SwiftUI 创建一个 watchOS 应用,包括添加 watchOS 目标,共享文件,创建详情视图,地图视图,跨平台列表视图,以及自定义通知界面。内容涵盖了目标设置,资源共享,视图创建和适配,以及处理通知显示。
摘要由CSDN通过智能技术生成

由于 API 变动,此文章部分内容已失效,最新完整中文教程及代码请查看 https://github.com/WillieWangWei/SwiftUI-Tutorials

image

微信技术群

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 添加到项目中。

image

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

image

1.3 Xcode 弹出提示,点击 Activate

这样选择 WatchLandmarks scheme 后,就可以构建和运行你的 watchOS app 了。

image

Whenever possible, create an independent watchOS app. Independent watchOS apps don’t require an iOS companion app.

1.4 在 WatchLandmarks ExtensionGeneral 标签中,勾选 Supports Running Without iOS App Installation 复选框。

尽可能创建一个独立的 watchOS app。独立的 watchOS app 不需要与 iOS app 配套使用。

image

2. 在多个 Target 中共享文件

设置了 watchOS target 后,你需要从 iOS target 中共享一些资源。比如重用 Landmark app 中的数据模型,一些资源文件,以及任何不需要修改就可以跨平台显示的视图。

image

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.swiftCircleImage.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 则管理你的故事板,图标和相关资源。

image

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 显示。

image

3.1 在项目导航器中,单击 WatchLandmarks Extension 文件夹旁边的显示三角形来显示其内容,然后添加一个新 SwiftUI 视图,命名为 WatchLandmarkDetail

image

3.2 给 WatchLandmarkDetail 结构体添加 userDatalandmarklandmarkIndex 属性。

这些和你在 处理用户输入 中添加到 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)
        //
    }
}

image

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)
    }
}

image

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")
        }
    }
}

image

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()
           
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值