how to recognize start and end of rotation gesture in iOS? - ios

I need two functions which would be fired when the rotation gesture starts and finishes, because I need to know the whole angle of the rotation. Currently the gesture recogniser is fired all the time until the rotation finishes, and I cannot find out when it has finished, to find to total angle.

That's because the method you hook to your gesture gets called for all of the gestures states, like began/ended/canceled/changed. You can however ask the gesture for its current state within the method, and add specific functionality for these different states. Here's a basic example:
- (void)rotationGestureHandler:(UIRotationGestureRecognizer *)gesture
{
if (gesture.state == UIGestureRecognizerStateBegan) {
// do stuff - call method for gesture began
}else if (gesture.state == UIGestureRecognizerStateEnded) {
// do other stuff - call method for gesture ended
}
}

Related

TouchesBegan delay on left hand side of the display

On iPhone's with 3D touch enabled, there is a feature where long pressing the left hand side of the screen with enough force opens lets you change which app is active. Because of this, when a non-moving touch happens on the left hand side of the screen, the touch event is delayed for a second or two until the iPhone verifies that the user is not trying to switch tasks and is interacting with the app.
This is a major problem when developing a game with SpriteKit, as these touches are delayed by a second every time a user taps/holds their finger on the left edge of the screen. I was able to solve this problem by registering a UILongPressGestureRecognizer in the main Scene of the game, thus disabling TouchesBegan and implementing a custom touches function (used as a selector by the gesture recognizer):
-(void)handleLongPressGesture:(UITapGestureRecognizer *)gesture {
CGPoint location = [gesture locationInView:self.view];
if (gesture.state == UIGestureRecognizerStateBegan)
{
//
}
else if (gesture.state == UIGestureRecognizerStateChanged)
{
//
}
else if (gesture.state == UIGestureRecognizerStateEnded)
{
//
}
else if (gesture.state == UIGestureRecognizerStateCancelled)
{
//
}
}
-(void)didMoveToView:(SKView *)view {
/* Setup your scene here */
UILongPressGestureRecognizer *longPressGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(handleLongPressGesture:)];
longPressGestureRecognizer.delaysTouchesBegan = false;
longPressGestureRecognizer.minimumPressDuration = 0;
[view addGestureRecognizer:longPressGestureRecognizer];
// continue
}
The problem with this is that I would have to implement a gesture recognizer for every touch (including simultaneous ones) that I expect the user to enter. This interferes with any touchesBegan methods as subclasses of SKSpriteNode, SKScene, etc. and kills a lot of functionality.
Is there any way to disable this delay? When registering the gestureRecognizer, I was able to set delaysTouchesBegan property to false. Can I do the same somehow for my SKScene?
To see this issue in action, you can run the default SpriteKit project, and tap (hold for a second or two) near the left hand side of the screen. You will see that there is a delay between when you touch the screen and when the SKShapeNodes are rendered (as opposed to touching anywhere else on the screen).
* Edit 1 *
For those trying to find a way to get around this for now, you can keep the gesture recognizer but set its cancelsTouchesInView to false. Use the gesture recognizer to do everything you need to do until TouchesBegan kicks in (touchesBegan will receive the same touch event about a second after the gesture recognizer recognizes the touch). Once touchesBegan kicks in, you can disable everything happening in the gesture recognizer. This seems like a sloppy fix to me, but it works for now.
Still trying to find a more-or-less formal solution.
I have experienced this as an user and it is really annoying. The only thing that worked for me was to disable the 3D touch. Otherwise the left side of the touchscreen is almost useless.

Periodic Event While Pan Gesture Hasn't Ended

