关于 政府门户网站 建设管理深圳市网络营销推广服务公司
在SwiftUI中实现触摸事件
SwiftUI是一个非常新的且发展迅速的框架-很棒,但是这也意味着它缺少很多不错的功能,包括触摸事件。
那么什么是触控事件? 如果您来自UIKit,则可能对它们很熟悉。 真的,它们只是说“检测到压力!”的一种奇特方式。
但是,由于iPhone和iPad是先进的,具有多点触控功能的设备,因此有时会“检测到按下!” 还不够 您可能想要检测用户何时首次降落或何时释放……或何时拖动并释放。
回到UIKit故事板上,这些触摸事件很容易处理。 UIButton附带了大量的触摸事件,包括内部触摸,外部触摸,外部触摸………还有更多,而且它们一起可以接收您的手指可以执行的所有操作。
但是SwiftUI呢? 呃……不多。 默认情况下,您只会获得一个触摸事件:Touch Up Inside。
Button(action: {/// This is only called when the user LIFTS their finger off the button (Touch Up Inside)print("Pressed!")
}) {Text("Click me!")
}
这意味着仅当同时满足以下两个条件时,才会调用按钮动作:
- 用户举起手指
- 抬起时,用户的手指位于按钮的范围内
在当今的应用中,“ Touch Up Inside”是最常用的触摸事件,因为它使用户有时间取消。如果他们决定不想按该按钮,则只需将手指移开即可,这是一种简便的方法。
因此,SwiftUI的按钮仅支持即开即用的Touch Up Inside是有道理的……但是,如果您想访问所有其他触摸事件怎么办?
假设您正在制作有油门踏板的赛车游戏。按下时,汽车应该走得更快。释放时,它应该变慢。这需要三个触摸事件-向下触摸,内部向上触摸和外部向上触摸。 (没有“ Touch Up触摸”事件,但是“ Touch Up Inside”和“ Touch Up Outside”的效果相同。)
但是,如果SwiftUI的按钮仅支持“ Touch Up Inside”,那么您应该如何制作这些赛车游戏?当然,您可能会严重牺牲UX并使用切换按钮-按加速,按减速,然后按再次加速:
或者,您也可以牺牲UI来使用多按钮方法:更多按钮,更好! 对? 等等……看起来太糟糕了!
SwiftUI仅对游戏不利吗?
SwiftUI可能不适用于游戏。 也许只是不喜欢复杂的触摸事件。 也许是时候回到UIKit了。
不,SwiftUI对于游戏来说还不错。 复杂的触摸事件没有问题。 当然也不是时候回到UIKit了。 苹果公司知道SwiftUI会花一些时间来赶上UIKit的功能水平,因此他们使使用视图修饰符进行自定义变得容易。 在本文中,我们将使用视图修饰符复制由内置手势支持的触摸事件。
我们的演示游戏
制作赛车游戏会花费太多时间,但我们可以将其刮到最低限度—我们需要的是:
跟踪油门踏板状态的属性
一个按钮
显示油门踏板状态的标签
这还不算太多,所以我们首先坚持使用默认的“ Touch Up Inside”,并创建一个按钮来切换按下/释放状态。
struct ContentView: View {@State var buttonPressed = falsevar body: some View {VStack {/// support for if-else introduced in Xcode 12if buttonPressed {Text("Gas pedal pressed!")} else {Text("Gas pedal not pressed!")}Button(action: {buttonPressed.toggle()}) {Text("Toggle gas pedal state!").foregroundColor(Color.white).padding(10).background(Color.gray).cornerRadius(6).padding(10)}}}
}
在这种替代方案中,逻辑工作正常-按下按钮可切换按下/释放状态-但这并不是很好的用户体验。 该按钮不仅具有一个用途,而且在两个用途之间交替显示。 这意味着某人可能会不小心刹车而不是加速,从而使他们的游戏付出代价,并给您带来不好的评价。
这就是触摸事件在这里效果很好的原因:按下可加速,放开可减速。 向下触摸,向上触摸。 让我们在SwiftUI中重新创建这些触摸事件! 我们可以使用视图修饰符和手势来实现:
/// an enum is more readable than true/false
public enum ButtonState {case pressedcase notPressed
}/// ViewModifier allows us to get a view, then modify it and return it with modifications
public struct TouchDownUpEventModifier: ViewModifier {/// Later, .onChanged will be called multiple times (around 10 times a second once your finger touches down)/// so we need a variable to keep track of the first time your finger touches down.../// ... we then set this to false when your finger lifts up, so the cycle can repeat again@State private var pressed = false/// this is the closure that will get passed around./// we will update the ButtonState every time your finger touches down or up.let changeState: (ButtonState) -> Void/// a required function for ViewModifier./// content is the body content of the caller viewpublic func body(content: Content) -> some View {/// prepare to add the gesture to the the body contentcontent/// we need to detect both .onChanged and .onEnded/// so we modify the original content by adding a simultaneousGesture to it.simultaneousGesture(DragGesture(minimumDistance: 0)/// equivalent to UIKit's Touch Down event, but is called continuously once your finger moves while still on the screen./// It will be called a lot, so we need a bool to make sure we change the state only when your finger first touches down.onChanged { _ in/// this will make sure that we only pass the new state one timeif !self.pressed {/// we lock the state to "pressed" so that it won't be set continuously by .onChanged. We will enable it to be changed once the user lifts their finger.self.pressed = true/// pass the new state to the callerself.changeState(ButtonState.pressed)}}/// equivalent to both UIKit's Touch Up Inside and Touch Up Outside event.onEnded { _ in/// we enable "pressed" to be changed now to allow another cycle of finger down/up events.self.pressed = false/// pass the new state to the callerself.changeState(ButtonState.notPressed)})}/// if you're on iPad Swift Playgrounds and you put all of this code in a separate file,/// you need to add a public init so that the compiler detects it.public init(changeState: @escaping (ButtonState) -> Void) {self.changeState = changeState}
}
而且,因为我们不在乎“ Touch Up Inside”和“ Touch Up Outside”之间的区别(两者都应释放油门踏板),所以我们可以将它们组合成一个.onEnded块! 现在,用以下命令替换原始的切换按钮:
Text("Press to accelerate!").foregroundColor(Color.white).padding(10).background(Color.gray).cornerRadius(6).padding(10).modifier(TouchDownUpEventModifier(changeState: { (buttonState) inif buttonState == .pressed {buttonPressed = true} else {buttonPressed = false}}))
我们将使用普通的Text标签(视图修饰符可在所有SwiftUI视图上使用),而不是使用Button! 结果如下:
您无法输入GIF,但标签会变为“踩下油门!” 当我按下按钮时,并且“未踩下油门踏板!” 当我释放。 但这是一个问题。 与默认按钮不同,我们没有免费的指示符来表明按钮已被按下。
这很容易解决! 我们可以添加另一个视图修改器(这次是内置视图修改器)来更改其比例。
Text("Press to accelerate!").foregroundColor(Color.white).padding(10).background(Color.gray).cornerRadius(6).padding(10).modifier(TouchDownUpEventModifier(changeState: { (buttonState) inif buttonState == .pressed {buttonPressed = true} else {buttonPressed = false}}))/// ↓ add this ↓.scaleEffect(buttonPressed ? 0.9 : 1).animation(.easeOut(duration: 0.2))
那是因为所有内置的视图修饰符都是…内置的。 为了使我们的custom-view修饰符看起来像内置修饰符,我们必须使其成为View的扩展。
/// we can make the modifier more Swifter by wrapping it in a method...
/// ... then making the method an extension of View, so we can easily add it to any SwiftUI view
public extension View {func onTouchDownUpEvent(changeState: @escaping (ButtonState) -> Void) -> some View {modifier(TouchDownUpEventModifier(changeState: changeState))}
}
调用方式
Text("Press to accelerate!").foregroundColor(Color.white).padding(10).background(Color.gray).cornerRadius(6).padding(10)/// our brand-new, shiny view modifier!.onTouchDownUpEvent(changeState: { (buttonState) inif buttonState == .pressed {buttonPressed = true} else {buttonPressed = false}}).scaleEffect(buttonPressed ? 0.9 : 1).animation(.easeOut(duration: 0.2))
看起来更干净。 大!
结论
过多的按钮或具有多种用途的按钮绝不是一个好主意。 相反,请使用触摸事件以保持一致性和易用性。 触摸事件并未内置到SwiftUI中,但您可以使用视图修饰符和手势来复制它们。 您还可以将custom-view修饰符包装到扩展中,以使其易于使用。
推荐
基础文章推荐
- 《SwiftUI是什么,听听大牛们如何说》
经典教程推荐
- 更新近百篇SwiftUI教程《SwiftUI2020教程》
- 帮您突破数据存储难关《SwiftUI vs CoreData数据存储解决方案》
上新
- 《WWDC2020专栏》
- 《SwiftUI WWDC2020 新增组件列表》
技术源码推荐
推荐文章
CoreData篇
- SwiftUI数据存储之做个笔记App 新增与查询(CoreData)
- SwiftUI进阶之存储用户状态实现登录与登出
- SwiftUI 数据之List显示Sqlite数据库内容(2020年教程)
Combine篇
- 一篇文章学懂弄通SwiftUI与Combine(含轮播动画App源码)
TextField篇
- 《SwiftUI 一篇文章全面掌握TextField文本框 (教程和全部源码)》
- 《SwiftUI实战之TextField风格自定义与formatters》
- 《SwiftUI实战之TextField如何给键盘增加个返回按钮(隐藏键盘)》
- 《SwiftUI 当键盘出现时避免TextField被遮挡自动向上移动》
- 《SwiftUI实战之TextField如何给键盘增加个返回按钮(隐藏键盘)》
JSON文件篇
- SwiftUI JSON文件下载、存储、解析和展示(代码大全)
一篇文章系列
- SwiftUI一篇文章全面掌握List(教程和源码)
- 《SwiftUI 一篇文章全面掌握TextField文本框 (教程和全部源码)》
- SwiftUI一篇文章全面掌握Picker,解决数据选择(教程和源码)
- SwiftUI一篇文章全面掌握Form(教程和源码)
- SwiftUI Color 颜色一篇文章全解决
技术交流
QQ:3365059189
SwiftUI技术交流QQ群:518696470
- 请关注我的专栏icloudend, SwiftUI教程与源码
https://www.jianshu.com/c/7b3e3b671970