views:

2566

answers:

3

I have a quick question regarding tracking touches on the iPhone and I seem to not be able to come to a conclusion on this, so any suggestions / ideas are greatly appreciated:

I want to be able to track and identify touches on the iphone, ie. basically every touch has a starting position and a current/moved position. Touches are stored in a std::vector and they shall be removed from the container, once they ended. Their position shall be updated once they move, but I still want to keep track of where they initially started (gesture recognition).

I am getting the touches from [event allTouches], thing is, the NSSet is unsorted and I seem not to be able to identify the touches that are already stored in the std::vector and refer to the touches in the NSSet (so I know which ones ended and shall be removed, or have been moved, etc.)

Here is my code, which works perfectly with only one finger on the touch screen, of course, but with more than one, I do get unpredictable results...

    - (void) touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
{
    [self handleTouches:[event allTouches]];
}

- (void) touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event
{
    [self handleTouches:[event allTouches]];
}

- (void) touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
    [self handleTouches:[event allTouches]];
}

- (void) touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event
{
    [self handleTouches:[event allTouches]];
}

- (void) handleTouches:(NSSet*)allTouches
{   
    for(int i = 0; i < (int)[allTouches count]; ++i)
    {
     UITouch* touch = [[allTouches allObjects] objectAtIndex:i];
     NSTimeInterval timestamp = [touch timestamp];

     CGPoint currentLocation = [touch locationInView:self];
     CGPoint previousLocation = [touch previousLocationInView:self];

     if([touch phase] == UITouchPhaseBegan)
     {
      Finger finger;
      finger.start.x = currentLocation.x;
      finger.start.y = currentLocation.y;
      finger.end = finger.start;
      finger.hasMoved = false;
      finger.hasEnded = false;

      touchScreen->AddFinger(finger);
     }
     else if([touch phase] == UITouchPhaseEnded || [touch phase] == UITouchPhaseCancelled)
     {
      Finger& finger = touchScreen->GetFingerHandle(i);

      finger.hasEnded = true;
     }
     else if([touch phase] == UITouchPhaseMoved)
     {
      Finger& finger = touchScreen->GetFingerHandle(i);

      finger.end.x = currentLocation.x;
      finger.end.y = currentLocation.y;
      finger.hasMoved = true;
     }
    }

    touchScreen->RemoveEnded();
}

Thanks!

A: 

You should be able to properly collate your touches by storing the previous location of all touches and then comparing these previous locations when new touches are detected.

In your -handleTouches method, you could put something like this in your for loop:

// ..existing code..
CGPoint previousLocation = [touch previousLocationInView:self];

// Loop through previous touches
for (int j = 0; j < [previousTouchLocationArray count]; j++) {
  if (previousLocation == [previousTouchLocationArray objectAtIndex:j]) {
    // Current touch matches - retrieve from finger handle j and update position
  }
}

// If touch was not found, create a new Finger and associated entry

Obviously you'll need to do some work to integrate this into your code, but I'm pretty sure you can use this idea to correctly identify touches as they move around the screen. Also I just realized CGPoint won't fit nicely into an NSArray - you'll need to wrap these in NSValue objects (or use a different type of array).

pix0r
Good idea. For some reason this did not work for me, however. It seems like the positions are sometimes off. This might be due to decreased performance, when I am rendering to the OpenGL context (I am working on a game). Maybe touch events do get dropped then and the previous locations do no longer correspond to the stored locations? Thank you, though!
FlorianZ
This caused a problem for me, too. Especially when you move a lot of fingers on the screen. Sometimes you get a location that doesn't match any previous location.
Nosredna
+3  A: 

To fix your problem scrap your "handleTouches" method. The first thing you do in your handleTouches method, is switch it on the touchPhase, but that is already given to you. If you recieve the touch in touchesBegan, you know the touch is in UITouchPhaseBegan. By funneling touches from the four touch methods into one method, you are defeating the purpose of having four delegate methods.

In each of those methods, Apple gives you an opportunity to deal with a different phase of the current touch.

The second thing is that you don't need to search the event for the current touch, it is given to you as a parameter: touches.

An event is comprised of sets of touches. For convienence, you are given the current touches even though it can also be found within event.

So, in touchesBegan, you start tracking a touch.

    - (void) touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event{


        NSString *startPoint = NSStringFromCGPoint([[touches anyObject] locationInView:self]);  

        NSDictionary * touchData = [NSDictionary dictionaryWithObjectsandKeys: startPoint, @"location", touches, @"touch"]

        [startingLocations addObject:touchData];

        }

I'm using an array of dictionaries to hold my touch data.

Try to seperate your code and move it into the appropriate touch method. For direction, Apple has a couple sample projects that focus on touches and show you how to setup those methods.

Remember, these methods will get called automatically for each touch during each phase, you don't need to cycle through the event to find out what happened.

The pointer to each set of touches remains constant, just the data changes.

Also, I would read the iPhone OS programming guide section on event handling which goes into greater depth of what I said above with several diagrams explaining the relationship of touches to events over time.

An excerpt:

In iPhone OS, a UITouch object represents a touch, and a UIEvent object represents an event. An event object contains all touch objects for the current multi-touch sequence and can provide touch objects specific to a view or window (see Figure 3-2). A touch object is persistent for a given finger during a sequence, and UIKit mutates it as it tracks the finger throughout it. The touch attributes that change are the phase of the touch, its location in a view, its previous location, and its timestamp. Event-handling code evaluates these attributes to determine how to respond to the event.

Corey Floyd
I agree with you. I split my code up into the appropriate methods. However, my problem is that I need to create a link between Obj-C and C++ and my game architecture requires for the input to be polled instead of being handled once events are triggered. That is why I am storing the touches in a STL vector.Anyways, your information about the UITouch object being persistent over time helped a lot. Now, I am using the address to the object as key to uniquely identify a touch. Works like a charm. Thank you.
FlorianZ
+2  A: 

It appears the "proper" way to track multiple touches is by the pointer value of the UITouch event.

You can find more details in the "Handling a Complex Multi-Touch Sequence" section of this Apple Developer Documentation

Danny Sung
If you only track previous locations and use that to find related touches, you won't succeed. Often touchesEnded will pass in a location that differs from all your cached values.Google for "Handling a Complex Multi-Touch Sequence" to find the docs; the link moves around. Erica Sadun wrote about this: http://blogs.oreilly.com/iphone/2008/10/recovering-multiple-touches.html and also provided some code: http://github.com/erica/iphone-3.0-cookbook-/blob/master/C08-Gestures/14-Resize%20And%20Rotate/main.mI guess Apple wishes it could redo the touch API to pass an NSArray rather than NSSet.
Graham Perks