第81天 项目 16 第三部分
今天我们将介绍三个重要功能:SwiftUI 中的列表行滑动操作、iOS 中的通知以及 Xcode 中的 Swift 包依赖。这三个功能对于应用开发者来说都是需要掌握的关键技能,希望你会发现它们都相对容易学习。
列表行分隔符 列表行滑动操作——即在 iOS 中滑动列表行时出现的那些按钮——允许我们为布局中的列表行添加额外操作。这是一种避免用户界面(UI)杂乱的好方法,但在使用时我希望你谨慎行事。斯科特·贝尔斯基(曾担任 Adobe 副总裁,现为准投资人)曾说过,设计良好用户体验的一条准则是:“选项越多,问题越多。”
因此,尽管可以在 UI 中添加额外功能,但始终要考虑可发现性。毕竟,如果用户找不到你的操作,那这些操作相当于不存在!
今天你需要学习三个主题,从中你将了解滑动操作、本地通知和 Swift 包依赖:
- 为列表(List)添加自定义行滑动操作
- 安排本地通知
- 在 Xcode 中添加 Swift 包依赖
为列表(List)添加自定义行滑动操作
作者:Paul Hudson 2024年2月1日
从我记事起,iOS 应用就有“滑动删除”功能,但近年来该功能的功能不断增强,列表行现在可以有多个按钮,通常位于行的两侧。在 SwiftUI 中,我们可以使用 swipeActions() 修饰符实现这一完整功能,该修饰符允许我们在列表行的一侧或两侧注册一个或多个按钮。
默认情况下,按钮会放在行的右侧,且没有任何颜色,因此当你从右向左滑动时,会显示一个灰色的按钮,代码如下:
List {
Text("泰勒·斯威夫特(Taylor Swift)")
.swipeActions {
Button("发送消息", systemImage: "message") {
print("你好")
}
}
}你可以通过为 swipeActions() 修饰符提供 edge 参数来自定义按钮所在的边缘,也可以通过为按钮添加分隔符 添加 tint() 修饰符并选择心仪的颜色,或者附加按钮角色(button role)来自定义按钮颜色。
因此,以下代码会在行的两侧各显示一个按钮:
List {
Text("泰勒·斯威夫特(Taylor Swift)")
.swipeActions {
Button("删除", systemImage: "minus.circle", role: .destructive) {
print("正在删除")
}
}
.swipeActions(edge: .leading) {
Button("固定", systemImage: "pin") {
print("正在固定")
}
.tint(.orange)
}
}与上下文菜单类似,滑动操作本质上默认对用户是隐藏的,因此重要的功能不宜隐藏在其中。在本应用中我们会同时使用这两种功能,希望能让你有机会直接对它们进行比较和对比!
安排本地通知
作者:Paul Hudson 2024年2月1日
iOS 有一个名为 UserNotifications 的框架,其功能与你预期的基本一致:允许我们创建向用户显示的通知,这些通知可在锁屏界面上展示。我们可以使用两种类型的通知,它们的区别在于创建位置:本地通知是我们在本地安排的通知,而远程通知(通常称为“推送通知”)则是从某个服务器发送的通知。
远程通知需要服务器才能工作,因为你要将消息发送到苹果的推送通知服务(APNS),然后由该服务将消息转发转发给用户。而本地通知则相对简单,只要用户允许,我们就可以在任何时间发送任何消息。
要尝试实现本地通知,首先在 ContentView.swift 文件顶部附近添加额外的导入语句:
import UserNotifications接下来,我们将构建一些基本结构,后续会在其中填充本地通知相关代码。使用本地通知需要先请求用户许可,然后实际注册要显示的通知进行注册。我们会将这两个操作分别放在垂直堆栈(VStack)内的两个独立按钮中,请现在将以下代码放入你的 ContentView 结构体中:
VStack {
Button("请求许可") {
// 第一个操作
}
Button("安排本地通知") {
// 第二个操作
}
}好了,设置工作已完成,现在我们将重点转向两个重要工作中的第一个:请求显示提醒的权限。通知可以有多种形式,但最常见的做法是请求显示提醒、标记和声音的权限——这并不意味着我们需要同时使用所有这些形式,但提前提前预先请求权限,以便之后可以灵活选择。
当我们告知 iOS 我们需要哪种类型的通知时,系统会向用户显示一个提示框,由用户最终决定我们的应用可以执行哪些操作。用户做出选择后,我们提供的闭包(closure)会被调用,并告知我们请求是否成功。
因此,将“// 第一个操作”注释替换为以下代码:
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { success, error in
if success {
print("一切就绪!")
} else if let error {
print(error.localizedDescription)
}
}如果用户授予权限后,我们就可以开始安排通知了。尽管通知看似简单,但苹果为了实现最大的灵活性,将其拆分为三个部分:
- 内容(content):指要显示的内容,可以是标题、副标题、声音、图片等。
- 触发器(trigger):决定通知何时显示,可以是从现在起的若干秒后、未来的某个日期和时间,或者某个位置。
- 请求(request):将内容和触发器结合起来,同时添加一个唯一标识符,以便之后可以编辑或移除特定的提醒。如果不需要编辑或移除提醒,可以使用
UUID().uuidString获取一个随机标识符。
在学习通知时,最简单的触发器类型是 UNTimeIntervalNotificationTrigger,它允许我们设置通知在从现在起的若干秒后显示。因此,将“// 第二个操作”注释替换为以下代码:
let content = UNMutable UNMutable UNMutableNotificationContent()
content.title = "喂猫"
content.subtitle = "它看起来饿了"
content.sound = UNNotificationSound.default
// 5秒后显示该通知
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
// 选择一个随机标识符
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
// 添加我们的通知请求
UNUserNotificationCenter.current().add(request)现在运行应用,点击第一个按钮请求通知权限,然后点击第二个按钮添加实际的通知。
现在是重要的一步:添加通知后,在模拟器中按下 Cmd+L 锁定屏幕。几秒钟后,设备应该会发出声音并唤醒,显示我们的消息——成功了!
在 Xcode 中添加 Swift 包依赖
作者:Paul Hudson 2024年2月1日
到目前为止,我们编写的所有代码都是从零开始构建的,这样你可以清楚地了解其工作原理,并将这些技能应用到自己的项目中。但有时,从零开始编写代码存在风险:可能代码复杂、容易出错、频繁变更,或者存在其他各种原因,这就是“依赖”存在的原因——依赖即获取第三方代码并在我们的项目中使用的能力。
Xcode 内置了一个名为 Swift 包管理器(Swift Package Manager,简称 SPM)的依赖管理器。你可以告知 Xcode 存储在网上的某个代码的 URL,Xcode 会为你下载该代码。你甚至可以指定要下载的版本,这意味着即使未来远程代码存储的代码发生变更,也能确保不会破坏你的现有的代码造成破坏。
为了演示这一功能,我创建了一个简单的 Swift 包,你可以将其导入任何项目。该包为 Swift 的 Sequence 类型(Array、Set、Dictionary 甚至范围都遵循该类型)添加了一个小扩展,能够同时同时提取多个随机项。
言归正传,第一步是将该包添加到我们的项目中:前往“文件”(File)菜单,选择“添加包依赖”(Add Package Dependencies)。在 URL 栏中输入 https://github.com/twostraws/SamplePackage,这是我示例包的代码存储地址。Xcode 会获取该包、读取 其配置,并显示选项询问你要使用哪个版本。默认选项是“版本——直至下一个主要版本”(Version – Up to Next Major),这是最常用的选项,意味着意味着如果包的作者未来更新了该包,只要不引入破坏性变更,Xcode 就会将包更新到新版本。
之所以能实现这一点,是因为大多数开发者都同意为其代码采用“语义化版本控制”(SemVer)系统。如果查看类似 1.5.3 的版本号,其中“1”是主版本号,“5”是次版本号,“3”是修订版本号。如果开发者正确遵循语义化版本控制规则,他们应:
- 修复错误且不破坏任何应用程序接口(API)或添加功能时,更改修订版本号。
- 添加功能但不破坏任何 API 时,更改次版本号。
- 破坏 API 时,更改主版本号。
这就是“直至下一个主要版本”(Up to Next Major)选项效果良好的原因,因为这意味着你会不断获得新的错误修复和功能更新,同时不会意外切换到破坏代码的版本。
好了,包的相关设置已完成,点击“添加包”(Add Package)两次两次,让 Xcode 将其添加到项目中。你应该会在项目导航器的“Swift 包依赖”(Swift Package Dependencies)下看到该包。
要试用该包,请打开 ContentView.swift,在顶部添加以下导入语句:
import SamplePackage是的,现在这个外部依赖已成为一个模块,我们可以在任何需要的地方导入它。
现在我们可以在视图中试用它了。例如,我们可以模拟一个简单的彩票程序:创建一个从 1 到 60 的数字范围,从中选择 7 个数字,将它们转换为字符串,然后合并成一个字符串。为了简洁,这里需要用到一些你之前没见过的代码,我会对其进行分步说明。
首先,用以下代码替换你当前的 ContentView:
struct ContentView: View {
var body: some View {
Text(results)
}
}是的,这样代码还无法运行,因为缺少 results,不过我们马上就会补充它。
首先,创建一个从 1 到 60 的数字范围,可通过向 ContentView 添加以下属性实现:
let possibleNumbers = 1...60其次,我们将创建一个名为 results 的计算属性,该属性会从上述范围中选择 7 个数字,并将它们合并成一个字符串,请同样添加这个属性:
var results: String {
// 后续会添加更多代码
}在该属性内部,我们将使用从 SamplePackage 框架中获得的扩展来从范围中选择 7 个随机数字。该扩展提供了一个 random() 方法,该方法接受一个整数参数,并会从序列中返回最多该数量的随机元素,且元素顺序随机。彩票号码通常会按从小到大的顺序排列,因此我们会对选出的数字进行排序。
因此,将以下代码添加到“// 后续会添加更多代码”的位置:
let selected = possibleNumbers.random(7).sorted()接下来,我们需要将这个整数数组转换为字符串数组。在 Swift 中,这只需一行代码即可完成,因为序列(sequence)有一个 map() 方法,该方法通过对每个元素应用一个函数,能将一种类型的数组转换为另一种类型的数组。在本例中,我们希望从每个整数创建一个新字符串,因此可以使用 String.init 作为要调用的函数。
因此,在之前一行代码后添加以下代码:
let strings = selected.map(String.init)此时,strings 是一个包含从上述范围中选出的 7 个随机数字的字符串数组,所以最后一步是将它们全部合并成一个字符串。现在向该属性添加最后一行代码:
return strings.formatted()这样我们的代码就完整了:文本视图(text view)会显示 results 中的值,results 会执行选出、排序、转换为字符串,然后用逗号将它们连接起来。
补充说明:你可以在 Xcode 中直接查看我这个简单扩展的源代码——只需打开“Sources > SamplePackage”组,找到 SamplePackage.swift 文件即可。你会发现它的代码非常简洁!
至此,本项目所需的最后一项技术已介绍完毕,请将代码重置为原始状态。