I have a view within a UIScrollView that loads an additional subview when the user presses a certain area. When this additional subview is visible, I want all touch events to be handled by this - and not by the scrollview.
It seems like the first couple events are being handled by the subview, but then touchesCancelled is called and the scrollview takes over the touch detection.
How can I make sure that the subview gets all the events as long as the movement activity is being performed on this view?
This is my implementation on touchesMoved - which I thought would do the job...
-(void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[touches allObjects] objectAtIndex:0];
CGPoint touchPt = [touch locationInView:self];
UIView *hitView = [self hitTest:touchPt withEvent:event];
UIView *mySubView = subviewCtrl.view;
if(hitView == mySubView) {
[subviewCtrl.view touchesMoved:touches withEvent:event];
}
else {
NSLog(#"Outside of view...");
}
}
The responder chain hierarchy "normally" goes from subview to superview, so you shouldn't need to do the hitTest in your superview. The problem that you are having is not that you need the superview to invoke touchesMoved on the subview, but rather that UIScrollView subverts the normal responder chain hierarchy by intercepting touch events in order to deliver a smooth scrolling experience to the user. If you don't want this behaviour, then you can disable this behaviour in the scrollView by sending it the following message:
[scrollView setDelaysContentTouches:NO];
Note that this will make sure that your subview has first crack at handling the events in question (provided that it is in fact the first responder). This can negatively impact the scrolling and zooming performance of the scrollView, however, which is probably why Apple sets it to YES by default.
Related
I want to disable touches on all areas of the screen apart from a specific few points (e.g buttons). I.e. I don't want 'touchesBegan' to trigger at all when I tap anything other than a button. Calling
self.view.userInteractionEnabled = NO;
has the desired effect for not registering touches, but then of course I can't tap any buttons. I basically want the button to still work, even if there are 5 points touching the screen, i.e. all touch inputs have been used up, and the button represents the 6th.
Is this possible?
I've tried inserting a view with userInteraction disabled below my buttons, but it still registers touches when the user taps the screen. It seems the only way to disable touch registering is to do so on the entire screen (on the parent UIView).
UPDATE:
I've tried using gesture recognizers to handle all touch events, and ignore those that don't qualify. Here is my code:
#interface ViewController : UIViewController <UIGestureRecognizerDelegate>
...
- (void)viewDidLoad
{
[super viewDidLoad];
UIGestureRecognizer *allRecognizer = [[UIGestureRecognizer alloc] initWithTarget:self action:nil];
allRecognizer.delegate = self;
[self.view addGestureRecognizer:allRecognizer];
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
CGPoint coords = [touch locationInView:self.view];
NSLog(#"Coords: %g, %g", coords.x, coords.y);
if (coords.y < 200) {
[self ignoreTouch:touch forEvent:nil];
return TRUE;
}
return FALSE;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(#"%i touch(es)", [touches count]);
}
However the screen still 'reads' the touches, so if I place 5 fingers down, the 6th one won't trigger a button press...
You need to set up an invisible UIButton and lay it between the view that should not register touches and the UIButtons that should still be active.
Now you need to set the invisible button's 'userInteractionEnabled':
//userInteractionEnabled == NO => self.view registeres touches
//userInteractionEnabled == YES => self.view doesn't register touches
[_invisibleButton setUserInteractionEnabled:NO];
What really matters in this solution is that both - the invisible and the visible buttons are direct subviews of the VC's view.
You can download an example project from my dropbox:
https://dl.dropboxusercontent.com/u/99449487/DontTapThat.zip
However this example just prevents the handling of certain touches. Completly ignoring input isn't technically possible: Third party apps are not responsible for for detecting input. They are just responsible for handling input. The detection of touch input is done iOS.
The only way to build up a case like you describe it in the comments is to hope that iOS won't interpret the input of your case as a "finger" because it's most likely going to cover an area that's way bigger than a finger.
So in conclusion the best way would be to change the material of the case you're about to build or at least give it a non conductive coating. From a third party developers point of view there is no way to achieve your goals with software if there is a need for 5 fingers as described in the comments.
There is couple of methods in UIView that you can override:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event; // recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event; // default returns YES if point is in bounds
This should prevent to call touchBegin and other methods.
Regards.
I have a nice solution for this. What ever the area you want to hide the interaction place a transparent button on top of the area.
touchesBegan is a default method so it must call all the time when touch happens on view so there is no way-out, But you can still do one thing set
self.buttonPlayMusic.userInteractionEnabled = FALSE;
for the object you don't need touch may be this could be help you with your desired output.
Have you tried using a UIScrollView as the background ? i.e the area where you do not want touch events to be fired.
UIScrollView does not call the touch methods.
You can add UIImageView Control on that area where you want to disable touch event.you can add UIImageView object as top of self.view subViews.
Example
//area -- is that area where you want to disable touch on self.view
UIImageView *imageView=[[UIImageView alloc]initWithFrame:area];
[self.view addSubView:imageView];
You touchDelegate will always call in this way, but if you are doing some task on touch then you can do your task like this way.
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
UIButton *touchObject=(UIButton*)[touch view];
if ([touchObject isKindOfClass:[UIButton class]])
{
//Do what ever you want on button touch
}
else{
return;
}
}
I have a lot of UIImageViews in my main view, some of these have images, others are blank. I have setup code that will check which UIImageView currently contains an image. At the same time, this code will take care of allowing a UIImageView with an image to be moved around.
Now what happens is this: when moving a selected UIImageView around (for some reason and quite randomly), the image will not stay on top of the other UImageViews in the screen. The reason why I say that this is random is because it will stay on top of some of the other views, but not on top of others.
The behavior is unexpected, several problems arise:
Visually it looks bad; there is no reason why a touched UIImageView should slip under another.
The way I have the code going is to allow UIImageViews to be moved only if they contain an image. So if the UIImageView goes under another who does not contain an image, I cannot touch and move it again. It looks like it is stuck in place.
Please note that I have not been setting subviews at all for this code, thus why this behavior occurs is beyond me.
So what my question boils down to, is there any way that I can tell the code to:
Get the object that was touched.
If it is a UIImageView with an image, then allow me to move the UIImageView.
allow this UIImageView to supersede (be on top of) all other UIImageViews.
Code reference:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{UITouch *touch;
UITouch *touch;
CGPoint touchLocation;
for (UIImageView *fruit in fruitArray)
{
// Check that the UIImageView contains an image
if([fruit image] != nil)
{
// Get the touch event.
touch = [touches anyObject];
// Get the location for the touched object within the view.
touchLocation = [touch locationInView:[self view]];
// Bring the UIImageView touch location to the image's center.
if([touch view] == fruit)
{
fruit.center = touchLocation;
}
}
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
//allow the selected event (in our case a UIImageView) to be dragged
[self touchesBegan:touches withEvent:event];
}
Thank you for your help! Let me know if you need a better explanation
[self.view bringSubviewToFront:imageView];
This was what you were looking for...
My problem is quite strange but simple.
I subclassed a my customer UIScrollView: MyScrollView, where i disabled the scroll:
self.scrollEnabled = NO;
that means apart from
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
all other UIScrollViewDelegate method won't be called
and in MyScrollView i do the content scroll by detecting the user touch movement on screen, that is to say no flipping, no bounces, my implementation is in the touchesMoved:withEvent: method
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
// NSLog(#"touch.phase:%d",touch.phase);
CGPoint currentPosition = [touch locationInView:self];
CGPoint lastTouchPosition = [touch previousLocationInView:self];
CGFloat deltaY = lastTouchPosition.y - currentPosition.y;
CGFloat contentYOffset = self.contentOffset.y + deltaY;
[self setContentOffset:CGPointMake(0,contentYOffset) animated:NO];
}
after the user drag movement have been finished, i do my own method according to the content offset of MyScrollView in touchesEnded:withEvent:
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
//some implementation according to current content offset
}
user moves finger on screen, whenever the finger left the screen surface, the touchesEnded:withEvent: get called and i can implement my own method correctly.
BUT, when user move finger on screen surface from inside the screen to outside either on top or bottom, then lift the finger up, the touchesEnded:withEvent: method never got called, it seems like the ios doesn't treat the move out bounds(top or bottom) event as a touches end event, also ios wouldn't know the what is going on when touch is outside it screen bounds
someone may suggest me to detect the current position in touchesMoved:withEvent: to check out whether it is inbounds or not. this MAY WORK WHEN THE MOVEMENT IS VERY SLOW, but when you move very fast, the system can not detect every point position, it seems like the movement is detected in a certain time interval.
can any one help me out how could i detect if the user finger has moved out of bounds or not
I think the touchesCancelled:withEvent: method will be called !
I have resolved this problem
bcs UIScrollView does too much work that we can not handle some event ourselves, fortunately the UIView can detect the touche move out of bounds and will invoke the touchesEnd:withEvent: method.
considering that replacing MyScrollView's superclass with UIView has too much work to do, so i figured out a simple way to resolve:
i added an TouchActionDetectView subclassed from UIView, whose work is to detect all user touches event and deliver those event to MyScrollView. of course i have to clear the background color of TouchActionDetectView to avoid blocking other view content.
I have several UIImageView as subview of a UIView that act as a canvas. The ImageViews receive touch events, so I can move them.
If I pan two views, each finger on each of them I can move two views at the same time. I don't want that.
I checked the maximumNumberOfTouches property on the superview but it affects that view, but it doesn't prevent that each other subview receives the touch event.
Any ideas how to avoid this behavior?
Thanks
How about just setting a flag that an image view is already moving, and the pan gesture on all views check that flag before actually moving their views? Just set the flag to false once the view has stopped moving.
you may do like this , extend UIImageView as a Custom UIView may called YouImageView
in the touch event
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
for ( UIView *v in self.superview.subviews )
{
if ( v != self )
{
v.userInteractionEnabled = NO;
}
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
for ( UIView *v in self.superview.subviews )
{
v.userInteractionEnabled = YES;
}
}
done!
I have a view derived from a UIScrollView controller that I use to page through images in a library. That works fine.
I overlayed a UIView on the right side to handle touches for scrolling quickly through the list of images. My problem is that if I pass the touches through to either 'super' or 'nextResponder' they never make it to the UIScrollView object below.
My question is how can I force the UIScrollView below to handle the touches that I don't need to handle? I'm setting a timer for 0.3 seconds that during that time all touches are passed to the UIScrollView to handle. So if the user started a swipe gesture to turn the page, it will happen.
Here's the code for the touchesBegan method:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
// Start a timer to trigger the display of the slider and the processing of events.
touchesTimer = [NSTimer scheduledTimerWithTimeInterval:0.3 target:self selector:#selector(showSlider:) userInfo:nil repeats:NO];
// Where are we at right now?
UITouch *touch = [touches anyObject];
CGPoint currentPoint = [touch locationInView:self.view];
lastPage = [self page:currentPoint.y];
// Pass the event though until we need it.
// [self.nextResponder touchesBegan:touches withEvent:event];
if ([touch.view isKindOfClass:[UIScrollView class]])
{
if (self.nextResponder != nil &&
[self.nextResponder respondsToSelector:#selector(touchesEnded:withEvent:)])
{
[self.nextResponder touchesEnded:touches withEvent:event];
}
}
}
I would try using a regular UIViewController rather than a UIScrollViewController and then add the scroll view object where you need it and put the scroll faster view you want on the right side. This way they are separate and the touches won't get confused.
Here's an easier solutions that worked well for me:
In the UIView on top of the UIScrollview make the width and height both 0 (so that the view is no longer technically on top of the UIScrollView) and set clipsToBounds = NO (so that the contents of the view still show up on top of the UIScrollView).
self.OverlayView.clipsToBounds = NO;
CGRect frame = self.OverlayView.frame;
self.OverlayView.frame = CGRectMake(frame.origin.x, frame.origin.y, 0, 0);
Note that if the UIView contains interactive controls then they will no longer work. You'll need to move it into it's own view above the UIScrollView.