Search code examples
iosobjective-csimulatoruitapgesturerecognizeruipangesturerecognizer

iOS simulator stops recognizing gestures correctly after random periods of time


Sorry for how long this is. It's a mystery to me, looking for any pointers anyone might have.

There seems to be a bug in the iOS simulator in how UIGestureRecognizers work and pass along touch points that persists until the simulator is restarted. Even killing the app and restarting it within the simulator does not fix it. The only way to fix it (temporarily) is to actually exit the simulator entirely and restart it.

I've got an SKView subclass inside of a UIViewController subclass. The SKView subclass occupies the bottom section of the UIViewController, with the top section containing some buttons and other controls. The game involves taps and pans, managed by UIGestureControllers that are owned by the SKView subclass that owns an SKScene subclass.

After arbitrary periods of time, the gestures stop working correctly. None of them are recognized correctly within the view owned by the SKScene. If I click outside the SKView and in the parent UIViewController, then they show up, but the coordinates are all wrong. It also gets state wrong - sometimes the UIPanGestureRecognizer thinks that there are two fingers pressed instead of only one. The UIPanGestureRecognizer is similarly off. There is no recognizable pattern to this behavior - it just starts all of the sudden.

Here is the relevant code for initializing the SKView and it's scene:

    SKView * _gameView = [[SKView alloc] initWithFrame : gameRect];
    _gameView.clipsToBounds = YES;
    // _gameView.scene is an SKScene subclass
    CGSize gameSize = CGSizeMake(_gameView.bounds.size.width,
                                 _gameView.bounds.size.width);
    _scene = [[GameScene alloc] initWithSize : gameSize];
    // Present the scene.
    [_gameView presentScene : _scene];
    [self.view addSubview : _gameView];

Here is the code inside the SKScene subclass that registers the UIGestureControllers:

- (void) initializeGestureRecognition : (SKView *) view
{
    UIPanGestureRecognizer * panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget : self action : @selector (handlePanGesture:)];
    UITapGestureRecognizer * tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget : self action : @selector (handleTapGesture:)];
    panRecognizer.delegate = self;
    tapRecognizer.delegate = self;
    panRecognizer.maximumNumberOfTouches = 2;
    [tapRecognizer requireGestureRecognizerToFail : panRecognizer];
    [view addGestureRecognizer : panRecognizer];
    [view addGestureRecognizer : tapRecognizer];
}

Here is the code for the UIPanGestureRecognizer's handlePanGesture delegate:

-(void) handlePanGesture : (UIPanGestureRecognizer*) panRecognizer
{
    self.deltaInView = [panRecognizer translationInView : self.view];
    self.deltaInScene = CGPointMake(deltaInView.x, -(deltaInView.y));
    if (UIGestureRecognizerStateChanged == panRecognizer.state)
        {
            self.panTouchPoint = [self getPanTouchPoint : panRecognizer];
        }
    else if (UIGestureRecognizerStateEnded == panRecognizer.state)
        {
            // Other non-relevant stuff here
        }
}

- (CGPoint) didGetPanTouchPoint : (UIPanGestureRecognizer*) panRecognizer
{
    CGPoint touchPoint = CGPointMake(0.0, 0.0);
    if (2 == panRecognizer.numberOfTouches)
    {
        CGPoint tempPoint = [self calculateSafeCentroidPointFromRecognizer : panRecognizer];
        touchPoint = [self convertPointFromView : tempPoint];
    }
    else if (1 == panRecognizer.numberOfTouches)
    {
        CGPoint tempPoint = [panRecognizer locationOfTouch : 0
                                                    inView : self.view];
        touchPoint = [self convertPointFromView : tempPoint];
    }
    return touchPoint;
}


- (CGPoint) calculateSafeCentroidPointFromRecognizer : (UIGestureRecognizer*) recognizer
{
    CGPoint thisPointInView = [recognizer locationOfTouch : 0
                                                   inView : self.view];
    CGPoint thatPointInView = [recognizer locationOfTouch : 1
                                                   inView : self.view];
    CGPoint safePoint = [self calculateCentroidPoint : thisPointInView
                                         secondPoint : thatPointInView];
    return safePoint;
}


CGPoint CalculateCentroidPoint(const CGPoint * thisPoint,
                               const CGPoint * thatPoint)
{
    CGPoint centroidPoint = CGPointMake((thisPoint->x + thatPoint->x) / 2.0),
                                        (thisPoint->y + thatPoint->y) / 2.0));
    return centroidPoint;
}

Here is the code for the UITapGestureRecognizer's handleTapGesture delegate:

- (void) handleTapGesture : (UIGestureRecognizer *) gestureRecognizer
{
    if (gestureRecognizer.state == UIGestureRecognizerStateEnded)
    {
        UITapGestureRecognizer * tapRecognizer = (UITapGestureRecognizer*) gestureRecognizer;
        CGPoint viewTouchLocation = [tapRecognizer locationOfTouch : 0
                                                            inView : self.view];
        CGPoint sceneTouchLocation = [self convertPointFromView : viewTouchLocation];
        // Do other stuff here
    }
}

One interesting thing to note is that BOTH the tap and pan gesture recognizers are incorrect. It isn't one or the other. This leads me to believe that there is some systemic problem.

(Or, more likely, I've mucked something up.)

Any thoughts?


Solution

  • Sometimes the simulator thinks that you are holding down the Option () key and gets stuck with that, which means the pinch gesture helper gets activated on the screen. To make this stop, just press Option again. If that doesn't work, try pressing Command as well.