iOSGestures (using iOS Gesture Recognizers)

iOSGestures

iOSGestures is a Xojo module designed to make detecting user gestures such as
Tap, Swipe, Pinch, and Rotate as easy a possible. The module uses declares to
connect up Apple’s own iOS Gesture Recognizers so you get ‘official’ behavior, including
when the user has enabled one or more accesability settings. Until Team Xojo adds
direct support iosGestures make makes adding them easy and they are fun to play with.

Supported Gestures: (with configurable parameters)
Tap (1-N taps by 1-N touches)
Swipe (Left, Right, Up, Down, 1-N Touches)
Long Press (1-N Touches, delay)
Pinch, Rotate, and Pan (1-N Touches)
Edge Pan From(Left, Right, Top, Bottom, 1-N Touches)

Gestures may be attached to either a view (e.g. to detect a swipe over the entire
screen) or a specific control (e.g. to detect taps on a canvas).

You may attach a gesture recognizer directly from the IDE by placing it on the shelf under
a view or directly from code.

Included are a rewrite of Xojo’s SwipeExample done using the new swipeLeft/Right/Up/DownGestures
and the creatively named ExampleOne project that demos all of the supported gestures.

https://github.com/sbeardsl/xojoGestures

Special Thanks: To Michel Bujardet, for XojoiOSWrapper and his TableView setup for sample code and
the Xojo Team for the Xojo examples, especially for iosAlert for showing how to get the delegate for
gesture events setup correctly.

Warning: This is the first time I’ve shared Xojo code through GitHub. I’ve made a good
faith effort to clean up naming and namespace issues but there is likely going to be a bit
of churn as I figure out the best way to layout the code so updates diff nicely online.

I appreciate your coding style/conventions. You could have charged for this module.

Congratulations for a much needed support for gestures, which are a landmark of iOS. This should have been implemented. Thank you for sharing :slight_smile:

I spent quite a bit of time adding swipe to my app through a canvas pointerdown and pointerdrag events, this would have saved me the effort.

I have added a note in https://github.com/Mitchboo/XojoiOSWrapper/tree/master to link to your repository.

[quote=156167:@Stephen J. Beardslee]Special Thanks: To Michel Bujardet, for XojoiOSWrapper and his TableView setup for sample code and
the Xojo Team for the Xojo examples, especially for iosAlert for showing how to get the delegate for
gesture events setup correctly.[/quote]

Stephen, I appreciate the attention, but I am only the curator of a project where I tried, in addition to my own methods, to put useful declares and classes created by others. In particular Jason King, for that particular implementation version.

Hopefully Xojo will get a hint, and add most of these much needed features in the next releases.

@Michel Bujardet Thanks for the link from the XojoiOSWrapper project.

I’ve updated the ReadMes to properly credit @Jason King for his overall contributions and for his TableView setup specifically.

[quote=156251:@Stephen J. Beardslee]@Michel Bujardet Thanks for the link from the XojoiOSWrapper project.

I’ve updated the ReadMes to properly credit @Jason King for his overall contributions and for his TableView setup specifically.[/quote]

Great :slight_smile:

Wow, thanks a lot, Stephen!
I am currently setting up a free library of basic control iOS extensions (currently with some build problems, see the animations … thread) and was about to add a Gesture Recognizer myself. Would it be ok to tweak your code into it?

However: Do you have any clue if the simulator is limited regarding touches? Two finger gestures on my MagicPad are not shown.

[quote=156263:@Ulrich Bogun]Wow, thanks a lot, Stephen!
I am currently setting up a free library of basic control iOS extensions (currently with some build problems, see the animations … thread) and was about to add a Gesture Recognizer myself. Would it be ok to tweak your code into it?

However: Do you have any clue if the simulator is limited regarding touches? Two finger gestures on my MagicPad are not shown.[/quote]

On the simulator, two fingers pinch and rotate are obtained with option-click. Two fingers drag with shift-option-click.

See https://developer.apple.com/library/ios/documentation/IDEs/Conceptual/iOS_Simulator_Guide/InteractingwiththeiOSSimulator/InteractingwiththeiOSSimulator.html

I have only the Magic Mouse, so I do not know how the trackpad could differ.But it is said there that it is the same.

It is. Thanks, I missed that information!

@Stephen (and the others too): I tried to implement the Gesture Recognizers directly into the iOSControl extension module, but I don’t have any clue how to catch the gestures then. The gesture is forwarded to the object I attached the GestureRecognizer to which then crashes with a “Selector unknown” message:

