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!
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 added one scrollview and imageview to my view. When I touch on this imageview my scrollview scrolls. And within my scrollview there are lots of imageviews which have touch events written separately.
Here is the view hierarchy
View
|
|
------- --------
| |
imageView1 scrollview
|
...........................
| | |.....|
imageviews2
Imageview1 is above imagesview2 in appearance.
And when I scroll the scrollview it is not possible to touch some of the imageViews within the scrollview as it comes behind the imageview within view.
How can I pass the touch events to the imageviews2 whenever it is behind imageview1.
You'll need to subclass UIView and override
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
in that subclass to skip imageView1.
You might get away with subclassing UIImageView and instantiating imageView1 as a member of that class, and overriding pointInside
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{ return NO; }
don't forget also to uncheck "delays content touches" and "cancellable content touches" on your scrollview !
I hope this answer might be useful for someone. I have done it without using hittest.
Took all the subviews in scrollview and checked if it is an image view and did the following.
Passed the touchevents to another view using [view touchesBegan:touches withEvent:event]; we have to write the same in touchesMoved and touchesEnded also.
Thanks,
Joku
You need to pass the touch event to the UIViews that are outside the bounds of the parent view.
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
// Convert the point to the target view's coordinate system.
// The target view isn't necessarily the immediate subview
CGPoint pointForTargetView = [self.targetView convertPoint:point fromView:self];
if (CGRectContainsPoint(self.targetView.bounds, pointForTargetView)) {
// The target view may have its view hierarchy,
// so call its hitTest method to return the right hit-test view
return [self.targetView hitTest:pointForTargetView withEvent:event];
}
return [super hitTest:point withEvent:event];
}
https://developer.apple.com/library/ios/qa/qa2013/qa1812.html
I have created a 3 Subviews to MyView by creating new class inherits from UIView.
then i tried to change text in a particular subview say second sub view.
But its not updating.So how to do this...
EDIT
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
CALayer *hitLater = [self layerForTouch:[touches anyObject]];
if ([hitLater isKindOfClass:[Tile class]])
{
Tile *tile = (Tile *)hitLater;
heldTile = tile;
[tile draw2];
[tile setNeedsDisplay];
}
}
Thanks in advance..
If you want a better answer show some code and how you add the text to your UIView subclasses etc.
But maybe you have to call setNeedsDisplay on your subviews so they get a redraw.
I have a UIScrollView that is set to have a clear background. Part of the scrollview does have content, but part does not (so it shows other views behind it). I would like to be able to click through the UIScrollView and to the MKMapView behind, but only for the transparent portion of the UIScrollView.
I have found some code which I am having a real hard time understanding how to get working:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if (![self yourMethodThatDeterminesInterestingTouches:touches withEvent:event])
[self.nextResponder touchesBegan:touches withEvent:event];
}
Could someone help me wrap my mind around how to forward a touch event to a view that is behind another view? Can I call - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event from a UIViewController?
What we did was to subclass UIScrollView and implement logic that passes responsibility down to views under it, if the touch happens inside of the transparent area.
In our case the transparent area is defined by contentOffset of 120 on Y axis, meaning our content starts 120 points below the start of the UIScrollView, and the code looks like this:
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
if (self.contentOffset.y < 0 && point.y < 0.0) {
return NO;
} else {
return YES;
}
}
Obviously this response is well past its prime but hopefully this is helpful to anyone searching for a solution.
Basically, it's up to you to determine what touch events you care to forward to another responder. If you simply want to forward all touch events, just remove that if statement in the code you posted so the next responder will receive all the touch events.
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.