So my current project has a pan gesture recognizer, and if I have panned to the top of the screen the screen it is supposed to scroll up to account for that gesture. While the gesture hasn't ended and the current position of the gesture remains at the top of the screen, I want to continually keep scrolling. My problem is the gesture recognizer only gets called when the state changes, therefore my content will only scroll if you move back and forth at the top, not continually while the gesture continues to be at the top. Is there any reasonable way to continually call code while the gesture hasn't ended, but isn't necessarily changing? Here is pseudo-code of what I have:
- (void)handleGestureRecognizer:(UIGestureRecognizer *)gesture {
if ( gesture.state == UIGestureRecognizerStateChanged ) {
CGPoint point = [gesture locationInView:self.view];
if (point.y < 100) {
//I would like this code to be called continually while the
//gesture hasn't ended, not necessarily only when it changes
[self updateScrollPosition];
}
}
I can think of a few ghetto ways to do it by setting state bools based on the current state of the recognizer and creating my own timer to periodically check, but it seems pretty hacky and I don't particularly like it, so I'm wondering if anyone could come up with a cleaner solution.
One way to use a timer and feel better about it would be to conceal it a little by using performSelector:withObject:afterDelay:
- (void)stillGesturing {
[self updateScrollPosition];
[NSObject cancelPreviousPerformRequestsWithTarget:self];
[self performSelector:#selector(stillGesturing) withObject:nil afterDelay:0.5];
}
// then in the recognizer target
if (gesture.state == UIGestureRecognizerStateEnded) {
[NSObject cancelPreviousPerformRequestsWithTarget:self];
} else if ( gesture.state == UIGestureRecognizerStateChanged ) {
CGPoint point = [gesture locationInView:self.view];
if (point.y < 100) {
//I would like this code to be called continually while the
//gesture hasn't ended, not necessarily only when it changes
[self stillGesturing];
}
}

Using gesture recognizers that dynamically stick to one touch among many

I have a view with four pan gestures attached. The first has both max and min number of touches set to 1, the second to 2, etc. This makes it so each will only recognize one touch while up to four fingers slide around on the screen.
That's working dandy. What isn't working is detecting when individual touches end. Anything I have set to happen when a gesture ends only happens when all gestures have ended completely.
Example delegate method:
- (void) handlePan:(UIPanGestureRecognizer*)recognizer {
//Setting what happens when a gesture is recognized as beginning
if (recognizer.state == UIGestureRecognizerStateBegan) {
//...whatever happens, bunnies follow your finger or whatever
} else
//Setting what happens when a gesture ends
if ((recognizer.state == UIGestureRecognizerStateEnded) |
(recognizer.state == UIGestureRecognizerStateCancelled) |
(recognizer.state == UIGestureRecognizerStateFailed)) {
NSLog(#"end");
}
}
What should be happening is that I see "end" in the console whenever any finger is lifted. Instead, I see nothing until all fingers are lifted, at which point I see "end" repeated four times (or as many times as fingers that were on the screen).
Is there any way I can make this work the way I intend?
edit After fiddling I see that I may not be analyzing my problem correctly. The whole reason I want to detect when a gesture's touch ends is that I want to have gestures able to become active when there is more than one touch on screen, but I want each gesture to only track one touch itself. I was setting an "active" flag on gestures that were tracking touches, and then toggling that flag off after touches ended, and that wasn't working, because touch-end-detection was hard to implement well.
But if there's a different way to achieve the same thing, that's the real thing I'm looking for: among many overlapping touches, have each gesture recognizer track one and only one.
You may want to do something like - it catches the change in fingers on the screen for the given gesture; you may need to add some more logic surrounding which gesture you're working with:
switch( recognizer.numberOfTouches ) {
case 1: {
NSLog(#"1 ");
break;
}
case 2: {
NSLog(#"2");
break;
}
case 3: {
NSLog(#"3");
break;
}
case 4: {
NSLog(#"4");
break;
}
default: {
NSLog(#"0");
}
}
This is what eventually worked.
In short, I made a flag that flipped whenever a gesture recognizer was assigned a touch, ensuring no other recognizers accepted that touch. I also tested each recognizer to make sure it only accepted a touch when it wasn't already following a touch. So I made each touch only get assigned once, and each recognizer only accept one touch. Worked like a charm.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
//set this to no every time a new touch happens, meaning it isn't taken yet.
touchTaken = NO;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
//If the touch is taken or the gesture's already following a touch, say no.
if (touchTaken | ([gestureRecognizer numberOfTouches] > 0)) {
return NO;
}
else {
touchTaken = YES;
return YES;
}
}

How to prefer tap gesture over draw gesture?

In my view I'm overriding all the "touches*" methods to let the user draw on the screen. I'm recording the locations.
In addition I have two gesture recognizers on my view to detect single tap and double tap.
If I now move my finger just a little bit and short enough, I will be recording a small "draw" gesture. However when raising the finger, an additional tap gesture will be triggered.
By trial and error I could possibly figure out a minimum time and movement threshold but I'm sure there are smarter ways?
I need to know after how much movement and/or it is save to assume that no tap gesture will trigger.
You can avoid tap gestures. Instead of that you can recognize taps in touch events itself.
- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
if(touches.count == 1)
{
if([[touches anyObject] tapCount] == 1)
{
// Do the action here for single tap
}
else if([[touches anyObject] tapCount] == 2)
{
// Do the action here for double tap
}
}
}
And you have to set a global bool variable for check whether user moved the finger on the screen.
BOOL _isMoved;
And make it TRUE in the touch move event
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
_isMoved = YES;
}
Before recording the track, you check whether this flag is TRUE or not? And also dont forget to make the flag to FALSE after saving the track
Hope this will help you :)

pressGestureRecognizer and touchesBegan

I have the following problem.
I am using a UILongPressGestureRecognizer to put a UIView into a "toggle mode". If the UIView is in "toggle mode" the user is able to drag the UIView around the screen. For dragging the UIView around the screen I am using the methods touchesBegan, touchesMoved and touchesEnded.
It works, but: I have to lift my finger in order to drag it, because the touchesBegan method got already called and therefore is not called again and therefore I can't drag the UIView around the screen.
Is there any way to manually call touchesBegan after UILongPressGestureRecognizer got triggered (UILongPressGestureRecognizer changes a BOOL value and the touchesBegan only works if this BOOL is set to YES).
UILongPressGestureRecognizer is a continuous gesture recognizer, so rather than resorting to touchesMoved or UIPanGestureRecognizer, just check for UIGestureRecognizerStateChanged, e.g.:
- (void)viewDidLoad
{
[super viewDidLoad];
UILongPressGestureRecognizer *gesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(handleGesture:)];
[self.view addGestureRecognizer:gesture];
}
- (void)handleGesture:(UILongPressGestureRecognizer *)gesture
{
CGPoint location = [gesture locationInView:gesture.view];
if (gesture.state == UIGestureRecognizerStateBegan)
{
// user held down their finger on the screen
// gesture started, entering the "toggle mode"
}
else if (gesture.state == UIGestureRecognizerStateChanged)
{
// user did not lift finger, but now proceeded to move finger
// do here whatever you wanted to do in the touchesMoved
}
else if (gesture.state == UIGestureRecognizerStateEnded)
{
// user lifted their finger
// all done, leaving the "toggle mode"
}
}
I would suggest you to use UIPanGestureRecognizer as it a recommended gesture for dragging.
You can configure the min. and max. number of touches required for a panning, using the following the properties:
maximumNumberOfTouches
minimumNumberOfTouches
You can handle the states like Began, Changed and Ended, like having animation for the required states.
Using the below method translate the point to the UIView in which you want it.
- (void)setTranslation:(CGPoint)translation inView:(UIView *)view
example:
You have to use a global variable to retain the old frame. Get this in UIGestureRecognizerStateBegan.
When the state is UIGestureRecognizerStateChanged. You can use the
-(void) pannningMyView:(UIPanGestureRecognizer*) panGesture{
if(panGesture.state==UIGestureRecognizerStateBegan){
//retain the original state
}else if(panGesture.state==UIGestureRecognizerStateChanged){
CGPoint translatedPoint=[panGesture translationInView:self.view];
//here you manage to get your new drag points.
}
}
Velocity of the drag. Based on the velocity you can provide a animation to show bouncing of a UIView
- (CGPoint)velocityInView:(UIView *)view

Resources