[quote]*** Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘-[UIButton UITapGestureRecognizer]: unrecognized selector sent to instance 0xc31cc80’
Application Specific Backtrace 1:
0 CoreFoundation 0x0235d946 __exceptionPreprocess + 182
1 libobjc.A.dylib 0x01981a97 objc_exception_throw + 44
2 CoreFoundation 0x023655c5 -[NSObject(NSObject) doesNotRecognizeSelector:] + 277
3 CoreFoundation 0x022ae3e7 forwarding + 1047
4 CoreFoundation 0x022adfae _CF_forwarding_prep_0 + 14
5 UIKit 0x00bf9287 _UIGestureRecognizerSendActions + 327
6 UIKit 0x00bf7b04 -[UIGestureRecognizer _updateGestureWithEvent:buttonEvent:] + 561
7 UIKit 0x00bf9b4d -[UIGestureRecognizer _delayedUpdateGesture] + 60
8 UIKit 0x00bfd4ca ___UIGestureRecognizerUpdate_block_invoke661 + 57
9 UIKit 0x00bfd38d _UIGestureRecognizerRemoveObjectsFromArrayAndApplyBlocks + 317
10 UIKit 0x00bf1296 _UIGestureRecognizerUpdate + 3720
11 UIKit 0x0080a26b -[UIWindow _sendGesturesForEvent:] + 1356[/quote]

The delegate does not fire, and I am unsure if this can work in a module anyhow. Do you have any ideas for a successful implementation? Currently it looks to me like we should use Stephen’s brilliant classes and hope for a native solution in one of the next releases.

@Ulrich Bogun without seeing your code -

‘NSInvalidArgumentException’, reason: '-[UIButton UITapGestureRecognizer]:
looks like a class name is being passed as a method selector to the UIButton. Your delegate object should be receiving the invocation with a selector whose name you passed when creating its class, so perhaps there is a problem connecting your delegate recognizer with the view?

My basic flow is
a) Create a new sub-class of NSObject (e.g. “myDelegateClass”)
b) Add a method that the OS will call to myDelegateClass using addMethod: <delegate method signature (“v@:@”)>
[you need to make sure your method actually has the right signature for the gesture recognizer callback “id as ptr, sel as ptr, recognizerDelegate as ptr” but you can ignore id and sel when you receive the callback]
c) Create a new object of my class (myDelegateClass alloc)
Now you have an actual object that will respond to method named which will invoke your callback code.
=== This happens in baseGestureRecognizer’s CreateDelegate method ===

Now that we have a delegate object for the system to talk to we have the system create an actual UITapGestureRecognizer using alloc and call InitWithTargetAction passing the new UITapGestureRecognizer object, your new delegate OBJECT (not class), and < myCallbackMethodName> so the system knows what selector to pass to your delegate object when the UITapGestureRecognizer object wants to invoke it.
===This happens in baseGestureRecognizer’s CreateGestureRecognizer method ===

Finally, you need to attach the UITapGestureRecognizer object to a UIView using the view’s addGestureRecognizer method which requires both a pointer to the view and a pointer to your delegate object.

Warning: For iOSControls the Handle method returns the correct pointer to use here (to a XOJCanvasView which is a sub-class of UIView). For iOSViews the both the ViewControler and Handle methods returns a pointer to a XOJViewControler which is NOT a sub-class of UIView. It does have a view: method which will get you what you need but if/when Xojo fixes/changes this in a future version you want to be ready so test the pointer you get from iOSViews to see if it responds to “addGestureRecognizer:” and if it doesn’t test it again to see if it responds to “view:” and if it does you can get the UIView pointer you need to call addGestureRecognizer on from there.
[see Attach(targetViewHandle) to see what I did]

In the current version of iOSGestures I create a unique ‘class’ for each recognizer. This is not really necessary, it is a holdover from when I was using the class name to get back to which Xojo recognizer object was being talked to. Now I just use the pointer to the system recognizer object.

Thanks a lot, Stephen! This is an excellent hint to where the problem may stem from. I will need to check the pointers I pass, maybe I have mixed them up somewhere. I wasn’t sure how to interpret the error message.
Basically, I certainly install “something” as a recognizer – the button I attach it to doesn’t respond to the selected gesture again –, but not catching it brought me to the idea it would have something to do with GRs pushing unrecognized gestures upwards on their parent chain. So maybe I am not that far away from getting it to work.
Your unique naming scheme confused me a bit. Could be that’s where I left the right track.

And thanks for your reminder on the iOSView handle problem. I reported that too but forgot about it – will have a look at it once I (hopefully) got the Recognizers running.

I’ve been planning on doing a minor refactor/tune up of the iOSGestures module.

