I want to change background color of UIView tap, I know how to catch touch events and gestures on UIView in iOS.
I can change color when touchesBegan fires and then change it to original color when touchesEnded or touchesCancelled fires.
This works fine, until user tap on UIView really fast, this can be determined by UITapGestureRecognizer and change background color, but I don't know how to change color after that!
I don't want to use some kind of "Timer" or similar approach.
any suggestion?
You can detect that the tap ended by checking its state:
Objective-C
if (recognizer.state == UIGestureRecognizerStateEnded) {
// change the color here
}
Swift
if recognizer.state == .Ended {
// change the color here
}
From your question it seems that you have successfully done it with following delegates of UIView.
touchesBegan:
touchesEnded:
touchesCancelled:
The second option is to use UITapGestureRecognizer which was already mentioned by you. Please try using following code in your gesture recognizer method.
Add gesture to your view:
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(viewTap:)];
[yourView addGestureRecognizer:tapGesture ];
Your gesture recognizer method
-(void) viewTap:(UITapGestureRecognizer *) gestureRecognizer
{
if(gestureRecognizer.state == UIGestureRecognizerStateEnded)
{
//All fingers are lifted.
}
}
Related
I have the following code in a viewDidLoad on a UIViewController:
UIScreenEdgePanGestureRecognizer *edgeRecognizer = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:#selector(handleRightEdgeSwipe:)];
edgeRecognizer.edges = UIRectEdgeRight;
[self.view addGestureRecognizer:edgeRecognizer];
and the purposes is to trigger a view to slide in when a right edge gesture is detected.
-(void)handleRightEdgeSwipe:(UIGestureRecognizer*)sender
{
NSLog(#"Showing Side Bar");
[self presentPanelViewController:_lightPanelViewController withDirection:MCPanelAnimationDirectionRight];
}
But I am seeing that the "handleRightEdgeSwipe" function is triggered multiple times - sometimes 5 times which makes the side bar view that should smoothly animate slide in to flash multiple times.
(NOTE: I tried triggering the view to appear from a UIButton and it works fine).
Why is the right edge gesture triggered multiple times and how can I fix it?
As noted, the UIScreenEdgePanGestureRecognizer invokes your action multiple times as the state of the GestureRecognizer changes. See the documentation for the state property of the UIGestureRecognizer class. So, in your case I believe the answer you're looking for is to check if the state is "ended". Thus:
-(void)handleRightEdgeSwipe:(UIGestureRecognizer*)sender
{
if (sender.state == UIGestureRecognizerStateEnded)
{
NSLog(#"Showing Side Bar");
[self presentPanelViewController:_lightPanelViewController withDirection:MCPanelAnimationDirectionRight];
}
}
This gesture is not a single-shot event but continuous.
handleRightEdgeSwipe: is called once whenever sender.state changes or the touch moved around. You have to move the UIButton depending on the gesture's state and locationInView:.
Is there any way to get UITapGestureRecognizer to run on touch began?
I can't use touchesBegan because I am using a UITableView and the super view steals the event essentially.
I just want to detect when the screen is first touched. Why is this so difficult? Maybe I need a different solution than using tapgesturerecognizer?
You can use state property of UIGestureRecognizer to identify various states of any gesture -
#property(nonatomic,readonly) UIGestureRecognizerState state; // the current state of the gesture recognizer
So when the gesture begin, use something like this in your registered handler method -
if (gestureRecognizer.state == UIGestureRecognizerStateBegan) {
// Do your stuff
}
You can add a tap gesture recognizer to the tableView in viewDidLoad like this:
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapGestureRecognized:)];
[self.tableView addGestureRecognizer:tapGestureRecognizer];
Then implement this method:
- (void)tapGestureRecognized:(UITapGestureRecognizer *)tapGestureRecognizer {
NSLog(#"tap gesture recognized");
}
Just tested this out, and works fine. For every tap i get the log message on my console. Note that this prevents the tableview from receiving the taps, other gestures will just be handled by the table view as usual.
You need to set delaysContentTouches = NO
I have a UILongPressGestureREcognizer with a minimum press length of 0 seconds. On UIGestureRecognizerStateStart I alter the UIView. On UIGestureRecognizerStateEnded, I run the code for what ever the press is suppose to do.
-(IBAction)longPressDetected:(UILongPressGestureRecognizer *)recognizer {
static NSTimer *timer = nil;
if (recognizer.state == UIGestureRecognizerStateBegan) {
// alter look of UIView
}
else if (recognizer.state == UIGestureRecognizerStateEnded) {
// do something
// change look back to original state
}
}
I am running into an issue where if the touch starts on the UIView and if the touch is dragged outside of the UIView and is lifted, the UIGestureRecognizerStateEnded still fires.
I need to be able to handle a touch inside the UIView, the user drag outside of the UIView and to cancel it.
I know a UIButton can do it, but it does not fulfill all the needs I have.
How can I cancel a touch if the touch is dragged outside of the UIView?
Add checking of touch location. This method can be heplfull.
- (CGPoint)locationInView:(UIView *)view
I want to achieve the following.
Scenario: The iOS keyboard is on-screen while the user is typing into a particular text field. The user can tap anywhere outside of the keyboard and text field to dismiss the keyboard (without activating any buttons which are visible). Also, the user can drag outside of the keyboard and observe the normal drag behavior on some arrangement of scrollable views.
Conceptually, I’m placing a “cover” UIView over most of the screen which behaves such that:
If the user taps on the cover, then I capture that tap (so that I can, e.g., dismiss the keyboard). This is easy to achieve by intercepting touch events in a UIView subclass or using a tap gesture recognizer.
If the user drags on the cover, then the cover ignores or forwards these touches; these are received by the layers underneath just as they would have been without a cover.
So: the user should be able to scroll content underneath the cover, but not tap content underneath the cover. A tap “outside” of the keyboard and text field should dismiss the keyboard (and cover), but should not activate anything.
How can I achieve this?
Add the tap gesture the usual way:
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapAction:)];
[self.view addGestureRecognizer:tapGesture];
But what you may be looking for is this :
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
Documentation says : This method is called when recognition of a gesture by either gestureRecognizer or otherGestureRecognizer would block the other gesture recognizer from recognizing its gesture. (https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIGestureRecognizerDelegate_Protocol/index.html#//apple_ref/occ/intf/UIGestureRecognizerDelegate)
This way, you may be sure it's totally transparent, and also that nothing will prevent your recognizer from being called.
A custom view which forwards all touches it receives:
class CustomView: UIView {
override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
var hitView = super.hitTest(point, withEvent: event)
if hitView == self {
return nil
}
return hitView
}
}
From there you can go different ways to just make use of tap gestures. Either observe the UIEvent for its touches, or use a gesture recognizer.
1: Add a tap gesture recognizer to the view:
//Adding tap gesture
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapGesture:)];
tapGesture.numberOfTapsRequired = 1;
[self.view addGestureRecognizer:tapGesture];
2: In handleTapGesture you resignFirstResponder of the keyboard
- (void)handleTapGesture:(UITapGestureRecognizer *)sender {
if (sender.state == UIGestureRecognizerStateRecognized) {
//Resign first responder for keyboard here
}
}
Elaborated a bit on the answer above. UIGestureRecognizerStateRecognized makes sure it's single tab events that gets recognized.
Is this the functionality you where after?
currently I want to let UITextView to have a double tap gesture. It seems UITableView has its own double tap gesture, when we double tapped, some text will be selected. So I want to remove this default double tap gesture to my own gesture recognizer. I tried many methods, and all failed. It seems there's no way to remove the default recognizer of UITextView. I also want to add a transparent view on this UITextView to do double tap event, but this subview will block other gestures on UITextView. Is there some method to add double tap gesture recognizer to UITextView? I really hope there will be a work around.
I am still expecting a work around of iOS5 :)
There are a number of other gesture recognizers attached to a text view. Since you don't seem to need them. You can remove them.
myTextView.gestureRecognizers = nil;
before adding your double tap recognizer. It works.
then you can add
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(mySelector)];
tapRecognizer.numberOfTapsRequired = 2;
tapRecognizer.numberOfTouchesRequired = 1;
[myTextView addGestureRecognizer:tapRecognizer];
I have the solution on iOS6, we can use UIGestureRecognizerDelegate, and override gestureRecognizerShouldBegin: and gestureRecognizer:shouldReceiveTouch:. In this two method, we can check if the gesture is doubleTapGestureForZooming, if not return NO, or return YES. This works perfect in iOS6, but in iOS5 these two delegate method hasn't been called, so iOS5 may need another workaround.
Finally, I get the workaround, we can override the addGestureRecognizer method of UITextView to remove default gesture, wish this will help somebody else.
PS: we really can't remove system gestures of UITextView, we even can't change their property. It seems when event happens, all gestures of UItextview will be added again.
I know this question is old, but to keep it current for future searchers, I figured I would add another solution that has worked for me from iOS 7 through 10. It basically brings together the solutions discussed here and here but tweaks them to get the UITextView to recognize the custom double tap.
It does this by subclassing the UITextView and overriding the addGestureRecognizer: method in order to inject our custom callback into the double-tap gesture and configure the single-tap gesture to respect the new double tap hook.
I do this in addGestureRecognizer: because a UITextView constantly deletes and adds gestures depending on its current state and so you constantly have to reset it back up.
This code should be enough to get someone started:
#interface MyCustomTextView ()
/**
* we want to keep track of the current single-tap gesture so we can make sure
* it waits for a double-tap gesture to fail before firing
*/
#property (weak, nonatomic) UITapGestureRecognizer *singleTap;
/**
* we want to keep track of the current double-tap gesture so we can tell a single
* tap gesture to ignore this double-tap when the single tap gesture changes
*/
#property (weak, nonatomic) UITapGestureRecognizer *doubleTap;
#end
#implementation MyCustomTextView
/**
* this will fire when the text view is double-tapped
*
* #param tgr
*/
- (void)_handleTwoTaps:(UITapGestureRecognizer *)tgr
{
// ADD CODE HERE
}
/**
* the reason why I've overridden this methods is these gestures change quite a bit
* depending on the state of the UITextView, (eg, when in focus and out of focus)
* and so this provides a reliable way to make sure any new gestures get updated
* with custom overrides.
*
* #param gestureRecognizer
*/
- (void)addGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
{
[super addGestureRecognizer:gestureRecognizer];
if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
UITapGestureRecognizer *tgr = (UITapGestureRecognizer *)gestureRecognizer;
if ([tgr numberOfTapsRequired] == 1 && [tgr numberOfTouchesRequired] == 1) {
self.singleTap = tgr;
if (self.doubleTap) {
[tgr requireGestureRecognizerToFail:self.doubleTap];
}
} else if ([tgr numberOfTapsRequired] == 2 && [tgr numberOfTouchesRequired] == 1) {
[tgr addTarget:self action:#selector(_handleTwoTaps:)];
self.doubleTap = tgr;
if (self.singleTap) {
[self.singleTap requireGestureRecognizerToFail:tgr];
}
}
}
}
// NOTE: I'm not sure if this is needed but it's been there for years
// and so I thought I would include it just in case
- (void)removeGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
{
if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
UITapGestureRecognizer *tgr = (UITapGestureRecognizer *)gestureRecognizer;
if ([tgr numberOfTapsRequired] == 2 && [tgr numberOfTouchesRequired] == 1) {
[tgr removeTarget:self action:#selector(_handleTwoTaps:)];
}
}
[super removeGestureRecognizer:gestureRecognizer];
}
#end