Hola guys.
I'm experiencing a little problem with touch handling and UIResponder.
My goal is to manage a single touch (moved) to interact with different views.
I added a gesture recognizer to my UIImageView that send a simple message to my customViewController. It allocate and display a custom view (subclassing a UIButton) over the tapped UIImageView.
I would be able (without have to release the initial finger tap) to send touch movement to my new allocated custom button.
It seems that my UIImageView swallow my touch and so my new Button can't feel the finger movement.
I tried to resign first responder but it seems having no effect.
My custom Button work pretty good but only if I release the first tap and then tap again over the new displayer button.
Any suggests?
Thank you in advance.
Some code here:
Inside my viewController I attached a longPressGestureRecognizer to my profileImageView that draw a circular button surrounding the profileImageView.
-(void)showMenu:(UIGestureRecognizer*)gesture {
[profileImageView removeGestureRecognizer:gesture];
[profileImageView setUserInteractionEnabled:NO];
ConcentricRadius radius;
radius.externalRadius = 75;
radius.internalRadius = 45;
GTCircularButton *circularMenu = [[[GTCircularButton alloc] initWithImage:[UIImage imageNamed:#"radio.png"] highlightedImage:[UIImage imageNamed:#"radio_selected.png"] radius:radius] autorelease];
[circularMenu setTag:kCircularTagControllerCircularMenu];
[circularMenu setCenter:[profileImageView center]];
// Buttons' frames will be set up by the circularMenu
UIButton *button1 = [[[UIButton alloc] init] autorelease];
UIButton *button2 = [[[UIButton alloc] init] autorelease];
UIButton *button3 = [[[UIButton alloc] init] autorelease];
UIButton *button4 = [[[UIButton alloc] init] autorelease];
[circularMenu setButtons:[NSArray arrayWithObjects:button1, button2, button3, button4, nil]];
//[[self view] addSubview:circularMenu];
[[self view] insertSubview:circularMenu belowSubview:profileImageView];
}
Now I handle manually the touchEvent inside the circularMenu using the touchMoved methods.
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint touchLocation = [(UITouch*)[touches anyObject] locationInView:self];
for (UIView *aSubview in _roundButtons) {
if ([aSubview isKindOfClass:[UIButton class]]) {
UIButton *aButton = (UIButton*)aSubview;
CGPoint buttonTouchPointConverted = [aButton convertPoint:touchLocation toView:aButton];
if (CGRectContainsPoint([[aButton layer] frame], buttonTouchPointConverted)) {
[self rotateHighlightImage:aButton];
}
}
}
}
The goal is always to handle the end of gestureRecognizer and pass the touch event to the new instance created without having to lift my finger from the screen.
Any ideas?
PD: I handle touch manually with touchMoved cause if I use the addTarget:action:forControlEvents: on my buttons (1,2,3, and 4) the button that detect touchEvent swallow it and make other buttons impossible to feel the touch moving over them.
Your touchesEnded:withEvent: will have to be the same as your touchesMoved:withEvent: except it must invoke the method which is probably tied to the control event Touch Up Inside event.
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint touchLocation = [(UITouch*)[touches anyObject] locationInView:self];
for (UIView *aSubview in _roundButtons) {
if ([aSubview isKindOfClass:[UIButton class]]) {
UIButton *aButton = (UIButton*)aSubview;
CGPoint buttonTouchPointConverted = [aButton convertPoint:touchLocation toView:aButton];
if (CGRectContainsPoint([[aButton layer] frame], buttonTouchPointConverted)) {
[aButton sendActionsForControlEvents:UIControlEventTouchUpInside];
return;
}
}
}
}
The only change I have made is to have the button send that control event for us.
Original Answer
In the function that handles your gesture, do this,
if ( gesture.state == UIGestureRecognizerStateEnded ) {
CGPoint point = [gesture locationInView:button];
if ( CGRectContainsPoint(button.bounds, point) ) {
[button sendActionsForControlEvents:UIControlEventTouchUpInside];
}
}
This checks whether the last touch of the gesture was inside the button and sends a touch up inside event to all its targets.
Ok guys finally I solved my problem and I reached my goal!!
I discovered what was the real problem.
Simply the GestureRecognizer handle touch Event until it's state is setted to UIGestureRecognizerStateEnded and for this reason the GestureRecognizer swallow touch events with no opportunity to send touch events to the rest of responder chain.
I read again the UIGestureRecognizer class reference and I found the property enabled.
To solve my problem the first thing I do when the gesture launch the target method is to set the gesture enabled to NO. In this way the gesture release immediately the touch control and the touchMoved can be executed.
Hope this help someone.
Above the modified code:
-(void)showMenu:(UIGestureRecognizer*)gesture {
[gesture setEnabled:NO]; // This is the key line of my problem!!
[profileImageView removeGestureRecognizer:gesture];
[profileImageView setUserInteractionEnabled:NO];
ConcentricRadius radius;
radius.externalRadius = 75;
radius.internalRadius = 45;
GTCircularButton *circularMenu = [[[GTCircularButton alloc] initWithImage:[UIImage imageNamed:#"radio.png"] highlightedImage:[UIImage imageNamed:#"radio_selected.png"] radius:radius] autorelease];
[circularMenu setTag:kCircularTagControllerCircularMenu];
[circularMenu setCenter:[profileImageView center]];
// Buttons' frames will be set up by the circularMenu
UIButton *button1 = [[[UIButton alloc] init] autorelease];
UIButton *button2 = [[[UIButton alloc] init] autorelease];
UIButton *button3 = [[[UIButton alloc] init] autorelease];
UIButton *button4 = [[[UIButton alloc] init] autorelease];
[circularMenu setButtons:[NSArray arrayWithObjects:button1, button2, button3, button4, nil]];
//[[self view] addSubview:circularMenu];
[[self view] insertSubview:circularMenu belowSubview:profileImageView];
}
I think you should change a setting on the view, once you create the button. Eg.
myImageView.userInteractionEnabled = NO;
This way, the UIImageView will not listen to the touches any more.
Let me know if it works.
Related
I want to be able to make a gesture recognizer listening for a two finger swipe, anywhere on the screen.
Right now I have a UITableView in a sub-view that scrolls, of course, and it doesn't seem to pick up the UIGestureRecognizer that I set on the view. When you use the two fingers, it just scrolls...
Is there a way to make the entire view, maybe superview, listen for a two finger touch and when it recognizes it, it will instead of scrolling the table view, do what I want it to do?
EDIT: Heres my code but this is suposed to be a very general question. I just want to be able to have UIGestureRecognizers across the entire view, the whole thing.
navBarTitle.title = #"Crunch";
//opening the menuView from launch
//setup the customtable view as a subview in menuView
SDMenuViewController *mvc = [[[SDMenuViewController alloc] initWithNibName:#"SDNestedTableView" bundle:nil] autorelease];
[self addChildViewController:mvc];
[mvc didMoveToParentViewController:self];
[menuView addSubview:mvc.view];
[mvc.view setFrame:CGRectMake(mvc.view.frame.origin.x, mvc.view.frame.origin.y, mvc.view.frame.size.width, mvc.view.frame.size.height + 44)]; //add navBarHeight, for some strage reason
//add swipe down gesture on for the menu
UISwipeGestureRecognizer *swipeClose =[[[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(closeMenu)] autorelease];
swipeClose.numberOfTouchesRequired = 2;
swipeClose.direction = UISwipeGestureRecognizerDirectionUp;
UISwipeGestureRecognizer *swipeOpen = [[[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(openMenu)] autorelease];
swipeOpen.numberOfTouchesRequired = 2;
swipeOpen.direction = UISwipeGestureRecognizerDirectionDown;
for (UIGestureRecognizer* rec in menuView.gestureRecognizers) {
[rec requireGestureRecognizerToFail:swipeClose];
[rec requireGestureRecognizerToFail:swipeOpen];
}
for (UIGestureRecognizer* rec in mvc.view.gestureRecognizers) {
[rec requireGestureRecognizerToFail:swipeClose];
[rec requireGestureRecognizerToFail:swipeOpen];
}
[mvc.view addGestureRecognizer:swipeClose];
[mvc.view addGestureRecognizer:swipeOpen];
[self.view addGestureRecognizer:swipeClose];
[self.view addGestureRecognizer:swipeOpen];
Maybe I did not explain my question clearly. But for what I was doing this ended up working, and maybe it will help other people in the future:
Two finger swipe in UIScrollview for iPad application
you can try to do something like this:
UISwipeGestureRecognizer* p = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(actionPan:)];
p.numberOfTouchesRequired = 2;
for (UIGestureRecognizer* rec in tableView.gestureRecognizers) {
[rec requireGestureRecognizerToFail:p];
}
[self.view addGestureRecognizer:p];
hope that will help
//In view did load
[self.view setMultipleTouchEnabled:YES];
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if ([[event touchesForView:self.view] count] > 1) {
NSLog(#"%d active touches",[[event touchesForView:self.view] count]) ;
UISwipeGestureRecognizer *swipeGesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(handleSwipeGesture:)];
swipeGesture.direction = UISwipeGestureRecognizerDirectionUp;
[self.view addGestureRecognizer:swipeGesture];
}
}
-(void)handleSwipeGesture:(UISwipeGestureRecognizer *) sender
{
//Gesture detect - swipe up/down , can be recognized direction
if(sender.direction == UISwipeGestureRecognizerDirectionUp)
{
// do some thing...
}
}
i Hope this would solve your problem. and in touches enabled make the count equal to 2 or any number of touches.. :) feel free to ask if you have any doubts
Add another view with frame of self.view over self.view like "viewForGesture" and add gesture recognizer on that view like
UIView *viewForGesture = [[ [UIView alloc]initWithFrame:self.view.frame] autorelease];
[viewForGesture setBackgroundColor:[UIColor clearColor]];
[self.view addSubview:viewForGesture];
.
.
.
[viewForGesture addGestureRecognizer:swipeOpen];
[viewForGesture addGestureRecognizer:swipeClose];
There's two way of making UIView detecting touch. The approach you defined and want that's UIGestureRecognizer, that answer already given by Ezeki.
Another approach you can use to detect touch is to override UITouch delegates.
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
if(touch.tapCount==2)
{
//Do something ... (Here, you can also check for the object which touched.)
}
}
i have a main view controller with two subview in it,one is note-view and other one is share-view . When i click the cell of the tableview that is in the main-view controller ,it popups note-view,there is button in the note-view ,if the user tap the button it pop sup a another subview ,the share-view .But i need to close the share view when the user tap the note-view.Is that possible to implement a touch event in a subview ?
I have done this coding but result is error
in viewdidload
NotesView *label =[[NotesView alloc]init];
UITapGestureRecognizer *tapGesture =
[[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(NoteTap)] autorelease];
[label addGestureRecognizer:tapGesture];
[tapGesture release];
then
-(void)NoteTap
{
UIView *langview = (UIView *)[self.view viewWithTag:120];
//ViewWithTag Number should be same as used while allocating
[langview removeFromSuperview];
}
Inside touchesBegan: of the view controller, you can check to see if a touch event takes places within the frame of the subview like so.
CGPoint touchPoint = [touch locationInView:self];
if (CGRectContainsPoint(label.frame, touchPoint)) {
UIView *langview = (UIView *)[self.view viewWithTag:120];
[langview removeFromSuperview];
}
I have implemented UITapGestureRecognizer on UIImageView, It is working on first tap. On First Tap, I am hiding that image and starting animation. Once the animations are completed, i am showing the image again. But After setting setHidden:FALSE, I am not getting the Tap event of that UIImageView.
Following is the code I am using :
- (void)viewDidLoad{
[super viewDidLoad];
defaultDogView= [[UIImageView alloc] initWithFrame:CGRectMake(3, 270, 110, 210)];
[defaultDogView setImage:[UIImage imageNamed:#"dog1.png"]];
defaultDogView.userInteractionEnabled = YES;
[self addGestureRecognizersToPiece:defaultDogView];
[self.view addSubview:defaultDogView];
}
- (void)addGestureRecognizersToPiece:(UIImageView *)piece
{
NSLog(#"in Gesture");
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(singleTapPiece:)];
[tapGesture setDelegate:self];
[piece addGestureRecognizer:tapGesture];
[tapGesture release];
UILongPressGestureRecognizer *longPressGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPressPiece:)];
[piece addGestureRecognizer:longPressGesture];
[longPressGesture release];
NSLog(#"%#", [piece gestureRecognizers]);
}
- (void)singleTapPiece:(UITapGestureRecognizer *)gestureRecognizer
{
NSLog(#"Image Tapped");
/** Hide the default Image and start the animation ***/
[defaultDogView setHidden:TRUE];
/***Animating the Dog***/
[dogArray addObject:[SpriteHelpers setupAnimatedDog:self.view numFrames:69 withFilePrefix:#"dog" withDuration:(12) ofType:#"png" withValue:0]];
dogView = [dogArray objectAtIndex:0];
//[self addGestureRecognizersToPiece:dogView];
[self performSelector:#selector(callBubbleUpdater) withObject:nil afterDelay:5.5];
}
-(void)showDogFrame{
NSLog(#"%#",[defaultDogView gestureRecognizers]);
[defaultDogView setHidden:FALSE];
defaultDogView.userInteractionEnabled = YES;
}
When view is hidden or its alpha component is zero that view won't receive any UIGestureRecognizers.
I can suggest to use next approach if you need to hide some view (let's name it touchableView) but want it to respond to gestures:
Create backgroundView with the same frame as touchableView:
UIView *backgroundView = [[UIView alloc] initWithFrame:touchableView.frame];
Set background color of backgroundView to clearColor:
backgroundView.backgroundColor = [UIColor clearColor];
Reset position of touchableView:
CGRect frame = touchableView.frame;
frame.origin.x = 0;
frame.origin.y = 0;
Disable user interaction of touchableView:
touchableView.userInteractionEnabled = NO;
Add touchableView as subview to backgroundView:
[backgroundView addSubview:touchableView];
Add appropriate gesture recognizers to backgroundView.
Add backgroundView to view that you want.
Now you can hide touchableView but you will still receive gesture recognizers.
I don't test this but I think it should work.
sure
when UIImageView is hidden. it does not receive any touch events
set alpha zero for uiimageview
I have a scrollView onto which some imageViews are added.
I want that when scrollView scrolls then also the imageViews should get touch events.
But it is not happening in my case.
Only one of the two things happen at a time.
Either the scrollView scrolls but the imageViews do not get touch events
or the imageViews get touch events and the scrollView does not get scrolled.
Can't both of the things happen simultaneously.
Please suggest.
You can use "Tap Gesture Recognizer" to get the events on your images while it's being added to the scroll view.
EDIT
You can refer to this code:
-(void)yourGestureRecognizer
{
UITapGestureRecognizer *yourTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTap:)];
[yourImageView addGestureRecognizer:yourGestureRecognizer];
[yourGestureRecognizer release];
}
You can handle tap here:
-(void)handleTap:(UIGestureRecognizer *)sender{
UIImageView *imageView = (UIImageView *)sender.view;
switch (imageView.tag)
{
case 1:
m_pYourInstance=[[ViewController alloc]initWithNibName:#"ViewController" bundle:nil];
[self.navigationController pushViewController:XXXXX animated:YES];
break;
}
Hope this helps !!
I know this was posted a while ago, but...
By default, UIImageView objects have their property userInteractionEnabled set to NO. This could be why touches aren't registering. This has tripped me up on more than one occasion and it's one of those little things that only experience will help.
Set it to YES and see if that fixes it.
Use UIGestureRecognizer. Add the recognizer to your image views.
And if you are targeting some old iOS version, have a look at this link.
yes you can do both. you need to customize your uiimageview by sub classing it and have to add Tap Gesture Recognizer like :
in .h file
#interface CustomView : UIImageView
{
// your variables
}
in .m file
//when you create an instants of your image view UITapGestureRecognizer will added in all your instants.
- (id)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame]))
{
// add Gesture for tapping
UITapGestureRecognizer* tap = [[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(tap:)];
tap.numberOfTapsRequired = 1;
[self addGestureRecognizer:tap];
}
return self;
}
then add delegate methods in .m file too
- (void) touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
{
UITouch *touch = [touches anyObject];
}
now in your custom cell, add your custom image view with Tap Gesture Recognizer
hope it will help you...
Use UIButtons with background images set on them. Here's the loop you use to layout UIScrollView subviews:
for (int i = 0; i < [images count]; i++) {
CGRect aFrame = ...;
UIButton *button = [[[UIButton alloc] initWithFrame:aFrame] autorelease];
[button setImage:[images objectAtIndex:i] forState:UIControlStateNormal];
button.tag = i;
[button addTarget:self action:#selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];
[scrollView addSubview:button];
}
And here you process the touch event:
- (void)buttonClicked:(id)sender {
UIButton *button = (UIButton *)sender;
[self performActionWithImageAtIndex:[images objectAtIndex:button.tag]];
}
That will omit the need to create additional gesture recognizers and will lower the amount of boilerplate work.
-- edit
The other reason your UIImageViews are not detecting the touches is because you create them in code and forget to set userInteractionEnabled to YES.
I am just getting stated with iPhone development and can't seem to find the answer I am looking for what I want to do.
It seems like I should be able to programmatically create a UIImageView and then set up an event handler for it's touch functions.
in c# i would have something that looks like
Button b = new Button();
b.Click+= my handler code
right now I have this
CGRect myImageRect = CGRectMake(0.0f, 0.0f, 141.0f, 151.0f);
UIImageView *myImage = [[UIImageView alloc] initWithFrame:myImageRect];
myImage.userInteractionEnabled = YES;
[myImage setImage:[UIImage imageNamed:#"myImage.png"]];
myImage.opaque = YES; // explicitly opaque for performance
[self.view addSubview:myImage];
[myImage release];
What do I need to do to override the touch events?
thanks
While it is not the complete answer to your question, i think the following solution might be better then doing the "invisible-button-workaround"..
simply subclass from UIImageView and override the following method:
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
//your UIImageView has been touched :)
//event -> "A UIEvent object representing the event to which the touches belong."
//touches -> "A set of UITouch instances in the event represented by event that represent the touches in the UITouchPhaseEnded phase."
}
hope this helps...
I have a mainview with some icons like "about" to open an about view.
In the corresponding controller (mainview) I added an event handler for touchesBegan.
Moreover I need to check which view has been "touched", as there are more icons on the view.
My UIImageView instance is "aboutImg" and user interaction needs to be checked (i.e. UI builder -> attributes inspector of view).
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
if ([touch view] == aboutImg){
[self ShowAbout];
}
}
Make sure you check the "User Interactions enabled". This is the easiest solution
Just use a custom UIButton with the image as background image.
I've not found a way to do it directly with UIImageView. But if you subclass it and override the nextResponder method to return a delegate under your control you can do what you're after. Expose the delegate as a property of the subclass and then you can pass in a delegate for the touch events and change it at any time.
I've seen other answers suggest putting an invisible button underneath the UIImageView, setting the image view to userInteractionEnabled: NO, and letting the touches pass through to the button.
You need nothing to do with UIImageView, You can use UIButton with type custom and add touch up inside action to it...set button.imageview setImage:
i-e:
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
btn.frame = CGRectMake(0,0,100,100); //edit for you own frame
UIImage *img = [UIImage imageNamed:#"myimage.png"];
[button setImage:img forState:UIControlStateNormal];
If you want to go with UIImageView, then you need to add gestures to your imageview. else you can subclass your UIImageView and then use touch events.
Easiest way to add an onClick event to an UIImageView or to a UIView is to add a tap gesture recognizer to it.
UIImage *myImage = [UIImage imageNamed:#"myImage.png"];
UIImageView *myImageView = [[UIImageView alloc] initWithImage: myImage];
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action: #selector(onImageViewClicked)];
singleTap.numberOfTapsRequired = 1;
singleTap.numberOfTouchesRequired = 1;
[self.myImageView addGestureRecognizer:singleTap];
[self.myImageView setUserInteractionEnabled:YES];