2

All I need it to move some view around based on pan gesture. The problem is that it doesn't happen instantly, just like Apple Maps, if I put down my finger on one point, and move it fast to any direction, the view will move with a delay, meaning I will be able to see the point that I put my finger on, and that is what I don't want.

Maybe it is impossible due to hardware limitations since in the simulator it works fine. Or maybe there is a better way that I don't know.

Thank you.


- (IBAction)pan:(UIPanGestureRecognizer *)sender 
{
    CGPoint center = [sender locationInView:sender.view];
    self.myView.center = center; 
}
Rob
  • 415,655
  • 72
  • 787
  • 1,044
Rodrigo Ruiz
  • 4,248
  • 6
  • 43
  • 75
  • - (IBAction)pan:(UIPanGestureRecognizer *)sender { CGPoint center = [sender locationInView:sender.view]; self.myView.center = center; } – Rodrigo Ruiz Jan 10 '13 at 18:48
  • I'm sorry, I don't know how to keep indentation. – Rodrigo Ruiz Jan 10 '13 at 18:49
  • What is on this view? If fairly complicated, you can rasterize and see if that fixes it. – Rob Jan 10 '13 at 19:31
  • This view has only 2 UIImageView's (one of the UIImageView's is being animated, just scale, like pulsing). Basically myView is the same as that blue ball with pulsating circle that you see on maps for your current location. And what I want is move that ball around making it stays always below the user's finger. So it should never be possible to see the center of the ball when the user pans. – Rodrigo Ruiz Jan 10 '13 at 19:37
  • The pulsing animation, while not a burden in standard operations, will have a material impact on dragging the whole thing. Try pausing the pulsing and see if the behavior improves. Even simple things like non-rasterized shadows can be surprisingly problematic. You need to simplify things (at least during the pan). Also see an example of rasterizing the view (but also pause the pulsing animation, too). – Rob Jan 10 '13 at 19:41
  • Thank you, as soon as I get home I'll try to remove the animation from the circle when panning. Btw, is there another way to stop the animation without removing it from the view? – Rodrigo Ruiz Jan 10 '13 at 19:44
  • I've updated my answer with animation suspension. – Rob Jan 11 '13 at 13:49

2 Answers2

6

When I first answered, I was presuming that there was something complicated about the view being dragged (e.g. a large CALayer shadow, which can be computationally expensive). Hence my original answer below. But in a subsequent exchange of comments, it has been discovered that one of the buttons had an pulsing animation, which is more likely to be the problem. The animation should be suspended during the dragging of the view if the dragging performance is not acceptable. Thus:

#import <QuartzCore/QuartzCore.h>

- (IBAction)pan:(UIPanGestureRecognizer *)gesture
{
    static CGPoint originalCenter;

    if (gesture.state == UIGestureRecognizerStateBegan)
    {
        originalCenter = gesture.view.center;
        [self pauseLayer:gesture.view.layer];
    }
    else if (gesture.state == UIGestureRecognizerStateChanged)
    {
        CGPoint translate = [gesture translationInView:gesture.view.superview];
        gesture.view.center = CGPointMake(originalCenter.x + translate.x, originalCenter.y + translate.y);
    }
    else if (gesture.state == UIGestureRecognizerStateEnded ||
             gesture.state == UIGestureRecognizerStateFailed ||
             gesture.state == UIGestureRecognizerStateCancelled)
    {
        [self resumeLayer:gesture.view.layer];
    }
}

-(void)pauseLayer:(CALayer*)layer
{
    CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
    layer.speed = 0.0;
    layer.timeOffset = pausedTime;
}

-(void)resumeLayer:(CALayer*)layer
{
    CFTimeInterval pausedTime = [layer timeOffset];
    layer.speed = 1.0;
    layer.timeOffset = 0.0;
    layer.beginTime = 0.0;
    CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
    layer.beginTime = timeSincePause;
}

Clearly, this can be combined with the rasterization of the image, discussed below, but I would try suspending the animation, like above, first.


Original answer:

If your view being dragged is at all complicated, you could try rasterizing it.

  1. Add QuartzCore.framework to your target's "Link binary with libraries" settings.

  2. And then adjust the source to rasterize accordingly.

Such as:

#import <QuartzCore/QuartzCore.h>

