I have a view to which I've added both a pan and long press UIGestureRecognizer. The pan is used to move the view around. What I'd like to do is notice also that the touch has stopped moving (while remaining active) and trigger the long press.
What I find is that the long press is never triggered after the pan has begun. I've tried setting a delegate and implementing:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
NSLog(#"simultaneous %#", gestureRecognizer.class);
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
NSLog(#"require fail %#", gestureRecognizer.class);
return [gestureRecognizer isKindOfClass:[UIPanGestureRecognizer self]];
// also tried return YES;
// also tried return [gestureRecognizer isKindOfClass:[UILongPressGestureRecognizer self]];
}
I've tried fooling with the pan gr's allowableMovement, also to no avail. I'm just about to give up and use a timer in the pan gr that get's invalidated and then reset on moves, but I was hoping that the SDK would do the state machine stuff for me.
In case anyone else needs it, here's the code that works for me. The goal is to have a view that is sensitive to both long press and pan, including a long press that isn't preceded by a pan and vice versa.
// setup
#property (strong,nonatomic) NSTimer *timer; // triggers the long press during pan
#property (strong,nonatomic) UIView *longPressView; // need this to track long press state
// view is the view we're interested in panning and long pressing
UIPanGestureRecognizer *panGR = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panGR:)];
[view addGestureRecognizer:panGR];
// this starts a long press when no pan has occurred
UILongPressGestureRecognizer *longGR = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPressGR:)];
[view addGestureRecognizer:longGR];
When a pan begins or changes, start a timer. If the timer expires before the pan ends (touch releases), then we have a long press.
- (void)panGR:(UIPanGestureRecognizer *)gr {
if (gr.state == UIGestureRecognizerStateBegan) {
[self startTimer:gr.view];
} else if (gr.state == UIGestureRecognizerStateChanged) {
[self startTimer:gr.view];
// do whatever you want to do with pan state in this method
// in my case, I'm translating the view here
} else if (gr.state == UIGestureRecognizerStateEnded) {
if (self.longPressView) {
[self longPressEnded];
} else {
[self.timer invalidate];
}
}
}
We give the timer user info of the view. You might need to store other parts of the gesture state, like location, etc. Do it the same way, with the user info dictionary.
- (void)startTimer:(UIView *)view {
if (self.longPressView) return;
[self.timer invalidate];
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.8 target:self
selector:#selector(longPressTimer:)
userInfo:#{ #"view": view} repeats:NO];
}
-(void)longPressTimer:(NSTimer *)timer {
self.longPressView = timer.userInfo[#"view"];
[self longPressBegan];
}
Since the timer method won't have an associated gr, factor out all of the logic that we'd normally put in the gr handler so it can be called by both the timer handler and the gr handler.
- (void)longPressGR:(UILongPressGestureRecognizer *)gr {
if (gr.state == UIGestureRecognizerStateBegan) {
self.longPressView = gr.view;
[self longPressBegan];
} else if (gr.state == UIGestureRecognizerStateEnded) {
[self longPressEnded];
}
}
- (void)longPressBegan {
NSLog(#"long press began");
}
- (void)longPressEnded {
self.longPressView = nil;
NSLog(#"long press ended");
}
First of we have to register both long press and pag gesture events like,
let longPress = UILongPressGestureRecognizer()
longPress.delegate = self
longPress.addTarget(self, action: #selector(sendMessageLongPress(_:)))
let panGesture = UIPanGestureRecognizer()
panGesture.delegate = self
panGesture.addTarget(self, action: #selector(sendMessagePanGesture(_:)))
self.imgRecord.addGestureRecognizer(longPress)
self.imgRecord.addGestureRecognizer(panGesture)
then we have to setup to capture multiple touch events via delegate method. For this we have to extend UIGestureRecognizerDelegate and then use,
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
then we can implement events as we needed.
(in my case I wanted to cancel recording audio if user swipes, then I had to consider about touch started point and touch ended point as needed.)
Related
I have a custom vertical range slider and handling the movements of heads using touchBegan, touchMoved and touchEnded. When I try to slide the head, it slides a bit and after that touch is cancelled and interactive dismiss transition starts on iOS 13. I want to prevent the touch to transfer while sliding, to the superview. How we can achieve that.
Thanks in advance.
Try using another gesture recognizer to the view with another selector
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(tappedOnBubble)];
[self.bubbleView addGestureRecognizer:tap];
UITapGestureRecognizer *tap2 = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(tappedOnMainView)];
[self.view addGestureRecognizer:tap2];
-(void)tappedOnMainView
{
NSLog(#"touched on main View");
[self.vwToShow setHidden:NO];
}
-(void)tappedOnView
{
NSLog(#"tapped on slider");
[self.vwToShow setHidden:YES];
}
UIView inherits from UIResponder, and basic touch events are detected by the view that triggers the touches began events. The subviews that you added in the main view also responds to the touches began method.Thats very basic. You have also added a selector method with tap gesture recognizer.
If you still want to use touchBegan , I think you should do something like this:
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent *)event
{
if(theTouchLocation is inside your bubble)
{
do something with the touch
}
else
{
//send the touch to the next view in the hierarchy ( your large view )
[super touchesBegan:touches withEvent:event];
[[self nextResponder] touchesBegan:touches withEvent:event];
}
}
I'm working on a Tap game, tap the screen to start the game.
I have this code and I need to make this animation to start after tap the screen. Now Is running when the game are loading (without tap the screen). What I need to change it to start this animation after the user tab (touch) on the screen? Thanks for your help.
[super viewDidLoad];
// Set Delay on Animations when Game Start - Animations Area ***** PERFORMS ****** 0.1
[self performSelector:#selector(Animation) withObject:nil afterDelay:0.1];
Add a tap gesture recognizer to your view and start the animation in the selector.
-(void) viewDidLoad
{
[super viewDidLoad];
UITapGestureRecognizer *tapGestureRg = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(beginAnimation)];
[self.view addGestureRecognizer:tapGestureRg];
}
-(void) beginAnimation
{
// Set Delay on Animations when Game Start - Animations Area ***** PERFORMS ****** 0.1
[self performSelector:#selector(Animation) withObject:nil afterDelay:0.1];
}
If you want to begin animation on touch begins then override -(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event and start the animation there.
You can add the Tap Gesture to screen.If you have UIViewController and you want to start animation on tap of UIViewController view tap then you can use this code as it is or you can change the view on which you want tap and start animation
-(void) viewDidLoad
{
[super viewDidLoad];
UITapGestureRecognizer *singleFingerTap =
[[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(handleSingleTap:)];
[self.view addGestureRecognizer:singleFingerTap]; //Replace the self.view if you want to add single tap on another view with your view refrence
}
- (void)handleSingleTap:(UITapGestureRecognizer *)recognizer {
[self Animation]; //You should rename this method as animation.Follow naming conventions.
}
I have view where pan a view from one location to another. I can get the location of the points when moved, but I want to do some action when pan has not moved or is idle in certain point. I Don't see any state to show pan gesture is idle. The touch event I came across is UITouchPhaseStationary nut I don't know how to implement it.
There isn't a state for that, and UITouchPhaseStationary as explained in this post is for multi-touch, and lets you know if there is a second stationary finger on the screen while another finger is moving.
However, you can implement something like this yourself. Just make a timer with a time interval set to the timeout before the touch should be considered stationary and run it when the gesture's position changes. You'll want to invalidate the timer when the gesture ends as well as when the gesture changes to reset the timer. Here's an example.
- (void)gestureDidFire:(UIPanGestureRecognizer *)gesture
{
static NSTimer *timer = nil;
if (gesture.state == UIGestureRecognizerStateChanged) {
if (timer.isValid) {
[timer invalidate];
}
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(timerDidFire:) userInfo:nil repeats:NO];
}else if (gesture.state == UIGestureRecognizerStateEnded) {
if (timer.isValid) {
[timer invalidate];
}
}
}
- (void)timerDidFire:(NSTimer *)timer
{
NSLog(#"Stationary");
}
You could implement touchesEnded:withEvent: to determine when touch has finished. Set a flag on your panGesture method and then check that value
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if (self.hasMoved) {
// Do something
self.hasMoved = NO;
}
}
Try this
- (void)panRecognized:(UIPanGestureRecognizer *)rec
{
CGPoint vel = [rec velocityInView:self.view];
if (vel.x == 0 && vel.y == 0)
{
// Not moved
}
else
// moved
}
Is there any way to get notified for continuous press on UIView? i.e. I keep pressing UIView with one finger and i want to keep calling particular method during that duration again and again.
I tried UILongPressGestureRecognizer but it just get notify of began, end, moved etc. Same as TouchesBegan.
Cheers
On TouchesBegan start a timer and perform a selector when you've reached a desired amount of time (long press).
On TouchesEnded invalidate the timer to prevent the selector from being performed.
I would also set up an extra flag detecting "fingerReleased":
Set fingerReleased = NO on TouchesBegan and fingerReleased = YES on TouchesEnded and put the code you want to execute in a:
if (!fingerReleased)
{
// Execute code
}
fire NSTimer in touchesBegan to call a selector that you want and invalidate it in touchesEnded method
What you can do is, use the combine functionality of UILongPressGestureRecognizer and NSTimer.
The following code should meet your requirement.
#property (strong, nonatomic) NSTimer *timer;
- (void) viewDidLoad
{
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(longPress:)];
[aView addGestureRecognizer: longPress];
}
- (void) longPress:(UILongPressGestureRecognizer*)gesture
{
if ( gesture.state == UIGestureRecognizerStateBegan )
{
self.timer=[NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(callMethod) userInfo:nil repeats:YES];
[self.timer fire];
}
if ( gesture.state == UIGestureRecognizerStateEnded )
{
[self.timer invalidate];
}
}
- (void) callMethod
{
//will be called continuously within certain time interval you have set
}
who knows which would be the following gesture:
Imagine I click within some region (say a button), I hold the finger down,
don't release it, and move the finger outside the region area??
is there some gesture to it?
I have a button which starts up in a selected/highlighted state, but if a user clicks
on it (using a finger), doesn't release his/her finger, moves his/her finger outside the button area, my button gets deselected - which is something I don't want. Can someone help?
You need to use, UIGesuture recongnizers: UIPanGestureRecognizer.
You need to handle "Touch Up Outside" button event.
==EDIT==
You can override UIControl's *TrackingWithTouch:withEvent: methods to get the behaviour you want:
#interface CustomButton : UIButton
#end
implementation
#interface CustomButton()
// Auxiliary property
#property(nonatomic, assign) BOOL isHighlightedBeforeTouch;
#end
#implementation CustomButton
-(BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
self.isHighlightedBeforeTouch = self.highlighted;
return [super beginTrackingWithTouch:touch withEvent:event];
}
-(BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
BOOL result = [super continueTrackingWithTouch:touch withEvent:event];
if( self.isHighlightedBeforeTouch && !CGRectContainsPoint(self.bounds, [touch locationInView:self]) ) {
dispatch_async(dispatch_get_main_queue(), ^{
self.highlighted = self.isHighlightedBeforeTouch;
});
}
return result;
}
-(void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
dispatch_async(dispatch_get_main_queue(), ^{
self.highlighted = self.isHighlightedBeforeTouch;
});
[super endTrackingWithTouch:touch withEvent:event];
}
#end
to change highlighted property e.g. on when button is pressed:
-(IBAction)buttonPressed:(UIButton*)sender
{
dispatch_async(dispatch_get_main_queue(), ^{
[sender setHighlighted:![sender isHighlighted]];
});
}