UIPanGestureRecognizer not working when slide in from top - ios

i have a problem with the UIPanGestureRecognizer behavior.
All works great unless I slide in from the top of my iPad. Top means here the side where the camera is located, no matter how the current device orientation is.
With the following code i debug the UIPanGestureRecognizer behavior:
- (void)viewDidLoad
{
[super viewDidLoad];
_pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(panGesture:)];
[self.view addGestureRecognizer:_pan];
}
- (void)panGesture:(UIPanGestureRecognizer*)gesture
{
if (gesture.state == UIGestureRecognizerStateBegan) {
NSLog(#"BEGIN");
} else {
NSLog(#"GO");
}
}
So when i slide in from the top nothing happens.
It seems that iOS would catch that gesture, perhaps it is related to the notification center?
In principle it seems possible to get that gesture because i saw this on other apps.
What am I missing here?

Apple says in their transition guide that the existence of the notification center means that your touches at the very bottom and very top of the screen may be canceled. I think this is likely an example of that.

Related

iPhone-X - How to force user to swipe twice home indicator to go home-screen

I'm using the code below to hide the home indicator on iPhone X, which is working fine in the emulator.
-(BOOL)prefersHomeIndicatorAutoHidden
{
return YES;
}
But even though it's hidden, I am still able to swipe up from the bottom and my game goes to the home screen.
I have seen a few games where the user has to swipe up once to bring up the home indicator and swipe up again to go to the home screen.
So, how can I force the user to swipe the home indicator twice to go to the home screen in iOS 11 with Objective-C?
This behavior is required for full-screen games.
I had the same problem.
PrefersHomeIndicatorAutoHidden must return NO but also PreferredScreenEdgesDeferringSystemGestures must be overridden and return UIRectEdgeBottom.
Swift 4.2
override var prefersHomeIndicatorAutoHidden: Bool {
return false
}
override var preferredScreenEdgesDeferringSystemGestures: UIRectEdge {
return UIRectEdge.bottom
}
Adding the following to the ViewController did the trick for me:
- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures
{
return UIRectEdgeBottom;
}
This made the Home Indicator more transparent and inactivated so that it requires an extra swipe in order to leave the game.
You can also use UIRectEdgeAll instead of UIRectEdgeBottom to defer the system gestures on all edges of the screen.
It is a choice between hidden and deferred but NOT both
-(BOOL)prefersHomeIndicatorAutoHidden
{
// YES for hidden (but swipe activated)
// NO for deferred (app gets priority gesture notification)
return NO;
}
register the gesture in viewDidLoad
UIScreenEdgePanGestureRecognizer *sePanGesture = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:#selector(handleGesture:)];
sePanGesture.edges = UIRectEdgeAll;
// or just set the bottom if you prefer, top-right seems to behave well by default
[self.view addGestureRecognizer:sePanGesture];
and define the handleGesture, no need to do anything there for this to work
- (void)handleGesture:(UIScreenEdgePanGestureRecognizer *)recognizer {
// to get location where the first touch occurred from docs
// CGPoint location = [recognizer locationInView:[recognizer.view superview]];
NSLog(#"gestured");
}
should be it
This works for me :
-(bool) prefersHomeIndicatorAutoHidden {
return NO;
}

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.

Disable user interaction on one UIView as the other UIView is swiped

Before any starts, I understand yourView.userInteractionEnabled = NO; is an option, but let me explain first the circumstance.
I have these 2 UIView objects, stoneOne and stoneTwo. I have 4 UISwipeGestureRecognizer objects attached to them for up, down, left and right. Imagine swiping these 'stones' around a 5x5 grid.
What I don't want is to be able to swipe both at the same time.
Currently, that bug is still an issue. I'll show you a method called swipeLeft: which represents the layout for all swipe directions.
- (IBAction)swipeLeft:(UISwipeGestureRecognizer *)recognizer {
_oldMove1 = _move1;
_oldMove2 = _move2;
if (recognizer.view == _oneStone
&& recognizer.direction == UISwipeGestureRecognizerDirectionLeft) {
_twoStone.userInteractionEnabled = NO;
_oneStone = recognizer.view;
[self moveOne:CGPointMake(-1, 0) withView:_oneStone];
self.move1++;
// 'causeADelay:' runs _twoStone.userInteractionEnabled = YES;
[self performSelector:#selector(causeADelay:) withObject:_twoStone afterDelay:1];
} else if (recognizer.view == _twoStone
&& recognizer.direction == UISwipeGestureRecognizerDirectionLeft) {
_oneStone.userInteractionEnabled = NO;
_twoStone = recognizer.view;
[self moveTwo:CGPointMake(-1, 0) withView:_twoStone];
self.move2++;
[self performSelector:#selector(causeADelay:) withObject:_oneStone afterDelay:1];
}
self.moveCount++;
}
One of the things I tried was creating a delay on when I can interact with my UIView objects. This worked ONLY IF I waited a split half-second to interact. The full delay would occur, and everything would work.
My bug is when you swipe them both at the same time. Is that because of the swipe gestures attached to them?
I also tried removing and reapplying the objects as subviews...didn't work, obviously. I really need this to work otherwise I have a dead-end game. I was very new to coding when I first started and never thought about Cocos2d or other game-driven platforms for development, so everything I have came from on-the-fly thinking.
There are several solutions, but here's a particularly easy one:
Remove the swipe gesture recognizers from the stones and attach them instead to the common superview of the stones. This solves the problem, because only one gesture recognizer on the same view will recognize at any one time.
Of course, you will now have to use hit-testing to find out which stone (if any) is being swiped. But that's an easy implementation detail, and is a small price to pay.
And of course another cool feature is that you now only need four gesture recognizers total!

iOS detect drag outside left edge of screen

In iPad when you put your finger outside top or bottom edge of screen and then drag it on screen a menu is revealed. How can I implement that?
There is specifically a Gesture Recogniser class for this, introduced in iOS 7. It's the UIScreenEdgePanGestureRecognizer. The documentation for it is here. Check it out.
To test this in the simulator, just start the drag from near the edge (~15 points).
Also, you will have to create a gestureRecognizer for each edge. You can't OR edges together, so UIRectEdgeAll won't work.
There is a simple example here. Hope this helps!
Well you can do something like this, this example is the case where you want you pan gesture to work only when the user swipes 20px inside from the right hand side of the screen
First of all add the gesture to your window
- (void)addGestures {
if (!_panGesture) {
_panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePanGesture:)];
[_panGesture setDelegate:self];
[self.view addGestureRecognizer:_panGesture];
}
}
After adding the check whether the touch you recieved is a pan gesture and then perform your action accordingly
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
CGPoint point = [touch locationInView:self.view];
if (gestureRecognizer == _panGesture) {
return [self slideMenuForGestureRecognizer:gestureRecognizer withTouchPoint:point];
}
return YES;
}
Here is how you can check whether your touch is contained in the region where you want it to be
-(BOOL)isPointContainedWithinBezelRect:(CGPoint)point {
CGRect leftBezelRect;
CGRect tempRect;
//this will be the width between CGRectMaxXEdge and the screen offset, thus identifying teh region
CGFloat bezelWidth =20.0;
CGRectDivide(self.view.bounds, &leftBezelRect, &tempRect, bezelWidth, CGRectMaxXEdge);
return CGRectContainsPoint(leftBezelRect, point);
}

Is there a built in way to add a gesture recognizer (specifically pan) just to the right or left edges of a view?

Throughout iOS 7 there's many situations where users can slide their finger in from the left or right edge of the screen in order to perform an action, such as popping a view controller or showing a sidebar.
Is there a built in way to do this that I've completely overlooked somehow (yes, I've searched extensively)? Or is the only way to check the frame position of where the pan started?
This is because I want to perform distinct actions if the user pulls from the edge, or say the middle.
You have UIScreenEdgePanGestureRecognizer, which is added to iOS7 to detect, well, panning from the edges of the screen. For panning from the middle, a normal pan gesture recognizer will suffice, where you can check if the pan gesture originated close enough to the middle.
Use the UIScreenEdgePanGestureRecognizer, but check that it's available first (since it's iOS 7+):
if (NSClassFromString(#"UIScreenEdgePanGestureRecognizer")) {
UIScreenEdgePanGestureRecognizer *panRecognizer =
[[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self
action:#selector(handleScreenEdgePanGesture:)];
panRecognizer.edges = UIRectEdgeLeft;
}
Set up gestures
UIPanGestureRecognizer* panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePanning:)];
[self.view addGestureRecognizer:panGesture];
Handling gesture states
- (void)handlePanning:(UIPanGestureRecognizer *)gestureRecognizer
{
switch ([gestureRecognizer state])
{
case UIGestureRecognizerStateBegan:
[self startDragging:gestureRecognizer];
break;
//u won't need following cases
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled:
case UIGestureRecognizerStateFailed:
[self stopDragging:gestureRecognizer];
break;
default:
break;
}
}
Recognizing start point of drag
- (void)startDragging:(UIPanGestureRecognizer *)gestureRecognizer
{
CGPoint pointInSrc = [gestureRecognizer locationInView:yourVIEW];
}
The pan gesture recognizer is a predefined recognizer, about all you can do with it is to determine how fast the user moved their finger. If you want to tell if where the movement started, you'll have to code your own recognizer. It's not that difficult, you'll be notified when the touch started and where it ended.

Resources