Prevent reordering of elements in gestureRecognizers array - ios

I'm experiencing a bug in my app that is causing gestures to stop working that I previously added to a UITextField via addGestureRecognizer:. Essentially, I add a tap and long press gesture recognizer to the UITextField (which already has 7 gesture recognizers applied from iOS). When logging self.textField.gestureRecognizers, it shows the existing 7 gestures and then the two I added at the end of the array. The gestures work just like I expected.
However, when I present a modal view controller and then dismiss it, my two gestures stop working on the text field. I'm not sure exactly why, but the view does disappear and it resignsFirstResponder (the keyboard is always up when the modal VC is presented) which may be related. But I discovered the gestures aren't removed from the text field, but the order of the gestures in the array has changed. My custom gestures are now located at index 0 and 1 instead of 7 and 8. I believe the 7 default gestures are conflicting/overriding my custom ones (I assume later placement in the array overrides those before it) which explains why they stop working even though they're still applied.
My questions are:
- Do you know why it is reordering the elements in self.textField.gestureRecognizers?
- How do I prevent that from occurring to ensure my custom gestures always work, without breaking the default gestures for UITextField?
My current solution is to add the two gestures for the first time then store the array of total (9) gestures, then in viewDidAppear I change the gestureRecognizers array (yes it is settable) to my stored array. This guarantees the array will be the 7 built-in gestures followed by my two custom gestures in that order. But I discovered my gestures are overriding the default gestures (that bring up the popup to Cut, Copy, etc), so I have to reset the gestures back to the default 7 after my custom gesture occurs (which is just fine - I only need to trigger the action a single time after recognizing my custom gesture). Simple enough to do - I store the original gestures in a property as well. But this doesn't feel like the best solution. I'd prefer to figure out the cause and address that or go about the situation differently instead of duct-taping the code together.

My first solution was to always add my two gestures in viewDidAppear
viewDidAppear: is called when your view controller's view first appears, but it is also called again later when the presented view controller is dismissed.
Thus you are adding the gesture recognizers twice.
The simplest solution is to use a BOOL instance variable (we call this a "flag") which you set to YES the first time and test afterwards:
if (!self.addedGestures) {
self.addedGestures = YES;
// ... add them! ...
}
Now you will only add them once.
(On the other hand it might be argued that if you care about the order of the gesture recognizers in the array you are already doing something wrong. Use delegate methods to resolve conflicts between gesture recognizers - that's what they are for.)

Related

Conflict with a UIPanGestureRecognizer on a UITableView's superview

I'm trying to build something roughly similar to the drawer menu in Apple Maps on iOS.
In this Xcode project I'm attaching a UIPanGestureRecognizer on the VC's view, and as the panning happens, move vertically a UITableView with scrolling disabled.
The issue is every time after the pan ends, the didSelectRow method is called only after a second tap happens somewhere on the UITableView. Of course I'd like it to be called after the first tap.
The funny thing is that the bug does not happen if I enable the table's scrolling, and in the gesture recognizer's delegate have shouldRecognizeSimultaneouslyWith returning true.
Other funny thing is a very similar thing seems to happen in Apple Maps itself, if you try pulling the drawer up with the finger resting on a recent location entry from the list inside the drawer.
Thanks for your help!
I don’t understand well what are your saying.
But I think that the main problem is with “Chain Responder”. When you use PanGestureRecognizerand the UITableView property isScrollEnable = false in the Responder Chain the PanGestureRecognizer it is the first who is called, and the system wait to that fails or the event is not handled, then it is passed to the next in the Responder Chain, who is the UITableView. For that reason, it takes too long to get called the didSelectRow function
I suggest to you create a new UIView and insert in the ViewController in the storyboard o nib and put the UITableVIew outside of that UIView, then link the PanGestureRecognizer to that new UIView. In that way the Responder Chain don’t get in conflict with both, because the system can detect when the drag is in the new UIView and call only to the PanGestureRecognizer and when it is in the UITableView will call to the didSelectRow
Best Regards
Write if it does not resolve

Get UITextView Gesture (To Identify Location of Tap/LongPress)

I'm rather confident [editable] UITextView's become firstResponder when a long press or tap gesture occurs within the scrollView. I want to identify where in the view this touch occured. Digging through the documentation and source code didn't yield me much. I might be going about this wrong. My concern is a race condition if I just add my own tap recognizer (how can I be sure it is called before the textView's delegate methods).
For practical clarification, I want to call two similar functions from a delegate function (editingDidBegin) but depending if they touched the left or right half of the text view, I want to call either of the two.

Proper UIGestureRecognizer and Delegate design

