I have a simple app which adds a subview over the main view when the user clicks on a UIButton in the main view. This subview is of size 480x320 (I'm in landscape mode), but there is a boarder around the centre image in this subview which is transparent.
This is where my problem lies. I would like only the subview to process touches until it is removed from the superview, but if there is a touch event on the transparent boarder, the event gets passed to the superview, and ignores the subview, even though the subview is the full size of the window.
Doing some research into this, it seems as though this is what apple intended to happen, as touches will only get passed to opaque sections, even if the subview is the full size of the window. It is explained in the reference:
http://developer.apple.com/library/ios/#documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/MultitouchEvents/MultitouchEvents.html
I would like to be able to set it such that the touches will stay with the subview, regardless of any transparency issues. The hitTest:withEvent: method seems to do something like this, but more for passing touches to different subviews then the one that was touched.
Is there anyone who has a fix/work-around that can achieve this?
Simple solution is to subclass your subview and add the following empty method...
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
}
This will prevent the touch from getting passed to the superview, even if the user were to touch a transparent part of the view.
You should just put a clear button in the back of the xib. That will prevent touch events from going off.
If you make the backgroundColor of your subview hidden, you will obtain the same effect (a transparent border around the UIButton), and your view will receive touches:
You can execute this code just before adding the subview:
subview.backgroundColor = [UIColor clearColor];
subview.hidden = NO;
[parentView addSubview:subview];
Related
Possibly a duplicate, but I couldn't find an exact answer to my problem searching SO tonight...
I have a UIButton which contains a UIScrollView, so the button is the superview.
All I want to do is pass a single-tap event from the UIScrollView subview to the UIButton superview, and have the UIButton handle it as it would if the user tapped the button directly.
Setting the scroll view's userInteractionEnabled property to NO won't work for me because there is content the user can scroll. Doing so would defeat the purpose of the scroll view.
Is this possible? Thanks!
Like huoxinbird said, it's definitely not common to lay your views like that.
In the case where you are solidly adamant about this, you could use UITapGestureRecognizer and have it call the button's target and selector.
If you wanted to include the button highlight and such, then you may have to subclass UITapGestureRecognizer and forward the touchesEnded to the button.
Create a subclass of scrollview and override
-(BOOL)touchesShouldBegin:(NSSet *)touches withEvent:(UIEvent *)event inContentView:(UIView *)view
{
return YES;
}
Also subclass the uibutton(Your superview) and handle touches in touches began.
Usually you don't put scrollview within a button. Instead consider using a UIView as the superview of scrollview. Or tell us more about your use case, so I can understand why you have to design in this way.
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.
My problem is about tap detection.
I have a uiviewcontroller and there are some controls on uiview (labels, buttons, tableview, imageview, etc..)
When I tap the uibutton I display a small uiview (200x150), if the user taps the uibuttons in smallview I hide the smallview.
But I can't hide the uiview if the user taps the background.
I tried this code..
-(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
//NSLog(#"Touches began.");
[self hideShareView];
}
It doesn't work if I tap the another button in the uiviewcontrols view.
I just want my uiviewcontrol's uiview to react first.
I think its about firstResponder but I dont know how to set it first.
edit: i want it to work like a uiPopover in ipad.
I believe that the correct approach is to add a new transparent UIView when you display your "smallview". You should add a UITapGestureRecognizer to that UIView, in order to trigger the desired selector when the tap is detected. Also, you must ensure that the views are arranged properly, with your smallview being the one at the top, the transparent UIView being immediately below and the rest of the view hierarchy below the transparent UIView.
Finally, you should ensure to remove the transparent UIView from your view hierarchy at the same time that you remove your smallview.
Does that make sense?
Please have a look at this question (how-to-make-a-superview-intercept-button-touch-events), it does seem highly related.
try your hands with bringing your small view (i.e. shareview) to front or sent your main view behind your small view.
If it still doesn't work & you don't want your main view to perform any action when smallview is opened then try
[<YOUR_MAIN_VIEW> setUserInteractionEnabled:NO];
, but MAKE SURE you can do this only when you don't want your main view to perform any action when smallview is opened
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)
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.