Skip to content

第64天 项目 13 第三部分

信不信由你,在进入实现阶段之前,这个项目我们还有最后一天的技巧要学习,而且我把有趣的内容留到了最后。

今天你将学习如何将数据“导入”应用,以及如何将数据“导出”,正是这一功能让应用真正焕发生机——用户喜欢导入现有的内容,对其进行某种程度的重新创作,然后与朋友分享成果。

坚持下去!今天过后,我们就会开始将所有这些概念付诸实践,所以你离实际开发已经非常近了。就像邮票给我们的启发一样——作家乔希·比林斯曾打趣道:“邮票的用处就在于,它能粘在一件东西上,直到抵达目的地。”

今天你需要学习三个主题,分别是加载照片、分享数据以及请求用户评价应用。

  • 从用户的照片库加载照片
  • 如何通过 ShareLink 让用户分享内容
  • 如何请求用户在 App Store 上为应用评分

我很想知道在你使用 SwiftUI 一段时间后,对这些 API 有什么看法。

从用户的照片库加载照片

作者:Paul Hudson 2023年12月11日

SwiftUI 的 PhotosPicker 视图为我们提供了一种简单的方式,可以从用户的照片库中导入一张或多张照片。为了避免影响性能,数据会以一种名为 PhotosPickerItem 的特殊类型提供给我们,之后我们可以通过异步加载的方式,将这些数据转换为 SwiftUI 中的 Image(图像)。

整个过程总共需要五个步骤,首先要在常规的 SwiftUI 导入语句之外,再添加对 PhotosUI 的导入,代码如下:

swift
import PhotosUI
import SwiftUI

第二步,我们需要创建两个属性:一个用于存储选中的项目,另一个用于将选中的项目存储为 SwiftUI 图像。这种区分很重要,因为在我们实际请求加载之前,选中的项目仅仅是对用户照片库中某张图片的引用。

现在添加这两个属性:

swift
@State private var pickerItem: PhotosPickerItem?
@State private var selectedImage: Image?

第三步,在 SwiftUI 视图层级结构中的某个位置添加 PhotosPicker 视图。创建该视图时,必须指定一个向用户显示的标题、一个用于存储选中图像的绑定,以及要显示的数据类型——最后这一点使得我们可以使用 PhotosPicker 加载视频、实况照片等内容。

将视图当前的 body 属性替换为以下代码:

swift
VStack {
    PhotosPicker("选择一张图片", selection: $pickerItem, matching: .images)
}

提示: 特意将其放在 VStack 中是有原因的,稍后会详细说明。

第四步,监听 pickerItem 的变化,因为当它发生变化时,就意味着用户已经选择了一张需要我们加载的图片。一旦检测到变化,我们就可以调用选择项的 loadTransferable(type:) 方法,该方法会告知 SwiftUI,我们希望将选择项中的实际基础数据加载为 SwiftUI 图像。如果加载成功,我们就可以将结果赋值给 selectedImage 属性。

VStack 添加以下修饰符:

swift
.onChange(of: pickerItem) {
    Task {
        selectedImage = try await pickerItem?.loadTransferable(type: Image.self)
    }
}

提示: 调用 loadTransferable(type:) 可能需要几秒钟才能完成,尤其是对于全景图这类较大的图片。

第五步也是最后一步,在某个位置显示加载后的 SwiftUI 图像。将以下代码添加到 VStack 中,可以放在 PhotosPicker 的前面或后面:

swift
selectedImage?
    .resizable()
    .scaledToFit()

现在试试看吧!模拟器自带了几张可供测试的图片,选择一张图片后,你应该会看到它被加载到 Image 视图中,并显示在屏幕上。

根据实际需求,PhotosPicker 还有很多其他用法。例如,你可以将它与一个选择项数组的绑定关联起来,让用户选择多张图片。这就需要先创建一个用于存储选择项的数组属性:

swift
@State private var pickerItems = [PhotosPickerItem]()

然后像之前那样将这个数组传入:

swift
PhotosPicker("选择图片", selection: $pickerItems, matching: .images)

接下来较难处理的部分是处理这些图片并以某种方式显示它们,因为现在我们不能再只监听单个照片选择项了。

相反,你应该创建一个数组来存储加载后的图像,代码如下:

swift
@State private var selectedImages = [Image]()

然后使用 ForEach 之类的视图来显示它们,例如:

swift
ScrollView {
    ForEach(0..<selectedImages.count, id: \.self) { i in
        selectedImages[i]
            .resizable()
            .scaledToFit()
    }
}

最后,更新 onChange() 方法,以便在选择新项时清空数组,然后逐个加载新的图像集:

