Handling "Smart Zoom" gesture?

  • Hi all,

    I'm adding touch gestures to my app. In the System Prefs, I notice a feature called "Smart Zoom" which is a double-tap with two fingers. How does one receive this event in a view?

    --Graham
  • You should watch "Session 231 - What's New with Gestures" from the WWDC 2012 video sessions...

    [[[Brainchild alloc] initWithName:@"Richard Altenburg"] saysBestRegards];

    Op 28 jun. 2012, om 06:34 heeft Graham Cox het volgende geschreven:

    > I'm adding touch gestures to my app. In the System Prefs, I notice a feature called "Smart Zoom" which is a double-tap with two fingers. How does one receive this event in a view?
  • On 28/06/2012, at 4:30 PM, Richard Altenburg (Brainchild) wrote:

    > You should watch "Session 231 - What's New with Gestures" from the WWDC 2012 video sessions...

    I did, but I have to target 10.6.x and later. Sorry I forget to mention that. BTW, features that are part of M.... Li.. are still under NDA.

    --Graham
  • The NDA is why I only gave you the title of the video, all developers in this list can access the WWDC videos and see for themselves what is new.

    I guess for 10.6 and 10.7 you could roll your own "Smart Zoom" functionality, but that is probably not what you wanted to hear.

    [[[Brainchild alloc] initWithName:@"Richard Altenburg"] saysBestRegards];

    Op 28 jun. 2012, om 08:59 heeft Graham Cox het volgende geschreven:

    > I did, but I have to target 10.6.x and later. Sorry I forget to mention that. BTW, features that are part of M.... Li.. are still under NDA.
    >
  • On 28/06/2012, at 5:06 PM, Richard Altenburg (Brainchild) wrote:

    > I guess for 10.6 and 10.7 you could roll your own "Smart Zoom" functionality, but that is probably not what you wanted to hear.
    >
    >

    Yes, it is what I wanted to hear, or more precisely, some sort of pointer or "how to" about how I can do that.

    Specifically, the problem is that the -clickCount property of an NSEvent is only applicable to mouse events, not touch events. So what's the right way to trap this specific gesture? I have read the various touch-input documentation but nothing became obvious.

    --Graham
  • On Jun 28, 2012, at 00:14 , Graham Cox wrote:

    > Specifically, the problem is that the -clickCount property of an NSEvent is only applicable to mouse events, not touch events. So what's the right way to trap this specific gesture? I have read the various touch-input documentation but nothing became obvious.

    The 10.6 document seems to imply that tapping isn't a gesture, so I'd assume you'd have to implement the NSResponder methods 'touches…WithEvent:' and use the event times to decide (a) if precisely 2 touches begin/end more or less simultaneously and (b) if a second 2-touch tap begins within a time interval after the 1st one [iOS defaults to 0.5 sec for this interval. Perhaps on the Mac you'd use something conditioned by the double-click time instead. I dunno.] and (c) if both 2-touch taps last less than some time interval [otherwise they'd represent a long press].

    Something like that?
  • On 28/06/2012, at 5:31 PM, Quincey Morris wrote:

    > On Jun 28, 2012, at 00:14 , Graham Cox wrote:
    >
    >> Specifically, the problem is that the -clickCount property of an NSEvent is only applicable to mouse events, not touch events. So what's the right way to trap this specific gesture? I have read the various touch-input documentation but nothing became obvious.
    >
    > The 10.6 document seems to imply that tapping isn't a gesture, so I'd assume you'd have to implement the NSResponder methods 'touches…WithEvent:' and use the event times to decide (a) if precisely 2 touches begin/end more or less simultaneously and (b) if a second 2-touch tap begins within a time interval after the 1st one [iOS defaults to 0.5 sec for this interval. Perhaps on the Mac you'd use something conditioned by the double-click time instead. I dunno.] and (c) if both 2-touch taps last less than some time interval [otherwise they'd represent a long press].
    >
    > Something like that?
    >
    >

    OK, that's pretty much what I'm doing, but it doesn't work all that well yet. The gesture is recognised, but only sometimes, so the effect for the user is that the gesture response is flaky.

    I'm not sure what I need to do to improve this.

    I've written a system somewhat similar (but simpler) than the iOS gesture recognizer system, where each gesture is recognized by an object, and as soon as it recognizes the gesture, it informs its delegate and tells the other recognizers in the set to fugeddaboudit. But in fact, I only have the one recognizer in there.

    Here's the code, which happens to be in my recognizer object, but this can be thought of as being implemented by a view:

    - (void)    touchesBeganWithEvent:(NSEvent*) event
    {
    NSSet* touches = [event touchesMatchingPhase:NSTouchPhaseBegan inView:self.view];

    if([touches count] == 2 )
    {
      if( self.state == DKWaitingForTap )
      {
      mFirstTapTime = [NSDate timeIntervalSinceReferenceDate];
      [NSTimer scheduledTimerWithTimeInterval:[NSEvent doubleClickInterval] + 0.1 target:self selector:@selector(timeOut:) userInfo:nil repeats:NO];
      self.state = DKGotFirstTap;
      }
      else if( self.state == DKGotFirstTap )
      {
      NSTimeInterval dt = [NSDate timeIntervalSinceReferenceDate] - mFirstTapTime;

      if( dt <= [NSEvent doubleClickInterval])
      {
        self.state = DKGotSecondTap;
        [self notifyDelegate];  // will eventually call -reset which puts the state back if timer or ended event doesn't do it first
      }
      }
    }
    }

    - (void)    touchesEndedWithEvent:(NSEvent *)event
    {
    if( self.state != DKWaitingForTap )
    {
      NSTimeInterval dt = [NSDate timeIntervalSinceReferenceDate] - mFirstTapTime;

      if( dt > [NSEvent doubleClickInterval])
      [self reset];
    }
    }

    - (void)    touchesMovedWithEvent:(NSEvent*) event
    {
    [self reset];
    }

    - (void)    touchesCancelledWithEvent:(NSEvent*) event
    {
    [self reset];
    }

    - (void)    reset
    {
    self.state = DKWaitingForTap;
    }

    - (void)    timeOut:(NSTimer*) timer;
    {
    [self reset];
    }

    As you can see, I don't bother to track the touches - I only determine that there are two, and use a simple state machine to detect a double-tap. On the second tap the touches will be new objects anyway, so there isn't any mileage in trying to match them up with the touches of the first event. There's also a timer that resets the state if the second tap is never received.

    It works, but since the results are flaky, this is obviously not good enough. Has anyone actually implemented something like this, and can see where I have gone wrong?

    --Graham
  • On Jun 28, 2012, at 19:57 , Graham Cox wrote:

    > - (void)    touchesBeganWithEvent:(NSEvent*) event
    > {
    > NSSet* touches = [event touchesMatchingPhase:NSTouchPhaseBegan inView:self.view];
    >
    > if([touches count] == 2 )

    Well, this looks wrong to me. In general, each of the 2 touches is going to begin at a different time, which means this method will be called twice, and there's only be one touch in the set at each call. (The touch that began earlier is in a different phase at this event.)

    I would expect this code to work only if both fingers hit the trackpad simultaneously. That's probably not very hard to do, which is why it works sometimes but only flakily.

    I think what you need to to is add up *all* the touches in all phases [well, at least in "began" and "moved" phases, not sure about the others] at *any* of the touch responder methods, to work out how many touches are present simultaneously. Even that won't ensure that the touches begin close together in time, though, which you may or may not care about.
  • On 29/06/2012, at 1:17 PM, Quincey Morris wrote:

    > Well, this looks wrong to me. In general, each of the 2 touches is going to begin at a different time, which means this method will be called twice, and there's only be one touch in the set at each call. (The touch that began earlier is in a different phase at this event.)
    >
    Hmm, ok, I can investigate that. I imagined that touches that went down very close in time were coalesced by the driver into a single event. That assumption, possibly faulty, was strongly reinforced by the example code listed in the documentation, which specifically does exactly this - [touches count] == 2.

    --Graham
  • On 29/06/2012, at 1:17 PM, Quincey Morris wrote:

    >> NSSet* touches = [event touchesMatchingPhase:NSTouchPhaseBegan inView:self.view];

    Ah, noticed a silly mistake. If I use NSTouchPhaseTouches here, I get a count of 2 far more often. That suggests that in fact events are coalesced, or at least sent several times with the touches set building up.

    --Graham
previous month june 2012 next month
MTWTFSS
        1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30  
Go to today