Question

I have the current code:

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

    self.objectPoint = [[touches anyObject] locationInView:self];
    float x, y;
    if (self.objectPoint.x > self.objectPoint.x) {
        x = self.objectPoint.x + 1;
    }
    else x = self.objectPoint.x - 1;
    if (self.fingerPoint.y > self.objectPoint.y) {
        y = self.objectPoint.y + 1;
    }
    else y = self.minionPoint.y - 1;
    self.objectPoint = CGPointMake(x, y);

    [self setNeedsDisplay];
}

My problem is that I want to keep the object follow your finger until you take your finger off the screen. It will only follow if my finger is moving. touchesEnded only works when I take my finger off the screen, so that's not what I want either. How can I enable something that would solve my problem?

Was it helpful?

Solution

If you want to touch a part of the screen and you want to move the drawn object in that direction as long as you're holding your finger down, there are a couple of approaches.

On approach is the use of some form of timer, something that will repeatedly call a method while the user is holding their finger down on the screen (because, as you noted, you only get updates to touchesMoved when you move). While NSTimer is the most common timer that you'd encounter, in this case you'd want to use a specialized timer called a display link, a CADisplayLink, that fires off when screen updates can be performed. So, you would:

  • In touchesBegan, capture where the user touched on the screen and start the CADisplayLink;

  • In touchesMoved, you'd update the user's touch location (but only called if they moved their finger);

  • In touchesEnded, you'd presumably stop the display link; and

  • In your CADisplayLink handler, you'd update the location (and you'd need to know the speed with which you want it to move).

So, that would look like:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    self.velocity = 100.0;     // 100 points per second
    self.touchLocation = [[touches anyObject] locationInView:self];

    [self startDisplayLink];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    self.touchLocation = [[touches anyObject] locationInView:self];
}

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

- (void)startDisplayLink
{
    self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleDisplayLink:)];
    self.lastTimestamp = CACurrentMediaTime();   // initialize the `lastTimestamp`
    [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}

- (void)stopDisplayLink
{
    [self.displayLink invalidate];
    self.displayLink = nil;
}

- (void)handleDisplayLink:(CADisplayLink *)displayLink
{
    // figure out the time elapsed, and reset the `lastTimestamp`

    CFTimeInterval currentTimestamp = CACurrentMediaTime();
    CFTimeInterval elapsed = currentTimestamp - self.lastTimestamp;
    self.lastTimestamp = currentTimestamp;

    // figure out distance to touch and distance we'd move on basis of velocity and elapsed time

    CGFloat distanceToTouch  = hypotf(self.touchLocation.y - self.objectPoint.y, self.touchLocation.x - self.objectPoint.x);
    CGFloat distanceWillMove = self.velocity * elapsed;

    // this does the calculation of the angle between the touch location and
    // the current `self.objectPoint`, and then updates `self.objectPoint` on
    // the basis of (a) the angle; and (b) the desired velocity.

    if (distanceToTouch == 0.0)                  // if we're already at touchLocation, then just quit
        return;
    if (distanceToTouch < distanceWillMove) {    // if the distance to move is less than the target, just move to touchLocation
        self.objectPoint = self.touchLocation;
    } else {                                     // otherwise, calculate where we're going to move to
        CGFloat angle = atan2f(self.touchLocation.y - self.objectPoint.y, self.touchLocation.x - self.objectPoint.x);
        self.objectPoint = CGPointMake(self.objectPoint.x + cosf(angle) * distanceWillMove,
                                       self.objectPoint.y + sinf(angle) * distanceWillMove);
    }
    [self setNeedsDisplay];
}

and to use that, you'd need a few properties defined:

@property (nonatomic) CGFloat velocity;
@property (nonatomic) CGPoint touchLocation;
@property (nonatomic, strong) CADisplayLink *displayLink;
@property (nonatomic) CFTimeInterval lastTimestamp;

OTHER TIPS

If you want to drag it with your finger, you want to:

  • In touchesBegan, save the starting locationInView as well as the "original location" of the object being dragged;

  • In touchesMoved, get the new locationInView, calculate the delta (the "translation") between that and the original locationInView, add that to the saved "original location" of the view, and use that to update the view.

That way, the object will track 100% with your finger as you're dragging it across the screen.


For example, you might:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    self.touchBeganLocation = [[touches anyObject] locationInView:self];
    self.originalObjectPoint = self.objectPoint;
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    CGPoint location = [[touches anyObject] locationInView:self];
    CGPoint translation = CGPointMake(location.x - self.touchBeganLocation.x, location.y - self.touchBeganLocation.y);
    self.objectPoint = CGPointMake(self.originalObjectPoint.x + translation.x, self.originalObjectPoint.y + translation.y);
    [self setNeedsDisplay];
}

Probably needless to say, you need properties to keep track of these two new CGPoint values:

@property (nonatomic) CGPoint originalObjectPoint;
@property (nonatomic) CGPoint touchBeganLocation;

Frankly, I might use gesture recognizer, but that's an example of dragging with touchesBegan and touchesMoved.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top