iOS gestureRecognizer not handled subviews - ios

i have the following problem: in my iphone app i have a UIView that holds 0 to several subviews. Those subviews may overflow the parent and therefore be hidden and all of them are UIButtons. I added a UISwipeGestureRecognizer to the UIView to move the buttons around which works great. However it only works when the gesture is done on background, the UIButtons interfere with the gesture recognizer.
How i can i pass the gesture through? Btw. i still need the tap of the Buttons.
Thanks!
EDIT:
I tried to add the gesture recognizer to the UIButtons too, but it is not triggered... Although performing the swipe gesture prevents the UIButton from going to highlighted state it doesn't trigger the gesture. I added setDelaysTouchesBegan:YES as suggested in UIButton and Swipe Gesture. That's how i do it right now:
UIButton *breadcrumb = [UIButton buttonWithType:UIButtonTypeCustom];
[breadcrumb setImage:[UIImage imageNamed:#"Next"] forState:UIControlStateNormal];
[breadcrumb setTitle:title forState:UIControlStateNormal];
[breadcrumb.titleLabel setFont:[UIFont systemFontOfSize:12.0f]];
[breadcrumb sizeToFit];
[breadcrumb setTag:level];
UISwipeGestureRecognizer *gr = [[UISwipeGestureRecognizer alloc] init];
[gr setDelegate:self];
[gr setDirection:UISwipeGestureRecognizerDirectionRight];
[gr setDelaysTouchesBegan:YES];
[self.tableView addGestureRecognizer:gr];
[breadcrumb addGestureRecognizer:gr];
EDIT 2:
I have now subclassed UIButton and initilizing it now like so: [BreadcrumbButton buttonWithType:UIButtonTypeCustom]. In the initializer i added the button itsself as a listener to all touch events [self addTarget:self action:#selector(eventReceiver:) forControlEvents:UIControlEventAllTouchEvents]; to inspect whats going on.
- (void)eventReceiver:(UIButton *)btn {
NSLog(#"Reveived event: %# ---------------", btn);
for(UIGestureRecognizer *gr in ev.gestureRecognizers) {
NSLog(#"Gesture: %#", gr);
}
}
What i see is that a) the button has just one gesture recognizer added and b) that this UISwipeGestureRecognizer jumps to state Possible during swipe but does not forward to its delegate methods.

You would have to subclass the UIButton and over ride the tap delegate callback and forward this call to whatever is handling a UISwipeGestureRecognizer. Unless you add the gesture recognizer on the UIButton, it will always call it's touch handler before the view behind it. You can also explicitly tell the button to not handle it's touch events thus passing the touch event down the chain (via userInteractionEnabled), but as you've already stated you do not want this. The best way to go about this would be by creating a subclass of UIButton and handling the touch events there and/or forwarding the events using delegation. Pressing the button is a touch event, so you may just want to add a tap gesture recognizer to the button and call the IBAction from that and then have the swipegesturerecognizer forward a delegate call.

Related

UILongPressGestureRecognizer on UIButton not working

I have a longpress gesture recognizer that I create in ViewDidLoad then attach to a button like this, the button is created in the storyboard and linked to my class.
UILongPressGestureRecognizer *hold = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(secretChange:)];
hold.minimumPressDuration = 5.0;
hold.delegate = self;
[_button addGestureRecognizer:hold];
The class conforms to the GestureRecognizer protocol and I have my selector here
- (void)secretChange:(UILongPressGestureRecognizer *)sender {
// Some stuff
NSLog(#"Secret");
}
The selector is not being called and I cannot figure out why, this seems to be the code everyone gives out on the internet, I have tried removing the minimum duration to make sure I didn't accidentally set it ridiculously long
UPDATE: I am actually adding this gesture recognizer to multiple buttons like this
[_button1 addGestureRecognizer:hold];
[_button2 addGestureRecognizer:hold];
[_button3 addGestureRecognizer:hold];
What is happening is the gesture recognizer is only being applied to the last button I add it to. How do I get the gesture recognizer added to ALL the buttons? Do I need to make a new one for every button?
You should have three instance of UILongPressGestureRecognizer.
Before add a gesture recognizer to a new view, the addGestureRecognizer method will remove the gesture recognizer from the view it has been attached to.

addTarget:action:forControlEvents: ignored when interacting with separate UITapGestureRecognizer

I'm somewhat new to iOS programming
I have some code (abridged) that looks like the following
UIView *someSubView = [[UIView alloc] initWithFrame:...];
[self addSubView:someSubView];
[someSubView addTarget:self action:#selector(_handleTapOnView:) forControlEvents:UIControlEventTouchUpInside];
_tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(_handleTap:)];
_tapGestureRecognizer.delegate = self;
[self.view addGestureRecognizer:_tapGestureRecognizer];
Unfortunately the gesture recognizer triggers and my views addTarget call does not. I've tried commenting out the gesture recognizer code and it works, so I know its not the call to addTarget on the subview.
I solved this initially by using the gestureRecognizer:shouldReceiveTouch: and doing a hit test for the sub view, but I feel like I'm missing some fundamental understanding here that wouldn't require me adding a manual hit test.
Its important to note that I don't want the code in the _handleTap in the _tapGestureRecognizer to execute when I have tapped on my subview.
Any guidance here? Thanks!
Try using:
_tapGestureRecognizer.cancelsTouchesInView = NO;
otherwise the gesture recogniser will intercept the touches and will not forward them further (in other words, the gesture recogniser gets the touch, handles it, and since it cancel it, no other object gets the touch). By not cancelling, the touch is forwarded for any other object (recognisers or views) to handle it.

Adding and removing dynamically generated buttons in xcode

This is a strange one.
I'm quite new to Xcode. I've been trying to make a simple app that adds new buttons when you single click on the button view and removes them when you double click.
Adding buttons is OK, but removing them is unreliable. I think it has something to do with the way I've written the code because it only seems to remove the most recently added button on double click and not the actual button I've clicked on.
My abridged .m Code is below:
- (void)viewDidLoad
{
- (void)handleSingleTap:(UITapGestureRecognizer *)tapper {
//adds the buttons and gives them a unique tag
ButtonCount = ButtonCount+1;
btn = [UIButton buttonWithType:UIButtonTypeCustom];
btn.frame = CGRectMake(0, 0, 150, 150);
btn.userInteractionEnabled = YES;
btn.tag=PuckCount;
//attaches double tap recognizer to button
UITapGestureRecognizer *doubleTapGestureRecognizer = [[UITapGestureRecognizer alloc]
initWithTarget:self
action:#selector(handleDoubleTap:)];
[doubleTapGestureRecognizer setNumberOfTapsRequired:2];
[btn addGestureRecognizer:doubleTapGestureRecognizer];
//Add Tap Recognizer to pucks to create new buttons
UITapGestureRecognizer *singleTapGestureRecognizer = [[UITapGestureRecognizer alloc]
initWithTarget:self
action:#selector(handleSingleTap:)];
[singleTapGestureRecognizer setNumberOfTapsRequired:1];
[singleTapGestureRecognizer requireGestureRecognizerToFail:doubleTapGestureRecognizer];
[btn addGestureRecognizer:singleTapGestureRecognizer];
UIImage *buttonImage = [UIImage imageNamed:#"puck2.png"];
[btn setImage:buttonImage forState:UIControlStateNormal];
[self.view addSubview:btn];
}
//handles what happens on a double tap - THIS IS WHERE I THINK THE PROBLEM IS.
- (void)handleDoubleTap:(UIGestureRecognizer *)doubletap{
PuckSelected = self.view.tag;
[[self.btn viewWithTag:PuckSelected] removeFromSuperview];
}
#end
If you are trying to remove the button that is being double tapped, then try this.
- (void) handleDoubleTap:(UIGestureRecognizer *) doubletap {
[doubletap.view removeFromSuperview];
}
The UIGestureRecognizer has the view attached to it. There is no need to retrieve it again. Plus your problem was that you were trying to retrive the view from self instead of doubletap
Your handleDoubleTap method doesn't make sense.
Do something like this ::
- (void)handleDoubleTap:(UIGestureRecognizer *)doubletap{
PuckSelected = doubletap.view.tag;
[[self.view viewWithTag:PuckSelected] removeFromSuperview];
}
And it will work fine
- (void)handleDoubleTap:(UIGestureRecognizer *)doubletap{
PuckSelected = self.view.tag;
[[self.btn viewWithTag:PuckSelected] removeFromSuperview];
}
According to the code above, the button that gets removed it the button that has the same tag as self.view. But you don't show how self.views tag gets set, so it's impossible for us to know why or how your code selects the button that will be removed.
Regardless, you're probably heading down the wrong road here. It looks like you might have buttons nested inside a button? Which is odd. Also, you're using gesture recognizers with UIButtons, which don't need it. They already respond to things like taps and double-taps via the target-action mechanism.
Generally speaking, controls (that is, objects that derive from UIControl) already handle touches, taps, and other simple interactions. You'd really only use gesture recognizers on UIViews that need to track swipes or pinches or the like.
There seem to be 2 problems with your code.
1. When you do a single tap you are adding a button at (0,0,150,150) so any new button that gets added will be on top of the previous button. Maybe I am seeing this because of your abridged code, but you may want to do something about that.
2. In the UITapgesturerecognizer it will give you the view(id) that was tapped. You should use that in your selector. Like:
doubletap.view.tag.

Homemade tap gesture recognizer for iOS

I'd like to code my own tap gesture recognizer, to detect the number of taps and number of touches (I don't want to use the iOS tap gesture recognizer because I want to extend it later in various other manners) ;
I tried the following : use the first motionBegin number of touches as the numberOfTouches of the tap, increment the numberOfTaps, and start the tap detection timer to detect the tap gesture if no new taps has been seen in a while
The problem is that one quickly realises that when doing a double-touch tap gesture, iOS either correctly detects one motionBegin with a double touch, or two quick one touch events. I guess a correct implementation should try to detect those quick one touch events that happen closely, but I'm wondering if there is a better way to implement the gesture recognizer.
Someone knows how the iOS tap gesture is implemented?
1. Add UIGestureRecognizerDelegate in your .h file. like
#interface finalScreenViewController : UIViewController <UIGestureRecognizerDelegate>
{
// do your stuff
}
2. Create a view in your viewDidLoad method (or any other method) you wanna to add the gesture in your .m file
ex
UIView * myView=[[UIView alloc]init];
myView.frame=CGRectMake(0,0.self.view.frame.size.width,self.view.frame.size.height);
[self.view addSubView: myView];
UITapGestureRecognizer *letterTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapMethod:)];
letterTapRecognizer.numberOfTapsRequired = 1;
[myView addGestureRecognizer:letterTapRecognizer];
3. you can get view by
- (void) tapMethod:(UITapGestureRecognizer*)sender {
UIView *view = sender.view;
NSLog(#"%d", view.tag);//By tag, you can find out where you had tapped.
}

How can I detect the touch event of an UIImageView?

I have placed an image (UIImageView) on the navigation bar. Now I want to detect the touch event and want to handle the event. How can I do that?
In practical terms, don't do that.
Instead add a button with Custom style (no button graphics unless you specify images) over the UIImageView. Then attach whatever methods you want called to that.
You can use that technique for many cases where you really want some area of the screen to act as a button instead of messing with the Touch stuff.
A UIImageView is derived from a UIView which is derived from UIResponder so it's ready to handle touch events. You'll want to provide the touchesBegan, touchesMoved, and touchesEnded methods and they'll get called if the user taps the image. If all you want is a tap event, it's easier to just use a custom button with the image set as the button image. But if you want finer-grain control over taps, moves, etc. this is the way to go.
You'll also want to look at a few more things:
Override canBecomeFirstResponder and return YES to indicate that the view can become the focus of touch events (the default is NO).
Set the userInteractionEnabled property to YES. The default for UIViews is YES, but for UIImageViews is NO so you have to explicitly turn it on.
If you want to respond to multi-touch events (i.e. pinch, zoom, etc) you'll want to set multipleTouchEnabled to YES.
To add a touch event to a UIImageView, use the following in your .m file:
UITapGestureRecognizer *newTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(myTapMethod)];
[myImageView setUserInteractionEnabled:YES];
[myImageView addGestureRecognizer:newTap];
-(void)myTapMethod{
// Treat image tap
}
You can also add a UIGestureRecognizer. It does not require you to add an additional element in your view hierarchy, but still provides you will all the nicely written code for handling touch events with a fairly simple interface:
UISwipeGestureRecognizer *swipeRight = [[UISwipeGestureRecognizer alloc]
initWithTarget:self action:#selector(handleSwipe:)];
swipeRight.direction = UISwipeGestureRecognizerDirectionRight;
[imgView_ addGestureRecognizer:swipeRight];
[swipeRight release];
UISwipeGestureRecognizer *swipeLeft = [[UISwipeGestureRecognizer alloc]
initWithTarget:self action:#selector(handleSwipe:)];
swipeLeft.direction = UISwipeGestureRecognizerDirectionLeft;
[imgView_ addGestureRecognizer:swipeLeft];
[swipeLeft release];
I've been on different threads on the past few hours trying to find a solution for my problem, to no avail. I see that many developers share this problem, and I think people here know about this. I have multiple images inside a UIScrollView, trying to get tap events on them.
I am not getting any events from an UIImangeView, but I do get an event from a similar UILable with very similar parameters I am setting to it. Under iOS 5.1.
I have already done the following:
set setUserInteractionEnabled to YES for both `UIImageView and parent
view .
set setMultipleTouchEnabled to YES for UIImageView.
Tried subclassing UIImageView, didn't help any.
Attaching some code below, in this code I initialize both a UIImageView and UILabel, the label works fine in terms of firing events. I tried keeping out irrelevant code.
UIImageView *single_view = [[UIImageView alloc]initWithFrame:CGRectMake(200, 200, 100, 100)];
single_view.image = img;
single_view.layer.zPosition = 4;
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(singleTapGestureCaptured:)];
[single_view addGestureRecognizer:singleTap];
[single_view setMultipleTouchEnabled:YES];
[single_view setUserInteractionEnabled:YES];
[self.myScrollView addSubview:single_view];
self.myScrollView.userInteractionEnabled = YES;
UILabel *testLabel = [[UILabel alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
testLabel.backgroundColor = [UIColor redColor];
[self.myScrollView addSubview:testLabel];
[testLabel addGestureRecognizer:singleTap];
[testLabel setMultipleTouchEnabled:YES];
[testLabel setUserInteractionEnabled:YES];
testLabel.layer.zPosition = 4;
And the method which handles the event:
- (void)singleTapGestureCaptured:(UITapGestureRecognizer *)gesture
{
UIView *tappedView = [gesture.view hitTest:[gesture locationInView:gesture.view] withEvent:nil];
NSLog(#"Touch event on view: %#", [tappedView class]);
}
As said, the label tap is received.
Instead of making a touchable UIImageView then placing it on the navbar, you should just create a UIBarButtonItem, which you make out of a UIImageView.
First make the image view:
UIImageView *yourImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"nameOfYourImage.png"]];
Then make the barbutton item out of your image view:
UIBarButtonItem *yourBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:yourImageView];
Then add the bar button item to your navigation bar:
self.navigationItem.rightBarButtonItem = yourBarButtonItem;
Remember that this code goes into the view controller which is inside a navigation controller viewcontroller array. So basically, this "touchable image-looking bar button item" will only appear in the navigation bar when this view controller when it's being shown. When you push another view controller, this navigation bar button item will disappear.
You might want to override the touchesBegan:withEvent: method of the UIView (or subclass) that contains your UIImageView subview.
Within this method, test if any of the UITouch touches fall inside the bounds of the UIImageView instance (let's say it is called imageView).
That is, does the CGPoint element [touch locationInView] intersect with with the CGRect element [imageView bounds]? Look into the function CGRectContainsPoint to run this test.
First, you should place an UIButton and then either you can add a background image for this button, or you need to place an UIImageView over the button.
Or:
You can add the tap gesture to a UIImageView so that get the click action when tap on the UIImageView.
For those of you looking for a Swift 4 solution to this answer, you can use the following to detect a touch event on a UIImageView.
let gestureRecognizer: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(imageViewTapped))
imageView.addGestureRecognizer(gestureRecognizer)
imageView.isUserInteractionEnabled = true
You will then need to define your selector as follows:
#objc func imageViewTapped() {
// Image has been tapped
}
Add gesture on that view. Add an image into that view, and then it would be detecting a gesture on the image too. You could try with the delegate method of the touch event. Then in that case it also might be detecting.

Resources