How would I detect a long tap (tap and hold) within a UIScrollView?
In view's touchesBegan: you can call your "long tap" handle with some delay.
[touchHandler performSelector:#selector(longTap:) withObject:nil afterDelay:1.5];
Then in view's touchesEnded: you can cancel that call if not enough time has passed:
[NSObject cancelPreviousPerformRequestsWithTarget:touchHandler selector:#selector(longTap:) object:nil];
//Add gesture to a method where the view is being created. In this example long tap is added to tile (a subclass of UIView):
// Add long tap for the main tiles
UILongPressGestureRecognizer *longPressGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longTap:)];
[tile addGestureRecognizer:longPressGesture];
[longPressGesture release];
-(void) longTap:(UILongPressGestureRecognizer *)gestureRecognizer{
NSLog(#"gestureRecognizer= %#",gestureRecognizer);
if ([gestureRecognizer state] == UIGestureRecognizerStateBegan) {
NSLog(#"longTap began");
}
}
Related
I'm using UILongPressGestureRecognizer class to handle if one item is being selected.
The logic is as follows: User press during 1 second an item (UIView subclass). Once the gesture is detected, the item is highlighted and moveable.
The user must move this item across the screen without stop touching it.
The problem I'm facing is the gesture recognized shadows touchesBegan/Move/Ended necessary for the item class to arrange the movement.
I tried to remove the gesture recognized once is detected and the item selected. But still sending messages to the handle of gesture instead of call touches methods.
Anyone knows any way to stop "listening" the gesture recognizer without leave the finger of the screen?
Thanks.
Here the code:
-(void)addGestures
{
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc]
initWithTarget:self
action:#selector(handleLongPress:)];
longPress.minimumPressDuration = iItemLongPressTime;
[self addGestureRecognizer:longPress];
[longPress release];
}
- (void)handleLongPress:(UILongPressGestureRecognizer*)sender {
if (sender.state == UIGestureRecognizerStateEnded) {
NSLog(#"Long press Ended");
}
else {
if (self.isSelected) return;
if ([delegate respondsToSelector:#selector(singleTouch:)])
[delegate singleTouch:self];
[self removeGestureRecognizer:[self.gestureRecognizers objectAtIndex:0]];
NSLog(#"Long press detected.");
}
}
As you can see in the else branch the delegate calls enables all procedures to mark this item as selected, and just after remove the recognizers.
What I'm missing?
--EDIT--
Done! This works:
#pragma mark Gesture Functions
-(void)addGestures
{
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc]
initWithTarget:self
action:#selector(handleLongPress:)];
longPress.minimumPressDuration = iItemLongPressTime;
[self addGestureRecognizer:longPress];
[longPress release];
}
- (void)handleLongPress:(UILongPressGestureRecognizer*)sender {
if (sender.state == UIGestureRecognizerStateEnded) {
NSLog(#"Long press Ended");
}
else {
NSLog(#"Long press detected.");
if (self.isSelected) return;
if ([delegate respondsToSelector:#selector(singleTouch:)])
[delegate singleTouch:self];
[sender removeTarget:self action:#selector(handleLongPress:)];
sender.enabled = NO;
[self removeGestureRecognizer:sender];
}
}
Regards!
Does the custom UIView class have its own touch handling code? If not, a simple solution is to set the allowableMovement property of the UILongPressGestureRecognizer to CGFLOAT_MAX, or some big number, and use the gesture update callbacks to drag your custom view around. You can get the displacement using the - (CGPoint)locationInView:(UIView *)view method on the superview, and compare its position to when the recognizer began.
There are two solutions in my mind.
For animating uiview, please wrote a new class which is inherited from the UIView class and implement the touch delegates instead of writing the Gustures to handle animation(if the touch delegates are not triggering in the current class).
2.I have successfully removed the UILongPressGestureRecognizer after triggered it once.
Please refer the below code .ask me if you have any queries
Steps I have Done
I have added a UIView as "myView" to my main-view when main-view loads.
I have given the Tag to the myView (you can give 1,2,3…etc) to differentiate the tapped view from the main-view subviews.
Assigned the UILongPressGestureRecognizer gesture to myView and assigned target as "moveMe" method.
When user Pressed the myView long, the "moveMe" method will trigger.
Then I iterated the mainView Subviews with the condition Tag == 1
I have removed the UILongPressGestureRecognizer from the subview.As we can know that Tagged 1 main-view subView is myView.
So the NSLog(#"gesture removed"); and NSLog(#"moveMe"); will log in console only at one time.
The NSLog(#"touchesBegan"); will trigger first instead of triggering the "moveMe" method.
Then NSLog(#"touchesBegan"); will trigger always after removed the gesture . "moveMe" method will not trigger ever.
Code
- (void)viewDidLoad {
//Adding to UIView to main view when application is loading.
UIView *myView = [[UIView alloc] initWithFrame:CGRectMake(20, 20, 80, 80)];
myView.backgroundColor = [UIColor viewFlipsideBackgroundColor];
myView.tag = 1; //adding a tag to identify it.
//Adding Long Press Gesture to the UIView.
UILongPressGestureRecognizer *myGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(moveMe:)];
[myView addGestureRecognizer:myGesture];
[myGesture release];
myGesture = nil;
[self.view addSubview:myView];
[myView release];
myView = nil;
[super viewDidLoad];
}
//Method to trigger when user pressed long on the added UIView.
-(void)moveMe:(id)sender
{
for (UIView *subViews in [self.view subviews])
{
if (subViews.tag == 1) {
[subViews removeGestureRecognizer:sender];
NSLog(#"gesture removed");
}
}
NSLog(#"moveMe");
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"touchesBegan");
}
or please refer Disable gesture recognizer iOS
I'm building an iPhone app that would let the user rearrange some of the UI elements on the screen.
How can I add a tap gesture recognizer and a long press gesture recognizer to the same UIView? When I lift up the finger from the long press, the tap gesture recognizer fires. How can I temporarily disable the tap gesture recognizer or prevent it from firing when the user is performing a long press?
Thank you!
To allow both gestures to work together, implement the following delegate method:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}
To make it so that the long press has first priority, do:
[tapGesture requireGestureRecognizerToFail:longPress];
To combine successfully both you need:
1º Add to interface gesture delegate at header
#interface ViewController : ViewController <UIGestureRecognizerDelegate>
2º Create gesture events and add to a view into source file:
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(touch:)];
[tap setNumberOfTapsRequired:1]; // Set your own number here
[tap setDelegate:self]; // Add the <UIGestureRecognizerDelegate> protocol
UILongPressGestureRecognizer *longTap = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longTouch:)];
[longTap setNumberOfTapsRequired:0]; // Set your own number here
[longTap setMinimumPressDuration:1.0];
[longTap setDelegate:self]; // Add the <UIGestureRecognizerDelegate> protocol
[tap requireGestureRecognizerToFail:longTap]; // Priority long
[self.view addGestureRecognizer:tap];
[self.view addGestureRecognizer:longTap];
3º Add callbacks in source file:
- (void) touch: (UITapGestureRecognizer *)recognizer
{
CGPoint location = [recognizer locationInView: self.HUDview];
if (recognizer.state == UIGestureRecognizerStateBegan)
{
NSLog(#"touch UIGestureRecognizerStateBegan");
}
if (recognizer.state == UIGestureRecognizerStateEnded)
{
NSLog(#"touch UIGestureRecognizerStateEnded");
//NSLog(#"Position of touch: %.3f, %.3f", location.x, location.y); // Position landscape
}
}
- (void) longTouch: (UILongPressGestureRecognizer *)recognizer
{
if (recognizer.state == UIGestureRecognizerStateBegan)
{
NSLog(#"longTouch UIGestureRecognizerStateBegan");
}
if (recognizer.state == UIGestureRecognizerStateEnded)
{
NSLog(#"longTouch UIGestureRecognizerStateEnded");
}
}
4º Set gesture recognizer available:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
As an alternative approach, don't have two separate recognisers - just use the LongPress recogniser for both events:
Configure as follows:
UILongPressGestureRecognizer* longPress = [ [ UILongPressGestureRecognizer alloc ] initWithTarget:self.nextResponder action:#selector(longPressEvent:)];
categoryPanelDrag.minimumPressDuration = 0.0;
Then handle as follows:
- (BOOL)longPressEvent:(UILongPressGestureRecognizer *)gesture {
// _dragStarted is a class-level BOOL
if(UIGestureRecognizerStateBegan == gesture.state) {
_dragStarted = NO;
}
if(UIGestureRecognizerStateChanged == gesture.state) {
_dragStarted = YES;
// Do dragging stuff here
}
if(UIGestureRecognizerStateEnded == gesture.state) {
if (_dragStarted == NO)
{
// Do tap stuff here
}
else
{
// Do drag ended stuff here
}
}
return YES;
}
I did try moby and journeyman's approach but somehow they didn't fit my project well, so I solved like below,
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch{
NSLog(#"%# %ld",touch.description, touch.phase);
[self performSelector:#selector(checkTouch:) withObject:touch afterDelay:0.5];
return YES;
}
and
- (void)checkTouch:(UITouch *)touch{
NSLog(#"touch phase = %ld",touch.phase);
if (touch.phase == UITouchPhaseStationary) {
//still holding my hand and this means I wanted longPressTouch
}
if (touch.phase == UITouchPhaseEnded){
//I released my finger so it's obviously tap
}
}
It could be simpler solution but of course it depends to project.
You could take care of it in the code, that during the long press, set a flag, and if the tap gets called while the flag is true or whatever then don't execute the tap code and reset the flag. I don't know a better way
I want to be able to dismiss the iPhone keyboard when the user taps anywhere outside of the keyboard. How can I go about doing this? I know I need to dismiss the responder, but need to know how to implement it when a user taps out of the keyboard space.
You'll need to add an UITapGestureRecogniser and assign it to the view, and then call resign first responder on the textfield on it's selector.
The code:
In viewDidLoad
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(dismissKeyboard)];
[self.view addGestureRecognizer:tap];
In dismissKeyboard:
-(void)dismissKeyboard {
[aTextField resignFirstResponder];
}
(Where aTextField is the textfield that is responsible for the keyboard)
OPTION 2
If you can't afford to add a gestureRecognizer then you can try this
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch * touch = [touches anyObject];
if(touch.phase == UITouchPhaseBegan) {
[aTextField resignFirstResponder];
}
}
The simplest solution I have used is this:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self.view endEditing:YES];
}
The endEditing command can be used on any view that contains your textfield as a subview. The other advantage of this method is that you don't need to know which textfield triggered the keyboard. So even if you have a multiple textfields, just add this line to the superview.
Based on the Apple documentation, I think this method exists specifically to solve this problem.
Add a tapGesture Recognizer but make sure cancelsTouchesInView = NO
UITapGestureRecognizer* tapGesture = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(closeTextInput)];
tapGesture.cancelsTouchesInView = NO;
[self.view addGestureRecognizer:tapGesture];
[tapGesture release];
You'll need to add an UITapGestureRecogniser and assign it to the view, and then call resign first responder on the textfield on it's selector.
The code:
In viewDidLoad
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(dismissKeyboard)];
[self.view addGestureRecognizer:tap];
In dismissKeyboard:
-(void)dismissKeyboard {
[self.view endEditing:true];
}
You need to add a transparent UIVIew as a subview below the keyboard and handle touches there, to dismiss the keyboard. Below code is for your reference.
UITapGestureRecognizer* gesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(overlayTouched:)];
gesture.delegate = self;
[(UITapGestureRecognizer *)gesture setNumberOfTouchesRequired:1];
UIView* trans = [[UIView alloc] initWithFrame:[[delegate view] bounds]];
[trans setOpaque:NO];
[trans setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin];
[trans setAlpha:0.3];
[trans setUserInteractionEnabled:YES];
trans.multipleTouchEnabled = YES;
[trans addGestureRecognizer:gesture];
[trans setBackgroundColor:[UIColor blackColor]];
[trans setTag:BLACK_SCREEN_VIEW];
This is a Swift 4 solution:
let tap = UITapGestureRecognizer(target: self, action: #selector(self.dismissKeyboard))
self.view.addGestureRecognizer(tap)
And the dismissKeyboard
#objc func dismissKeyboard() {
self.view.endEditing(true)
}
Other way to do is simple:
Makes your UIView as UIControl in custom class in the interface builder, then you can attach an IBAction method in Touch up inside event of your UIView : UIControl, then you put [yourTextField resignFirstResponder] inside the IBAction method, like this:
- (IBAction) hideKeyboard: (id) sender
{
// If you have more than one textfield to dismiss, of course only can be active 1, but here you can't know who is it, because sender will be the UIView : UIControl
[alias resignFirstResponder];
[password resignFirstResponder];
}
Then, you have other option and it's to put in your textfield the return key of the keyboard as Done (It can be any of those you want, but Done it's good for this, because return means to do an action with the form) in the interface builder, so you can press Done and hide the keyboard, but in that case you have to attach the previous IBAction method to the Did end on exit event.
And in this way the keyboard will hide touching outside or touching Done from the keyboard.
If you want to improve the code, if only will hide the keyboard touching Done from the keyboard the method should be:
// Attach all textFields here on Did end on exit event, will not work if touch outside the keyboard
- (IBAction) hideKeyboard: (id) sender
{
[sender resignFirstResponder];
}
If you are on a UITableViewController or have a UITableView that the user will be interacting with, in your ViewDidLoad you can simply do:
tableView.KeyboardDismissMode = UIScrollViewKeyboardDismissMode.OnDrag;
Note: this is Xamarin.iOS syntax.
If you don't want to add a gesture recognizer you can also use the touchesBegin method
Code in Swift 4
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
view.endEditing(true)
}
You don't even need a gesture recogniser. Views can detect touches without gesture recognisers. All you need is literally this in your view controller....
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self.view endEditing:YES];
}
Hello I have an opengl view and on that I have a tab bar. I'm using a tap recognizer to tap different 3d objects on screen. In the tab bar I have a button but it doesn't work because the tap recognizer catches these taps too. How do I stop this? I've already tried this:
- (BOOL) gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
if ([touch.view isKindOfClass:[UIBarButtonItem class]]) return FALSE;
return TRUE;
}
I think I am somehow comparing wrong classess because when I debug it returns TRUE always.
Or you can just do [singleTap setCancelsTouchesInView:NO]. Example:
UITapGestureRecognizer *singleTap = [
[UITapGestureRecognizer alloc]
initWithTarget: self
action: #selector(yourSelector:)
];
[singleTap setCancelsTouchesInView:NO];
[[self view] addGestureRecognizer: singleTap];
if ([touch.view.superview isKindOfClass:[UIToolbar class]]) return FALSE;
This is how I got it to work. The superview is a UIToolbar, probably UIBarButtonIttem is a view after all.
I need to do something like this:
UISCrollView with UIImageViews as its subviews. When user taps on UIImageViews an action occurs. But when user want to scroll UIScrollView should scroll even if scrolling started at UIImageView (at location where UIImage of UIImageView is displayed).
Basicly I can get one of two scenarios:
I can get that if user taps on UIImageView (which is subview of UIScrollView) an action occur, but when you try to scroll by draging finger from UIImageView the action also occurs (and I want a scroll to occur).
I can make that regardles where user taps the view will scroll but if user taps UIImageView the action will NOT occur.
I can't get you any of my code because I'm testing a lot of aprroches here and there and it's bit messy so it would be no use at all (without a tons of commenting).
Is there a clean and simple solution for doing this?
Ok here is some code:
-(UIView*) hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if(isDragging == NO)
{
return [super hitTest:point withEvent:event];
}
NSLog(#"dragging ><><><><><>>><><><><>");
return nil;
}
Now if I return nil then I can scroll but I can't get to tap my UIImageView for an action. If I return [super hitTest:point withEvent:event] I can't scroll over my UIIMageView.
isDragging is test code to determine if I'm trying to scroll or just tap. But hit test occurs before I can set isDragging property accordingly to event that is happening.
Here is my init
-(id) initWithCoder:(NSCoder *)aDecoder
{
if(self = [super initWithCoder:aDecoder])
{
[self setUserInteractionEnabled:YES];
UISwipeGestureRecognizer *swipeRecLeft = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(swipe)];
swipeRecLeft.direction = UISwipeGestureRecognizerDirectionDown;
UISwipeGestureRecognizer *swipeRecRight = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(swipe)];
swipeRecRight.direction = UISwipeGestureRecognizerDirectionUp;
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapSingle)];
[self addGestureRecognizer:swipeRecRight];
[self addGestureRecognizer:swipeRecLeft];
[self addGestureRecognizer:singleTap];
[swipeRecLeft release];
[swipeRecRight release];
[singleTap release];
isDragging = NO;
}
else {
self = nil;
}
return self;
}
Here are the rest of actions
-(void) tapSingle
{
[self.delegate hitOccur];
}
-(void) swipe
{
isDragging = YES;
}
And on top of it in my UIScrollView I've setted delegate and when scrolling ends I set isDragging property manualy to NO on each subview of my UIScrollView.
It's working... but it's not perfect. To actually scroll content I must swipe TWICE in UIImageView (first one is to set isDragging to YES and then we can scroll...). How to this right and proper?
LATEST UPDATE:
Ok I've managed to solve this problem. However I'm dead sure my way isn't clean or good one (but regardles it works).
In my UIScrollView subclass I've overided hitTest method with this:
-(UIView*) hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if(!self.dragging)
{
if([[super hitTest:point withEvent:event] class] == [ResponsiveBookView class])
{
container = [super hitTest:point withEvent:event];
}
}
return self;
}
Where the container is id container and it holds my UIView subclass. So I can recognize if the touch was on my image or on scroll view itself. Now I need to detec if it is scrolling or just touching and I do this here:
-(void) touchesEnded: (NSSet *) touches withEvent: (UIEvent *) event
{
if (!self.dragging) {
NSLog(#"touch touch touch");
[container tapSingle];
[self.nextResponder touchesEnded: touches withEvent:event];
}
[super touchesEnded: touches withEvent: event];
}
As you see if self.dragging (scrolling) the default behaviour will apply. If !self.dragging I will manually call tapSingle on my container (which will then make an action "occur"). It works!
Yeah—use gesture recognizers. If you add a UITapGestureRecognizer to each UIImageView (using the UIVIew -addGestureRecognizer: method), you should get both recognition of the taps and the default scrolling behavior.