UIScrollView touches don't seem to work - iphone

I used the "View-based Application" template in Xcode for an iPhone project and went into the view controller's XIB.
I changed the view from a basic UIView to a UIScrollView, and now the method
(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
never gets called. I have an NSLog statement in there. It gets called just fine when the controller's view is a UIView, so what's different/special about UIScrollViews that you don't receive touch events? How can I respond to a touch event?

UIScrollView does not pass on all touch events. It does this because it tries to interpret swipe and scroll gestures itself.
Have a look at this thread: http://discussions.apple.com/thread.jspa?messageID=7519817 to learn about UIScrollView and events. The bottom line is that you can handle events in your view's -(void)touchesEnded:(NSSet *)aTouches withEvent:(UIEvent *)anEvent but it's not straight forward.
I have personally seen some funkyness in UIScollView's effect on hit testing sub views.

I don't have any direct experience here, but I was looking over documentation that might explain this yesterday. I think you'll find that UIScrollView captures touches in order to handle its scrolling. Take a look at delaysContentTouches in the documentation window.

Just use a custom content subview, then implement touchesEnded:withEvent: in that view. You'll get all the taps you want!

Related

How to get a UIView under a UIScrollView to detect touches?

I have a UIScrollView ontop of my UIViewController recreating an effect like in the Gowalla iPhone app when you're on a spot's page. Under my scroll view I have a button that I want to be able to perform it's action even when the scroll view's frame covers it up (where it's ontop of the button, the scroll view's clear). How would I do something like this? Is it possible? (it has to be, Gowalla [somehow] did it)
As for me, I will transfer touch event to another view by override following methods.
– touchesBegan:withEvent:
– touchesMoved:withEvent:
– touchesEnded:withEvent:
– touchesCancelled:withEvent:
Like this way,
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// pass touch event by default implement.
[super touchesBegan:touches withEvent:event];
// redirect the touch events.
[anotherView touchesBegain:touches withEvent:event];
}
hitTest:withEvent: is used to decide which view should response touch event on the view hierarchy tree. If the view doesn't want to response touch event, it will return nil in hitTest. As result the above touch event methods won't be called.
I have no idea what this "Gowalla" app might do; hopefully, your description of "a button behind the scroll view, that you can touch 'through' the scroll view" is accurate.
If your button behind the scroll view can be sized to fill the entire contentSize area of the scroll view without screwing up your interface, the easiest solution would be to just put it inside the scroll view (under all the other views) and do just that.
Otherwise, your best bet is probably to create a custom view with a clear background to be placed in the scroll view as above. The easy solution is to have the custom view (probably a UIControl) just do whatever touching the button does. If that's not possible for some reason, your best option would be to override hitTest:withEvent: on the custom view to return the underlying button. I'd be wary of overriding hitTest:withEvent: on the scroll view itself, as that might interfere with scrolling.
Try adding the button on top of the scroll view. The only problem with that is if you hit the button, you will not be able to interact with the scrollView, but it will still be visible.

How to steal touches from UIScrollView?

Today on my creative time I did some quite comprehensive research on how to steal touches from a UIScrollView and send them instantly to a specific subview, while maintaining the default behavior for the rest of the scroll view. Consider having a UIPickerView inside of a UITableView. The default behavior is that if you drag your finger over the picker view, the scroll view will scroll and the picker view will remain unchanged.
The first thing I tried was to override
- (BOOL)touchesShouldCancelInContentView:(UIView *)view
and simply not allow the UIScrollView to cancel touches inside the picker view. This works, but it has an unpleasant side effect. You would like the picker view to respond immediately and thus you will have to set delaysContentTouches to NO. The problem is that you don't want the rest of the table view to respond immediately, because if it does the table view cell will always get highlighted for a few milliseconds before the scrolling starts.
The second thing I tried was to override
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
because I had read that the scroll view always returns itself, so that it will "steal" the touches from its subviews and later send them to the subview if they weren't of interest to the scroll view. However, this isn't true anymore. UIScrollView's default implementation of hitTest:withEvent: actually returns the subview that should receive the touch. Instead it uses gesture recognizers to intercept the touches.
So the third thing I attempted was to implement my own gesture recognizer and cause it to fail if the touch was outside of the picker view and otherwise succeed. Then I set all the scroll view's gesture recognizers to fail unless my gesture recognizer failed using the following code:
for (UIGestureRecognizer * gestureRecognizer in self.tableView.gestureRecognizers)
{
[gestureRecognizer requireGestureRecognizerToFail:myRecognizer];
}
This does in fact steal the touches from the scroll view, but the picker view never receives them. So I though maybe I could just send all the touches that my gesture recognizer receives using this code:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
for (UITouch *touch in touches)
[touch.view touchesBegan:touches withEvent:event];
}
The above code is a simplified version. I also make sure that the view is a picker view (or one of it's subviews) and set the appropriate state for the gesture recognizer as I mentioned above. I also did the same for canceled, ended and moved. However, the picker view was still not responding.
I also tried one last thing before returning to my regular work. During my extensive googling I read that nested UIScrollViews just magically worked since 3.x, so I tried putting my picker view inside a nested UIScrollView and set the following properties on it:
scrollView.delaysContentTouches = NO;
scrollView.canCancelContentTouches = NO;
As one would expect the outer scroll view didn't treat the inner scroll view any different than it treated the picker view, so the inner scroll view did not receive the touches. I thought that it was a long shot, but it was simple enough to implement, so I thought it was worth to give it a shot.
What I know is that UIScrollView has a gesture recognizer named UIScrollViewDelayedTouchesBeganGestureRecognizer that intercepts the touches and sends them to the appropriate subview after 150 (?) ms. I'm thinking that I should be able to write a similar recognizer that causes the scroll view's default recognizers to fail and instead of delaying the touches immediately sends them to the picker view. So if anyone knows how to write such a recognizer please let me know and if you have any other solution to the problem, you're very welcome share that as well.
Thank you for reading through the whole question and even if you don't know the answer you could still upvote the question so that it gets more attention (hopefully from someone that can answer it). Thanks! :)
Sometimes you have to ask the question before you can find the answer. Dan Ray had a similar problem and solved it with a very different solution.
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView* result = [super hitTest:point withEvent:event];
if ([result.superview isKindOfClass:[UIPickerView class]])
{
self.scrollEnabled = NO;
}
else
{
self.scrollEnabled = YES;
}
return result;
}
I've tested the code and it works fine for me as well. However, this is not really stealing touches from the scroll view, so if anyone knows how to actually steal touches that would be great.
Source:
UIPickerView inside UITableView.tableFooterView doesn't receive drag touches
A bit late, but I found this solution:
http://www.cocoanetics.com/2010/06/hacking-uiscrollview-gesture-recognizers/
Works for me
I'm also late to the party, but for newcomers, if you're just looking to flat out ignore swipes on the scroll view, what worked for me was to add a pan gesture recognizer to the view I want ignoring the swipes, like this:
let panGesture = UIPanGestureRecognizer()
panGesture.cancelsTouchesInView = false
myView?.addGestureRecognizer(panGesture)