This is a pretty hypothetical question just to understand proper design but lets say I have two custom UIViews.
One of them is essentially a container that I'll call a drawer. Its purpose is to hide and show content. It's a lot like the notification center on iOS where you swipe to pull it open and flick it back up to close it. It's a generic container than can contain any other UIView. It has a UIPanGestureRecognizer to track the finger that's pulling it open/closed. It might also have a UISwipeGestureRecognizer to detect a "flick".
The other view is a custom map widget that has UIPan/Rotation/Pinch GestureRecognizers.
I think the drawer view should be the UIGestureRecognizerDelegate for the Pan/Swipe GestureRecognizers so that it can prevent touches from being delivered unless the user is grabbing "the handle".
My first instinct is for the map to be the UIGestureRecognizerDelegate of the pan/rotation/pinch gestures so that it can allow them to all run simultaneously.
The problem I'm having is that, I really don't want the map to receive any touches or begin recognizing gestures until the drawer is completely open. I'd like to be able to enforce this behavior automatically in the drawer itself so that it works for all subviews right out of the box.
The only way that I can think to do this is to wire all of the gestures handlers to the ViewController and let it do everything, but to me that breaks encapsulation as now it has to know that the map gestures need to run simultaneously, that the drawer should only get touches on it's handle and that the map should only get touches when it's open.
What are some ways of doing this where the logic can stay in the Views where I think it belongs?
I would do something like this to make the subviews of the drawer disabled while panning. Essentially loop through the drawer's subviews and disbale interaction on them.
[self.subviews enumerateObjectsUsingBlock:^(UIView *subview, NSUInteger idx, BOOL *stop){
subview.userInteractionEnabled = NO;
}];
And something similar again for when you want to re-enable user interaction on the subviews.
This should already Just Work™. A gesture recogniser is attached to a view; when a continuous gesture is recognised, all subsequent touches associated with that gesture are associated with that view.
So in your case, when the drawer pan is recognised, no touches associated with that pan should ever cause behaviour in your map view's pan/pinch/rotation gestures (unless you explicitly specify that they should using the appropriate delegate methods).
Or do you mean that you want to prevent the user from, halfway through opening the drawer, using another finger (i.e. another gesture) to start scrolling the (half-visible) map? If so, you should just set userInteractionEnabled on the drawer's contentView (or equivalent) to NO at UIGestureRecognizerStateBegan/Changed and YES again at UIGestureRecognizerStateEnded/Cancelled.

Touch Down firing in a weird way

I'm adding a touch down action to a uitextfield (actually it's a subclass, but I think that might not be important). I created a simple view controller and added this textbox to it, and wired up the event to println("Hello").
When I quickly tap the item (both in simulator, and on my phone) it works perfectly and says hello!
I then created a UITableViewController subclass, and in one of the static cells I added the same textbox.
In this case, when I quickly tap the textbox nothing happens! When I actually hold down the mouse or my finger for about 1/2 a second, it works. But not if I quickly tap it.
This is different from the previous textbox, which always works perfectly no matter how fast I tap it.
Are there some problems with different events being intercepted ors something of that sort?
I even went so far as to add a tap gesture recognizer to both the table cell, and the textbox, but neither work unless I hold it down (the table cell action won't even fire unless I click off the textbox and into the cell proper, of course).
Thanks so much this is very strange.
UIButton not showing highlight on tap in iOS7
and
iOS - Delayed "Touch Down" event for UIButton in UITableViewCell
have a lot of information about this. Apparently there is a delay for uitableviewcells that can be avoided by taking some of the approaches above.
I'll post the solution that works for me once I work on it. thanks!
EDIT OP DID DELIVER!! (lol sorry)
in IOS8, the idea is that table cells no longer have the uiscrollview that would basically delay the touching, so what you can do instead is something like this in your page did load:
for subview in self.tableView.subviews as [UIView]
{
if subview is UIScrollView
{
let scroll = subview as UIScrollView
scroll.delaysContentTouches = false
break
}
}
So see how we're iterating over self.tableview's subviews, and anytime we hit a scrollview set delaysContentTouches to false. This worked for me on both the simulator and on my phone.

UISwipeGestureRecognizer to override all others?

I have an pretty standard application that uses gesture recognizers in various places. I’m trying to add an above-all UISwipeGestureRecognizer with three fingers which can be performed anywhere in the app, similar to the Apple four-fingered ones. This is working fine in some views, but if there’s another swipe recognizer beneath it, it’ll trigger that one instead of the new one.
I’d like this new three-finger swipe to be given priority at all times – I’ve added it to my root view controller’s view, but it still seems to bleed through at times.
Is there an easier way to do this than going through and requiring all other recognizers to fail?
You can use requireGestureRecognizerToFail: method to filter through unneeded gestures.
Apple doc.

Resources