- (IBAction)pan:(UIPanGestureRecognizer *)gesture
{
    static CGPoint originalCenter;

    if (gesture.state == UIGestureRecognizerStateBegan)
    {
        originalCenter = gesture.view.center;
        gesture.view.layer.shouldRasterize = YES;
    }
    if (gesture.state == UIGestureRecognizerStateChanged)
    {
        CGPoint translate = [gesture translationInView:gesture.view.superview];
        gesture.view.center = CGPointMake(originalCenter.x + translate.x, originalCenter.y + translate.y);
    }
    if (gesture.state == UIGestureRecognizerStateEnded ||
        gesture.state == UIGestureRecognizerStateFailed ||
        gesture.state == UIGestureRecognizerStateCancelled)
    {
        gesture.view.layer.shouldRasterize = NO;
    }
}

As an aside, I'd suggest saving the originalCenter like this code does, so that if you grab the view being dragged a little off center, it won't jump jarringly on you.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • I removed the animation and used rasterization like you said, but still there is a delay. – Rodrigo Ruiz Jan 11 '13 at 17:23
  • @RodrigoRuiz Two things: First, try temporarily getting rid the of the image altogether and seeing what the performance is. That establishes a baseline. (Set a background color so you can see the view moving around, if you can't otherwise see it.) You'll see a little lagginess, but it should be reasonable. Second, assuming that solves it, then look at the image itself? Is is a large image that is being resized or rescaled? Or is it a nice small image that matches the `UIImageView`/`UIButton` perfectly? Is it set to scale or just center? – Rob Jan 11 '13 at 17:32
  • I did try to use only a simple UIView with rounded corners and still I have a not acceptable delay. – Rodrigo Ruiz Jan 14 '13 at 04:13
  • @RodrigoRuiz I've been playing around with this and I always see a little lag. I even tried changing the `position` property of the `layer` and [turned off implicit animations](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreAnimation_guide/Articles/Transactions.html#//apple_ref/doc/uid/TP40006096-SW6), and I still saw the delay. Clearly using `bringSubviewToFront` and ensuring that you have no transparency helps, but I still see the same modest delay I see in all of the built in apps. You say Maps is good, but I see the same delay in my app as I see there. – Rob Jan 16 '13 at 05:53
  • Ya, I guess this is a hardware problem =/ Not much we can do... And no, I didn't say Maps was good, I said my app had the same bad delay as Maps has. – Rodrigo Ruiz Jan 16 '13 at 18:29
  • 1
    @RodrigoRuiz Ah, sorry, I misunderstood you re maps. The only other thing that I can imagine is that you might be able to do some slight of hand, dragging the object a little ahead of the direction that you're headed (e.g. factoring in not only `translation`, but `velocity`, too), but playing around with that, it's promising, but hard to make it smooth. (Sigh.) – Rob Jan 16 '13 at 22:57
1

Try disabling animations when you set the new value for the center point.

- (IBAction)pan:(UIPanGestureRecognizer *)sender 
{
    CGPoint center = [sender locationInView:sender.view];
    [CATransaction begin];
    [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
    self.myView.center = center; 
    [CATransaction commit];
}

If that helps you could try to smooth it with faster animation.

You could also try to disable the animations as described here: Explicitly disabling UIView animation in iOS4+

Shortly:

- (IBAction)pan:(UIPanGestureRecognizer *)sender 
{
    CGPoint center = [sender locationInView:sender.view];
    [UIView setAnimationsEnabled:NO];
    self.myView.center = center; 
    [UIView setAnimationsEnabled:YES];
}

Once you manage to disable all animations during the repositioning, it's up to the speed of the hardware and your code.

Community
  • 1
  • 1
Matti Jokipii
  • 561
  • 1
  • 6
  • 20
  • The code above had no effect, I think.. But I did remove the animation completely and still had the same problem, the center of the view doesn't follow my finger. My cousin tried on his phone too and he said it got better (less delay), but still, if you move your finger a little faster, you can still see the center of the view. – Rodrigo Ruiz Jan 11 '13 at 17:20
  • Oh, you should notice when you have the animation disabled. The animated view will move in steps instead of sliding smoothly. If non-animated view will still respond too slowly, you will most likely have to make your code faster. Please see the updated answer. – Matti Jokipii Jan 12 '13 at 09:09
  • Just to clarify, i'm talking about disabling the implicit motion animation that appears when repositioning a view. Once you disable that the view will always go instantly to the last touch position. Well not instantly but as fast as the events and redraws are processed. If thats not fast enough, which may be the case, you could try to predict the future touch positions and move the view slightly ahead of time. But that could get complicated, and it's at the best a tradeoff between being slightly wrong all of the time and being right most of the time (but more wrong when your not right). – Matti Jokipii Jan 12 '13 at 09:42