iPhone: Handling touches transparently

I have a UIViewController and an associated with it UIView. The UIView has a number of subviews.
How to handle touch event inside a specified rectangle in my UIViewController subclass?
This handling should be transparent: UIView and its subviews should still receive their touch events even if they intersect with the specified rectangle.
P.S.
Overriding touchesBegan in the view
controller doesn't work. Inner views
doesn't pass events through.
Adding a custom
button to the end of UIView subviews
list also doesn't work. Inner
subviews doesn't receive touch
events.
touchesBegan/touchesMoved/touchesEnded is probably the way to go. Depending on exactly what you are trying to do, UIGestureRecognizer may also be an option.
To make sure subviews pass events up, set userInteractionEnabled to YES on the subviews.
UIView sets this to YES by default, but some subclasses (notably UILabel) override this and set it to NO.
Just because the views don't pass events through by default doesn't necessarily mean that's the way it has to be.
You could subclass the views that you are using and have them pass the events onto the view controller.
Another idea is to literally have a transparent handler on top of all of your views. That is, have a transparent UIView that sits above your views, handling touch events but also passing them through. I have no idea if this works in practice, but it sounds like it would.
Your views, and their controllers can handle a touch using touchesBegan, touchesEnded or touchesMoved. Within touchesBegan as an example you can then choose to pass the event to
the next responder in the responder chain. This way each of your views will get a chance to do something based on the touch event.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//do something here and then pass the event on
[self.nextResponder touchesBegan:touches withEvent:event];
}

Not receiving touchesEnded/Moved/Cancelled after adding subView

Title more or less says it all. In response to a touchesBegan event, my UIViewController recolours itself and adds some subviews.
It never receives the touchesEnded. I guess because the added subviews are somehow intercepting the event. I tried calling resignFirstResponder on the subviews to no avail.
The code works fine when I don't add the child views and the touch events are called as normal.
Any ideas?
Thanks
EDIT: Bit of detail and how I fixed it.
Basically I had a master view with some subviews, when I touched the subview, the event would be passed through to the master view, however, on this event I was removing the subviews and adding new ones in their place. The fact that the touch originated on a subview which no longer existed meant that the rest of the touch was lost.
I fixed this by overriding hitTest:withEvent in my master view, to stop touches ever getting tested against the subviews
Did you try to set the userInteractionEnabled property to NO for the subview before adding it as a subview ?
You're going to need pass the touch from the subview onto the superview using something like this:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
}

What can be the reason that my UIImageView subclass doesn't receive any touch events?

I have made sure that all superviews of my customized UIImageView and the UIImageView itself have userInteractionEnabled = YES;
Also in the nib all views and subviews have userInteractionEnabled = YES;
But for some reason, these get never called when I click on the UIImageView:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"check!");
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"other check");
}
I have an UIView that acts as an grouping for some subviews. Can that be a problem?
This is just a guess -- is there a UIScrollView somewhere in the view hierarchy? UIScrollViews don't pass touch events like normal views, you have to subclass it and implement a custom touchesBegan method. You can find information about that here -- it's a problem that sent me scratching my head and Googling for several hours the first time I encountered it.
After a while I figured out the following problem:
I had an UIView that I used to group some UIImageViews together, so that I can move all of them at once by moving that UIView only. I made the UIView 0.01 x 0.01 big while it does not clipsToBounds. As I clicked on subviews that were drawing outside the bounds of their superview, the UIView, no touch events where received on these subviews. I don't know if there were any touch events at all.
So, if you have the same problem, make sure that your touches occur on an area which fits into the superview. In my case, I just made that grouping UIView bigger, making the touch-sensible area bigger. After that, it worked. I now slightly remember that Apple had mentioned that in the documentation somewhere, that subviews may draw outside of their superviews, but touch events will be recognized only if they happen in the rectangle of the superview.
It looks like you have a typo there: the correct selector name is 'touchesBegan'. Are you adding this view programatically or through a nib?
touchesBegun should be touchesBegan, but I think that's just a typo?
Depsite that, usually touches not coming through indicates a stressed CPU. Try testing the performance of your app with Instruments.