[SwiftUI 100 天] 用 DragGesture 和 offset() 来移动视图
2020/7/28 23:04:12
本文主要是介绍[SwiftUI 100 天] 用 DragGesture 和 offset() 来移动视图,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
译自 www.hackingwithswift.com/books/ios-s…
更多内容,欢迎关注公众号 「Swift花园」
喜欢文章?不如来个 🔺💛➕三连?关注专栏,关注我 🚀🚀🚀
用 DragGesture 和 offset() 来移动视图
SwiftUI 允许我们添加手势给任意视图,然后利用手势产生的数值来操作视图。为了说明这一点,我们要添加一个 DragGesture
给 CardView
,以便我们能移动卡片。我们还要使用手势产生的数值来控制视图的透明度和旋转角度 —— 当卡片被拖动后,它会旋转出画面, 同时渐隐消失。实现这些效果只需要很少的代码,因为大部分工作是 SwiftUI 替我们完成的,相信你一定会为之惊艳!
首先,添加下面这个新的 @State
属性到 CardView
,用以跟踪用户拖拽的距离:
@State private var offset = CGSize.zero 复制代码
接下来我们要添加三个 modifier 到 CardView
,直接放在 frame()
modifier 之下。记住:你应用 modifier 的顺序是很重要的,对于这里的 offset 和 rotation 尤其关键。
如果我们先旋转再偏移,那么偏移将会基于视图旋转后的坐标轴。例如,如果我们往左移动某样东西 100 像素,然后旋转 90 度,我们将得到左偏移 100 像素加上 90 度的旋转角度。但如果我们先旋转 90 度再向左移动 100 像素,我们将得到某个旋转了 90 度,并且往下移动了 100 像素的东西,因为这里的 “左” 方向已经被旋转了。
当你把 SwiftUI 通过包装 modifier 会创建出新视图这一机制考虑在内时,事情就变得更为复杂。对于移动和旋转,当我们希望视图向正西方向(无视旋转)的滑动的同时旋转自身,我们需要先添加 rotation,然后再添加 offset !(听起来有点反直觉是不是?)
现在,offset.width
会包含我们拖拽卡片的距离,但我们不会把这个值直接作为旋转的角度值使用。因为那样的话卡片会非常快的旋转,所以我们采用它的 1/5:
.rotationEffect(.degrees(Double(offset.width / 5))) 复制代码
接下来是应用移动,卡片会相对于拖拽数值做水平方向上的移动。再一次,我们不会使用原始的offset.width
,因为那会要求用户拖拽较长的距离才能得到有意义的结果,所以我们采用它的 5 倍数值,通过小手势就能轻扫卡片。
把下面这个 modifier 添加到前面那个之后:
.offset(x: offset.width * 5, y: 0) 复制代码
进行到这里,我们要基于拖拽手势再添加一个 modifier:我们要让卡片随着自身被拖远时淡出。
这里的计算需要一点思考,工作方式如下:
- 我们要使用拖拽数值的 1/50,以便卡片不会太快隐去。
- 在改变卡片透明度时,我们并不关心他们是被移到左边(负值)还是被移到右边(正值),因此我们会把数值放进
abs()
函数。 - 然后我们用 2 减去这个绝对值。
这里采用 2 是有意的,因为这样可以确保卡片在被拖拽不远时保持透明度。注意,大于 1.0 的透明度都等同于 1.0。如果用户向左或者向右拖动 50 个点,50 除以 50 得到 1,2 减去 1 得到的透明度还是 1 —— 卡片仍然是完全不透明的。但是当移动距离超出 50 个点时,卡片就开始渐隐,直到 100 个点时完全隐去。
把这个 modifier 添加到前面两个之后:
.opacity(2 - Double(abs(offset.width / 50))) 复制代码
现在,我们已经创建了一个属性用于存储拖拽数值,并且添加了三个 modifier 来改变视图的渲染方式。接下来是最重要的部分:我们需要添加一个 DragGesture
给我们的卡片,以便用户拖拽卡片时能更新 offset
数值。 拖拽手势自身有两个有用的 modifier,可以让我们在手势变化时触发函数(每当手指移动时就触发),以及在手势结束时触发函数(手指离开屏幕)。
这两个函数都会抛出当前的手势状态,以便我们拿来评估。在我们例子中,我们需要读取 translation
属性,用它来设置我们的 offset
属性,同时你也可以读取开始位置,预测的结束位置,等等。而对于 ended 函数,我们会检查用户是否移动了超过 100 个点,以便我们决定是否移除卡片。如果没有,我们会把 offset
设置回 0。
把下面这个 gesture()
modifier 添加到前面三个之后:
.gesture( DragGesture() .onChanged { gesture in self.offset = gesture.translation } .onEnded { _ in if abs(self.offset.width) > 100 { // 移除卡片 } else { self.offset = .zero } } ) 复制代码
运行应用:你应该能看到卡片会随着拖拽手势移动,旋转和渐隐,当你拖拽到某个距离时,卡片就保持在被拖到的位置,不会跳回原始位置。
要完成最后一步我们需要把 // 移除卡片
注释替换为实际的实现:把卡片从父视图中真正移除。现在的问题是,我们不希望让 CardView
去向上直接要求 ContentView
维护数据,因为这样会带来难以维护的套娃程序。相反,更好的解决方案是在 CardView
中存储一个闭包,可以稍后添加任意代码 —— 这意味着,我们拥有了从ContentView
获得回调的灵活性,同时不必显性地把两个视图耦合在一起。
把下面这个新属性添加到 CardView
的 card
属性下方:
var removal: (() -> Void)? = nil 复制代码
如你所见,这个闭包不接收参数,也不返回任何东西,并且默认为 nil
,所以除非显式需求,我们可以不用给它赋值。
现在可以把 // 移除卡片
注释替换为对移除闭包的调用:
self.removal?() 复制代码
提示: 问号表明这个闭包只有在非空的时候才能被调用。
回到 ContentView
,我们现在可以写一个专门处理卡片移除的方法,然后将它连接到前面的闭包:
func removeCard(at index: Int) { cards.remove(at: index) } 复制代码
我们还需要在 CardView
的创建代码处利用拖尾闭包给卡片添加移除的处理代码,我们会用 withAnimation()
调用包装移除的代码,这样卡片就能平滑地滑出。
代码如下:
ForEach(0..<cards.count, id: \.self) { index in CardView(card: self.cards[index]) { withAnimation { self.removeCard(at: index) } } .stacked(at: index, in: self.cards.count) } 复制代码
再次运行应用 —— 效果还不赖,你可以轻扫卡片栈里的卡片,直到它们滑出屏幕。
我的公众号 这里有Swift及计算机编程的相关文章,以及优秀国外文章翻译,欢迎关注~
这篇关于[SwiftUI 100 天] 用 DragGesture 和 offset() 来移动视图的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2022-10-05Swift语法学习--基于协议进行网络请求
- 2022-08-17Apple开发_Swift语言地标注释
- 2022-07-24Swift 初见
- 2022-05-22SwiftUI App 支持多语种 All In One
- 2022-05-10SwiftUI 组件参数简写 All In One
- 2022-04-14SwiftUI 学习笔记
- 2022-02-23Swift 文件夹和文件操作
- 2022-02-17Swift中使用KVO
- 2022-02-08Swift 汇编 String array
- 2022-01-30SwiftUI3.0页面反向传值