Traduci Press the Button Again Start to Be in a State of Suspension
My task today was quite simple: adding an optional long-press handler to a push button in SwiftUI. A regular tap opens our website and a long press does… something else. Not and then difficult, right?
Naive Offset Version
Hither'south my first naive iteration:
1 two 3 iv 5 half-dozen 7 8 nine
Button ( action : { openWebsite ( . pspdfkit ) }) { Image ( "pspdfkit-powered" ) . renderingMode ( . template ) . onLongPressGesture ( minimumDuration : 2 ) { print ( "Hole-and-corner Long Press Action!" ) } }
While the to a higher place works to discover a long press, when adding a gesture to the paradigm, the button no longer fires. Alright, not quite what we desire. Let'southward move the gesture out of the characterization and to the push.
Moving Things Around Version
Here's my next attempt:
one 2 iii four five half-dozen 7 8 9
Button ( action : { openWebsite ( . pspdfkit ) }) { Image ( "pspdfkit-powered" ) . renderingMode ( . template ) } . onLongPressGesture ( minimumDuration : 2 ) { print ( "Secret Long Printing Action!" ) }
Great! Now the push button tap works once again — unfortunately the long-press gesture doesn't work anymore. OK, permit'south use simultaneousGesture
to tell SwiftUI that we actually care about both gestures.
Getting Fancy with simultaneousGesture
Take iii:
ane 2 iii iv five 6 7 8 9 10
Push button ( action : { openWebsite ( . pspdfkit ) }) { Image ( "pspdfkit-powered" ) . renderingMode ( . template ) } . simultaneousGesture ( LongPressGesture () . onEnded { _ in print ( "Undercover Long Printing Action!" ) }) Spacer ()
Great — that works. However, at present we always trigger both the long printing and the action, which isn't quite what we want. We want either/or, so allow's endeavor adding a 2nd gesture instead.
2 Gestures Are Ameliorate Than Ane
Hither we go again:
ane ii 3 4 5 6 seven 8 9 10 eleven 12 13 14
Button ( action : { // ignore }) { Image ( "pspdfkit-powered" ) . renderingMode ( . template ) } . simultaneousGesture ( LongPressGesture () . onEnded { _ in impress ( "Secret Long Press Action!" ) }) . simultaneousGesture ( TapGesture () . onEnded { impress ( "Boring regular tap" ) openWebsite ( . pspdfkit ) }) Spacer ()
It… works! It does exactly what we expect, and it'southward nicely calling either tap or long press. Woohoo! And then let's do some QA and examination everywhere. iOS 13: cheque. iOS 14: bank check. Let'southward compile the Catalyst version to be sure. And: Information technology does not piece of work. Neither tap nor long tap. The button has no outcome at all.
Goad… Always Catalyst!
If nosotros can ignore the long printing on Catalyst, so this combination works at least for the regular action:
i ii three 4 5 6 7 8 nine ten 11 12 13 14 xv 16 17 18 19 20 21 22 23
@State var didLongPress = false var trunk : some View { Button ( action : { if didLongPress { didLongPress = false } else { print ( "Boring regular tap" ) openWebsite ( . pspdfkit ) } }) { Image ( "pspdfkit-powered" ) . renderingMode ( . template ) } // None of this ever fires on Mac Catalyst :( . simultaneousGesture ( LongPressGesture () . onEnded { _ in didLongPress = true impress ( "Secret Long Printing Action!" ) }) . simultaneousGesture ( TapGesture () . onEnded { didLongPress = false }) }
In our instance, we really want the long press though, so what to practice? I remembered a trick I used in my Presenting Popovers from SwiftUI article: Nosotros tin can use a ZStack
and only utilize UIKit for what doesn't work in SwiftUI.
The Nuclear Choice
The use is elementary:
1 2 3 4 v 6 vii 8
LongPressButton ( action : { openWebsite ( . pspdfkit ) }, longPressAction : { impress ( "Secret Long Press Activity!" ) }, characterization : { Image ( "pspdfkit-powered" ) . renderingMode ( . template ) })
Now, permit's talk about this LongPressButton
subclass…
1 2 3 4 five 6 7 viii 9 ten eleven 12 13 14 15 16 17 eighteen 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
struct LongPressButton < Label > : View where Characterization : View { let label : (() -> Label ) let activeness : () -> Void let longPressAction : () -> Void init ( action : @escaping () -> Void , longPressAction : @escaping () -> Void , characterization : @escaping () -> Label ) { self . label = label cocky . action = activity self . longPressAction = longPressAction } var body : some View { Button ( action : { }, label : { ZStack { label () // Using .simultaneousGesture(LongPressGesture().onEnded { _ in works on iOS just fails on Catalyst TappableView ( activeness : activity , longPressAction : longPressAction ) } }) } } private struct TappableView : UIViewRepresentable { allow action : () -> Void let longPressAction : () -> Void typealias UIViewType = UIView func makeCoordinator () -> TappableView . Coordinator { Coordinator ( activity : action , longPressAction : longPressAction ) } func makeUIView ( context : Cocky . Context ) -> UIView { UIView () . then { let tapGestureRecognizer = UITapGestureRecognizer ( target : context . coordinator , action : #selector( Coordinator.handleTap(sender:) ) ) $0 . addGestureRecognizer ( tapGestureRecognizer ) let doubleTapGestureRecognizer = UILongPressGestureRecognizer ( target : context . coordinator , action : #selector( Coordinator.handleLongPress(sender:) ) ) doubleTapGestureRecognizer . minimumPressDuration = 2 doubleTapGestureRecognizer . crave ( toFail : tapGestureRecognizer ) $0 . addGestureRecognizer ( doubleTapGestureRecognizer ) } } func updateUIView ( _ uiView : UIView , context : Self . Context ) { } class Coordinator { let activity : () -> Void let longPressAction : () -> Void init ( activity : @escaping () -> Void , longPressAction : @escaping () -> Void ) { cocky . action = action self . longPressAction = longPressAction } @objc func handleTap ( sender : UITapGestureRecognizer ) { guard sender . land == . ended else { return } activeness () } @objc func handleLongPress ( sender : UILongPressGestureRecognizer ) { guard sender . state == . began else { return } longPressAction () } } }
And hither we get. This version works exactly as we look on iOS 13 and iOS xiv, and on Catalyst on Catalina and Big Sur. UIKit is verbose, but it works. And with the power of SwiftUI, we tin hide all that code behind a convenient new push button subclass.
In our project, this lawmaking is much smaller, as we use small categories to allow block-based gesture recognizers and automatic wrapping of UIViews:
i ii three iv 5 6 7 8 9 10 11 12 13 14 15 sixteen 17 18 19 twenty 21 22 23 24 25 26 27 28 29 thirty 31 32 33 34 35 36
struct LongPressButton < Label > : View where Label : View { let characterization : (() -> Label ) allow action : () -> Void let longPressAction : () -> Void let longPressDelay : TimeInterval init ( action : @escaping () -> Void , onLongPress : @escaping () -> Void , longPressDelay : TimeInterval = 2 , label : @escaping () -> Label ) { self . label = label cocky . activity = action self . longPressAction = onLongPress cocky . longPressDelay = longPressDelay } var torso : some View { Push ( action : { }, label : { ZStack { label () UIViewContainer ( UIView () . and then { let tapGestureRecognizer = UITapGestureRecognizer ( name : "Tap" ) { sender in guard sender . state == . concluded else { return } activeness () } $0 . addGestureRecognizer ( tapGestureRecognizer ) let doubleTapGestureRecognizer = UILongPressGestureRecognizer ( name : "Long Printing" ) { sender in guard sender . state == . began else { render } longPressAction () } doubleTapGestureRecognizer . minimumPressDuration = longPressDelay doubleTapGestureRecognizer . crave ( toFail : tapGestureRecognizer ) $0 . addGestureRecognizer ( doubleTapGestureRecognizer ) }) } }) } }
Addendum: Why Employ Button?
Twitter folks have commented that this would all exist much easier if I didn't use Button
but — like here — the Image
struct directly. This indeed makes the SwiftUI tap gestures work much better, just it also misses out a few smashing default features that Push has:
- Automatically highlighting on tap; and so fading that out if the mouse goes too far abroad
- Automatically tinting the image when the window is active and using gray when the window is inactive again (especially noticeable on Catalyst)
- Automatically adding some click padding around the content
I've tried various variations, but information technology seems longPress
is buggy on Catalyst. If you don't have to bother with Mac Goad, try post-obit sample code.
Conclusion
And so what'southward really special about the secret long-press action? It does enable the Debug Fashion of PDF Viewer, showing diverse settings that aren't really useful for regular folks, only that help with QA testing. If you're curious, download our app (it'southward free), long press on our icon in the Settings footer, and come across for yourself.
Source: https://steipete.com/posts/supporting-both-tap-and-longpress-on-button-in-swiftui/
0 Response to "Traduci Press the Button Again Start to Be in a State of Suspension"
Post a Comment