Please help me get a grip on UIGestureRecognizers in swift - ios

I am having the hardest time figuring out gesture recognizers and such on iOS. Unfortunately a lot of the documentation by apple appears to be in Objective-C and/or it doesn't give you examples that show what values can go in. Could you show me some examples of how to the the following things.
Get the current position of each touch event on the screen. If the finger isn't down then return false.
Make a direction recognizer other then the 4 main directions. Currently my code looks like this
var leftSwipe = UISwipeGestureRecognizer(target: self, action: Selector("HandleSwipes:"))
leftSwipe.direction = .Left
view.addGestureRecognizer(leftSwipe)
However what if I wanted to detect diagonal movement? Their isn't a .Diagonal. So the detection of a gesture works in the direction (0.5, 0.5).
Going back to the code I put before I have a function that I check the direction in
func HandleSwipes(sender: UISwipeGestureRecognizer) {
if (sender.direction == .Left) {
Label.text = "Left"
}
}
What if I want this swipe to only work for the 2nd finger down? Also how would I get the ending, and starting position of that gesture? (preferably inside of that function)
How can I find out how long it has taken the finger to do the gesture (getting from point A to point B).

I am not that familiar with swift, but in objective-c, this is how you will obtain the current position of a touch event.
CGPoint _originalCenter;
-(void)handlePan:(UIPanGestureRecognizer *)recognizer {
if (recognizer.state == UIGestureRecognizerStateBegan) {
// if the gesture has just started, record the current centre location
_originalCenter = self.center;
}
Hope this helps answer the first part of your question.

Related

UITapGestureRecognizer - Detecting if there's a sprite at tap location (swift)

I'm making an iOS game and need to know how to get the location of tap for uitapgesturerecognizer. I do NOT want to use touchesBegan, touchesEnded, etc. I also need to know how to check if a user double tapped the sprite node. I already have set up uitapgesturerecognizer's all I need to know is how to find the location of touch and see if it is the same location as a sprite node.
Any help is greatly appreciated!
Assuming your sprite is of a class that inherits from UIView (and I think they generally are) the handler function for the gesture recognizer should look like this.
func handleDoubleTap(gestureRecognizer: UITapGestureRecognizer) {
let tapLocation = gestureRecognizer.location(in: gestureRecognizer.view)
if mySprite.frame.contains(tapLocation){
print("tapped")
}
}

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.

Touch locations UIPinchGestureRecognizer

I'm trying to resize an image using the UIPinchGesture recognizer and in order to carry that out I need to find the location of the center point upon which the pinching is focused on.
Originally I thought about creating a midpoint calculation for the center point based on the two touched points. The problem is that for some reason the returned points using the touch indexes are not the touch locations that are applied on screen.
For example, when I tried zooming using a touch that was approximately at (333, 187) and another at (1000, 563) the returned locations of the touches were (496, 279) and (170, 95).
What exactly are UITouch 1 and UITouch 2 the indexes of? How can I find the center-point value?
func handlePinchGesture(gesture: UIPinchGestureRecognizer){
// Finds the midpoint location of the pinch gesture
var touch1 = gesture.locationOfTouch(0, inView: self.view)
var touch2 = gesture.locationOfTouch(1, inView: self.view)
var midPointX = (touch1.x + touch2.x)/2
var midPointY = (touch1.y + touch2.y)/2
var touchedPoint = CGPointMake(midPointX, midPointY)
}
How can I find the center-point value
You don't have to find it. The gesture recognizer gives it to you. It is the gesture recognizer's locationInView:.
You can use the following code to get the touch location:
#objc func onGesture(gesture: UIPinchGestureRecognizer) {
print("Location", gesture.location(ofTouch: 0, in: nil))
}
CGPoint centerPoint = [recognizer locationInView: self.view];
Apple says:
The returned value is a generic single-point location for the gesture computed by the UIKit framework. It is usually the centroid of the touches involved in the gesture.
This will give you the center. Hope this helps.. :)

UISwipeGestureRecognizer with SpriteKit using Swift

I am creating a game in swift that involves making words out of letters. The letters, which are individual SKSpriteNodes, sit on a "shelf" which is a SKSpriteNode. To remove the letters from the "shelf", I am trying to introduce swiping up. Unfortunately I am having issues with the swipe being picked up by the letters. The shelf seems to be absorbing it, or sometimes even the SKScene. I have disabled the user interaction on the shelf node.
This is my setup:
swipeRecognizer = UISwipeGestureRecognizer(target: self, action: Selector("move:"))
swipeRecognizer.direction = UISwipeGestureRecognizerDirection.Up
self.view.addGestureRecognizer(swipeRecognizer)
I add the swipeRecognizer to the view, then in the move method, I have the following:
func move(swipe:UISwipeGestureRecognizer){
if(swipe.state == UIGestureRecognizerState.Ended && swipe.numberOfTouches() == 1){
var touchLocation = swipe.locationInView(swipe.view)
touchLocation = self.convertPointFromView(touchLocation)
var sprite = self.nodeAtPoint(touchLocation)
if sprite is Letter{
let letter = sprite as Letter
if(gameManager.letterOnShelf(letter)){
gameManager.letterFlicked(letter)
}
}
}
}
Only sometimes to it recognize the sprite as a letter, 80% of the time the sprite is the shelf and I can't figure out what the correct way to work it is.....
Any help is greatly appreciated.
Cheers all!!
I would recommend:
Using a UILongPressGestureRecognizer with minimumPressDuration set to 0.
When the gesture is at the UIGestureRecognizerStateBegan state, see if the sprite your touch is over is a Letter and save a reference to that tile.
In the UIGestureRecognizerStateEnded state, you should be able to see what location your gesture ended (or perhaps the trajectory, etc) and then move the tile you stored a reference to from earlier.

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 :)

Resources