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];
}
Related
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.
i have a quick question,
I have a view with multiple subviews.
In my view i have touches began etc..
is there a way to retrieve a subview from the touches began without having to detect the location from the touch and then have a big if else to c which subview is in that specific area.
Thanks.
My solution is instead of calculating the touch position from the super view. Sub class the child view and get the touch position form there only and convert the point with respect to the super view.
The method you want is the UIView instance method -(UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event. That will return you the lowest-level subview containing point.
So you get the point of your touch, pass it to that method of your biggest container UIView, and get back a handle to the frontmost subview that got touched. Easy peasy.
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];
}
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.
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!