swift
.onChange(of: pickerItems) {
    Task {
        selectedImages.removeAll()

        for item in pickerItems {
            if let loadedImage = try await item.loadTransferable(type: Image.self) {
                selectedImages.append(loadedImage)
            }
        }
    }
}

如果你打算允许用户选择多张照片,建议通过添加 maxSelectionCount 参数来限制一次可选择的图片数量,代码如下:

swift
PhotosPicker("选择图片", selection: $pickerItems, maxSelectionCount: 3, matching: .images)

自定义照片导入还有另外两种方式,首先是标签。与许多 SwiftUI 视图一样,你可以根据需要提供完全自定义的标签,比如使用 Label 视图或者其他完全自定义的内容:

swift
PhotosPicker(selection: $pickerItems, maxSelectionCount: 3, matching: .images) {
    Label("选择一张图片", systemImage: "photo")
}

第二种方式是限制可导入图片的类型。我们在前面的示例中一直使用 .images,这意味着我们可以导入普通照片、截图、全景图等多种类型。你可以使用 .any().all().not() 并传入一个数组来应用更精细的筛选。例如,以下代码匹配除截图之外的所有图像:

swift
PhotosPicker(selection: $pickerItems, maxSelectionCount: 3, matching: .any(of: [.images, .not(.screenshots)])) {
    Label("选择一张图片", systemImage: "photo")
}

作者:Paul Hudson 2023年12月11日

SwiftUI 的 ShareLink 视图允许用户将内容从我们的应用中导出并分享到其他地方,例如将图片保存到照片库、通过“信息”应用向朋友发送链接等。

我们只需提供想要分享的内容,iOS 就会负责显示所有能够处理我们所发送数据的应用。例如,我们可以像这样分享一个 URL(统一资源定位符):

swift
ShareLink(item: URL(string: "https://www.hackingwithswift.com")!)

这样会创建一个带有图标的“分享”按钮,点击该按钮会弹出 iOS 的分享面板。在模拟器中,你只能看到几个示例选项,其中有些甚至无法正常使用,但如果在真实设备上,你会发现可以正常分享这个 URL。

如果你想对分享的数据有更多控制权,有以下几种选择。

首先,你可以为分享的数据附加主题和消息,代码如下:

swift
ShareLink(item: URL(string: "https://www.hackingwithswift.com")!, subject: Text("在这里学习 Swift"), message: Text("来看看 SwiftUI 100 天挑战吧!"))

这些信息的使用方式取决于用户分享到的应用——URL 总会被附加,因为它是最重要的内容,但有些应用会使用主题,有些会使用消息,还有些则会同时使用两者。

其次,你可以通过提供任意想要的标签来自定义按钮本身:

swift
ShareLink(item: URL(string: "https://www.hackingwithswift.com")!) {
    Label("宣传一下 Swift", systemImage: "swift")
}

第三,你可以提供一个要附加的预览,这在分享更复杂的内容时非常重要——我们可以在这里分享完全自定义的数据,因此预览有助于让接收方了解内容的大致情况。

令人有些困扰的是,即使是像图像这样本身可以作为预览的数据,也需要添加预览。为了避免代码重复,建议将图像赋值给一个本地常量,然后再使用它:

swift
let example = Image(.example)

ShareLink(item: example, preview: SharePreview("新加坡机场", image: example)) {
    Label("点击分享", systemImage: "airplane")
}

让用户能够从你的应用中分享数据非常重要,因为如果没有这个功能,应用中的数据在用户的生活中可能会显得非常孤立!

如何请求用户在 App Store 上为应用评分

作者:Paul Hudson 2023年12月11日

SwiftUI 提供了一个特殊的环境键 .requestReview,通过它我们可以请求用户在 App Store 上为应用评分。苹果会负责处理整个用户界面的显示,确保不会向已经评分过的用户再次显示评分提示,同时也会限制评分请求的显示频率——我们只需要在合适的时机发起请求即可。

这个过程需要三个步骤,首先要导入 StoreKit 框架:

swift
import StoreKit

第二步,添加一个属性,用于从 SwiftUI 的环境中获取评分请求器:

swift
@Environment(\.requestReview) var requestReview

第三步,在合适的时机发起评分请求。刚开始学习时,你可能会觉得将评分请求绑定到按钮点击事件上是个不错的主意,代码如下:

swift
Button("评分") {
    requestReview()
}

然而,这种方式远非理想选择,尤其是因为我们只是“请求”显示评分提示——用户可能在系统层面禁用了这类提醒,或者已经达到了允许的评分请求次数上限,在这些情况下,你的按钮将无法发挥任何作用。

相反,更好的做法是在你认为合适的时机自动调用 requestReview()。一个不错的切入点是当用户完成某项重要操作达到一定次数时,因为这样可以明确用户已经体会到了应用的价值。

本站使用 VitePress 制作