译自 www.hackingwithswift.com/books/ios-s…
更多内容,欢迎关注公众号 「Swift花园」
喜欢文章?不如来个 🔺💛➕三连?关注专栏,关注我 🚀🚀🚀
Flashzilla:介绍
在这个项目中我们将构建一个帮助用户通过抽认卡来学习的应用 —— 比如卡片一面写着 “to buy”,另一面写着 “comprar”。当然,因为这是一个数字化的应用程序,我们不必真的担心所谓的“另一面”,只需要确保卡片在被点击时显示正确的信息。
项目的名称实际上来自我的第一个 iOS 应用 —— 我很久以前为 iPhoneOS 开发的应用,当时还没有 iPad。 Apple 在审核的时候拒绝了应用,因为产品名中包含 “Flash”,彼时 Apple 还坚持应用商店里不能出现任何 “Flash” 字眼。时光飞逝啊...
言归正传,这个项目中有许多有趣的东西值得学习,包括手势,震动反馈,定时器等。请用 Single View App 模板创建一个新的 iOS 应用,取名 Flashzilla。和往常一样,在构建实际应用之前,我们先涉及几个技术项。让我们开始吧...
应用手势
SwiftUI 为视图提供了许多配合的手势,它们让我们可以专注于更重要的业务逻辑。最常见的是 onTapGesture(),此外还有另外几种手势,把它们组合在一起使用也是很有趣的。
我会跳过 onTapGesture(),因为之前已经接触过很多次了。不过,在尝试更复杂的功能之前,我们先用它的 count 参数实现双击和三击:
Text("Hello, World!")
.onTapGesture(count: 2) {
print("双击!")
}
接下来是比点击复杂一点的长按,即 onLongPressGesture():
Text("Hello, World!")
.onLongPressGesture {
print("长按!")
}
跟点击手势类似,长按手势也是可以定制的。例如,你可以指定按压需要满足的最短时长。动作闭包只有在按压时间达到这个给定的时长时才会触发。比如,下面这个长按动作在两秒后触发:
Text("Hello, World!")
.onLongPressGesture(minimumDuration: 2) {
print("长按!")
}
你甚至可以在手势变化过程中添加另外一个闭包,闭包传入的参数是一个布尔型:
- 按下时该闭包即以参数为 true 被调用。
- 如果你在长按被识别之前松手(比如,在 2 秒的长按识别中在 1 秒就松手),那么该闭包会以参数为 false 被调用。
- 如果你保持按压直到识别,该闭包也会以参数为 false 被调用(因为手势识别过程已经结束),然后识别完成的闭包也被调用。
你可以自行尝试下面的代码:
Text("Hello, World!")
.onLongPressGesture(minimumDuration: 1, pressing: { inProgress in
print("进行中:\(inProgress)!")
}) {
print("长按!")
}
对于更高级的手势,你需要使用 gesture() modifier,并提供 DragGesture,LongPressGesture, MagnificationGesture,RotationGesture 和 TapGesture 中的某一个。所有这几种手势都有特定的 modifier,最常用的包括 onEnded(),onChanged(),你可以用它们在手势进行中(onChanged())或者完成时(onEnded())执行动作。
举个例子,我们可以添加一个放大手势给某个视图,以便放大或者缩小视图。实现这个功能需要创建两个 @State 属性,分别存储当前的缩放增量和最后的索芳志,用于 scaleEffect() modifier,代码如下:
struct ContentView: View {
@State private var currentAmount: CGFloat = 0
@State private var finalAmount: CGFloat = 1
var body: some View {
Text("Hello, World!")
.scaleEffect(finalAmount + currentAmount)
.gesture(
MagnificationGesture()
.onChanged { amount in
self.currentAmount = amount - 1
}
.onEnded { amount in
self.finalAmount += self.currentAmount
self.currentAmount = 0
}
)
}
}
旋转视图用的是 RotationGesture,对应 rotationEffect() modifier:
struct ContentView: View {
@State private var currentAmount: Angle = .degrees(0)
@State private var finalAmount: Angle = .degrees(0)
var body: some View {
Text("Hello, World!")
.rotationEffect(currentAmount + finalAmount)
.gesture(
RotationGesture()
.onChanged { angle in
self.currentAmount = angle
}
.onEnded { angle in
self.finalAmount += self.currentAmount
self.currentAmount = .degrees(0)
}
)
}
}
当手势发生冲突时,事情就变得有趣了 —— 你有两个或者两个以上的手势需要同时识别,比如一个给当前视图,一个给父视图。
例如,下面的代码对某个文本视图及其父视图都添加了 onTapGesture():
struct ContentView: View {
var body: some View {
VStack {
Text("Hello, World!")
.onTapGesture {
print("文本被点击")
}
}
.onTapGesture {
print("VStack被点击")
}
}
}
在上面的代码中 SwiftUI 会始终给子视图赋予更高的优先级,也就是说,当你点击时,你会看到打印的消息是 “文本被点击”。不过,假如你想要改变这个优先级,可以使用 highPriorityGesture() modifier 来强制父视图的手势优先识别,就像下面这样:
struct ContentView: View {
var body: some View {
VStack {
Text("Hello, World!")
.onTapGesture {
print("文本被点击")
}
}
.highPriorityGesture(
TapGesture()
.onEnded { _ in
print("VStack被点击")
}
)
}
}
或者你可以使用 simultaneousGesture() modifier 来告诉 SwiftUI,你希望父视图和子视图的手势同时触发,代码如下:
struct ContentView: View {
var body: some View {
VStack {
Text("Hello, World!")
.onTapGesture {
print("文本被点击")
}
}
.simultaneousGesture(
TapGesture()
.onEnded { _ in
print("VStack被点击")
}
)
}
}
上面的代码在点击时会同时打印 “文本被点击” 和 “VStack被点击”。
最后,SwiftUI 还能让我们创建手势序列,序列中的手势只有前一个手势成功识别之后才会被激活识别。由于手势之间存在引用,所以比较复杂,你不能简单地将它们直接添加到视图。
下面是一个展示手势序列的例子,在长按之后,你可以继而拖拽一个圆:
struct ContentView: View {
// 圆被拖拽的偏移量
@State private var offset = CGSize.zero
// 是否正在被拖拽
@State private var isDragging = false
var body: some View {
// 拖拽手势更新偏移量和 isDragging
let dragGesture = DragGesture()
.onChanged { value in self.offset = value.translation }
.onEnded { _ in
withAnimation {
self.offset = .zero
self.isDragging = false
}
}
// 长按手势激活 isDragging
let pressGesture = LongPressGesture()
.onEnded { value in
withAnimation {
self.isDragging = true
}
}
// 组合手势强制用户先长按才能触发拖拽
let combined = pressGesture.sequenced(before: dragGesture)
// 一个 64x64 的圆,应用组合手势,在被拖拽时放大,并且按照拖拽的位置做出偏移
return Circle()
.fill(Color.red)
.frame(width: 64, height: 64)
.scaleEffect(isDragging ? 1.5 : 1)
.offset(offset)
.gesture(combined)
}
}
手势是创建流畅,有趣的用户界面的一种极佳手段。不过,确保你向用户展示了手势的工作方式,不然他们可能会感到困惑。
我的公众号 这里有Swift及计算机编程的相关文章,以及优秀国外文章翻译,欢迎关注~