If you could tell me what is missing I might be able to add it to the module for you.

Your code (attaching UITapGesture to a button) got me interested enough to put together a very simple project just to see what would happen. I created a view with an iOSButton on it and attached a tapGesture set to watch for double-taps on the button. The button worked fine, you could both single and double tap the button which I guess shows the advantage of using Apple’s system recognizers instead of rolling our own.

I’ll add MultiTapButton to the Example project when I finish the update but here is a temporary dropbox link if you are interested. Attaching the tapGesture took 4 lines of code (plus adding a property and the actual tap and double event handlers) but it could all have been reduced to a single line with a gesture specific constructor.

Add property tapHandlerForButton of type tapGesture to View1
Add method “tapHandler” with parameters “recognizer as iosGestures.tapGesture, pos as xojo.Core.Point, eventInfo as iosGestures.gestureEventInfo” to View1]
In View1 Open Event add:
tapHandlerForButton = new iOSGestures.tapGesture()
tapHandlerForButton.NumberOfTapsRequired = 2
AddHandler tapHandlerForButton.Tap, AddressOf tapHandler
tapHandlerForButton.Attach( MultiTapBtn.Handle )

https://dl.dropboxusercontent.com/u/242430/MultiTapButton.zip

=== Nothing is this post should be considered as supporting the idea of actually implementing a multi-tap button in an app’s UI ===

Thanks again, Stephen! I would have liked to build a native declare version, with no need to add Xojo GestureRecognizers to a class, but I’ve come to the conclusion that would be too much uncertain work with probably no real use when mostly controls having built-in GRs like buttons will be used, and for the other cases your implementation is brilliant (and would need subclasses anyway).
Would you oppose against me adapting your class with a few convenience methods to my lib, of course with full reference to it? I would rather restrain to adding more functions to it – the old UIVIew animation classes are now fully available in the iOSControl extension module :wink:

@Stephen J. Beardslee thank you for your contribution with XojoGestures! Great work!

Is it possible to make this work with ImageView controls? I have an image viewer that adds multiple ImageView controls to a view and I’d like users to be able to tap an individual image to open it in another view. I realise I could use Canvas controls instead of ImageView controls, however the in-built ScaleAspectFill property of the ImageView is very fast and produces a much better image than drawing into a Canvas at a reduced scale.

Hi Jason, I know this isn’t for iOSGestures (I haven’t looked through his code enough to understand what is/isn’t possible) but with my UITapGestureRecognizer in UIKit of iOSKit you should be able to detect a tap. The following code in the view open event should do it:

Declare sub addGestureRecognizer lib "UIKit" selector "addGestureRecognizer:" (ctrl as ptr, recognizer as ptr) //recognizer1 is a UITapGestureRecognizer dragged onto the view //imageView1 is an iOSImageView dragged onto the view addGestureRecognizer(imageView1.handle, recognizer1)
You would then receive the gesture event in the recognizer1 object. Maybe this helps?
Jason

Thanks Jason. I’m using some of your classes that were provided outside of the latest iOSKit Github code and so dropping UIKit into my app is causing me some grief. For example, I have your UISearchBar with a GotFocus event definition but the one in iOSKit does not have that. There seem to be a few other issues as well. But I’ll try to work out how to slice and dice the gesture stuff to get it into my app. :slight_smile:

Thanks for letting me know that, I’ll fix that when I update the code. Looks like I overlooked updating the UISearchbar after adding that for you.

Thanks Jason. I’ve tried updating my UIKit and putting back the UISearchBar but I get a series of build errors in my Views that have a UISearchBar, viz:

Location: ViewName.UISearchBar.Name Layout (property name). Issue: Type "Text" has no member named "UISearchBar" Name.

I hope you’re not going to tell me to delete and re-add them. The Auto-Layout implications could send me over the edge! :wink:

That is possibly the least helpful error message I have seen in Xojo :slight_smile: Maybe @Joe Ranieri would have an idea what would cause that message? I plan to update iOSKit in the next day or two and it will include the new UISearchBar, maybe that will help.

Jason

PS. We might want to take this to PM since it’s heading off topic :wink:

I don’t want to take this off-topic but I just want to post this bit of information in case it helps anybody else in the future. I have found that answers to other people’s problems very often help me solve my problems when I conduct a form search, which makes this forum an extremely valuable resource.

The fix for the “Type “Text” has no member named…” error was to remove the prefix for the UISearchBar control’s Super name. So the super was set to “UIKit.UISearchBar” and by removing “UIKit.” the error was not longer reported.

Now, back on topic ! :slight_smile: