Question

Goal

I am trying to replicate the UI Dynamics pull animation/behaviour. I have setup a UIPanGestureRecognizer on my UIView for dragging it around. When drag occurs, I set the touch point as UIView.layer.anchorPoint to allow rotation around the touch point. During the drag, I check the touch velocity direction and apply CGAffineTransformRotate either clockwise or anticlockwise. Here's what I am talking about.

enter image description here

Issue

The animation does not happen smoothly, the UIView flickers(multiple traces of view appear in short instances) when dragging and applying the rotation. The video recording is 30fps hence, flickering is not visible clearly. I hope my explanation is making it clear.

Source code in my view controller is as below. I am trying this on iOS5.0+

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    UIView *animationView = [[UIView alloc] initWithFrame:CGRectMake(50, 50, 100, 100)];
    animationView.layer.backgroundColor = [UIColor redColor].CGColor;
    animationView.layer.shouldRasterize = YES; // Does not help :(
    animationView.autoresizingMask = UIViewAutoresizingNone; // Does not help :(

    [self.view addSubview:animationView];

    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
    [animationView addGestureRecognizer:pan];
    [animationView release];
    [pan release];
}

- (void) pan:(UIPanGestureRecognizer*) gesture {

    if(gesture.state==UIGestureRecognizerStateBegan) {
        CGPoint anchor = [gesture locationInView:gesture.view];

        gesture.view.layer.anchorPoint = CGPointMake(anchor.x/gesture.view.bounds.size.width,
                                                     anchor.y/gesture.view.bounds.size.height);

        gesture.view.center = CGPointMake(anchor.x/gesture.view.bounds.size.width,
                                          anchor.y/gesture.view.bounds.size.height);
    }
    else if(gesture.state==UIGestureRecognizerStateChanged) {
        CGPoint point1 = [gesture locationInView:self.view];
        gesture.view.center=point1;

        CGPoint velocity = [gesture velocityInView:gesture.view];
        if(velocity.x > 0)
        {
            // Dragged right
            if(velocity.y > 0) {
                // Dragged down
                // counter clock
                CGFloat angle = -0.2;
                gesture.view.transform = CGAffineTransformRotate(gesture.view.transform, angle);
            }
            else {
                // Dragged up
                // clock
                CGFloat angle = 0.2;
                gesture.view.transform = CGAffineTransformRotate(gesture.view.transform, angle);
            }
        }
        else
        {
            // Dragged left
            if(velocity.y > 0) {
                // Dragged down
                // clock
                CGFloat angle = 0.2;
                gesture.view.transform = CGAffineTransformRotate(gesture.view.transform, angle);
            }
            else {
                // Dragged up
                // counter clock
                CGFloat angle = -0.2;
                gesture.view.transform = CGAffineTransformRotate(gesture.view.transform, angle);
            }
        }
    }
}

I have tried applying CATransform3DMakeRotation (angle, 0.0f, 0.0f, 1.0f) to layer instead of view CGAffineTransformRotate. But the flickering still happens. Any ideas why this issue? I welcome advice on any other approach to achieve the same result.

Was it helpful?

Solution

After some serious thought in solving this issue, I realised that it was a trivial mistake. The flicker was caused because I am making the rotation for even slightest change in drag direction. To solve this, I assumed an arbitrary margin of pixel-change before I make rotation.

The modified code that solved the issue is as below

- (void) pan:(UIPanGestureRecognizer*) gesture {

    if(gesture.state==UIGestureRecognizerStateBegan) {
        CGPoint anchor = [gesture locationInView:gesture.view];
        gesture.view.layer.anchorPoint = CGPointMake(anchor.x/gesture.view.bounds.size.width,
                                                     anchor.y/gesture.view.bounds.size.height);
    }
    else if(gesture.state==UIGestureRecognizerStateChanged) {
        CGPoint point1 = [gesture locationInView:self.view];
        gesture.view.layer.position=point1;

        CGPoint velocity = [gesture velocityInView:gesture.view];
        CGFloat angle = 0.0;
        if(velocity.x > self.view.bounds.size.width*0.15)
        {
            // Dragged right
            if(velocity.y > self.view.bounds.size.height*0.02) {
                // Dragged down
                // counter clock
                angle = -0.05;
            }
            else if (velocity.y < -self.view.bounds.size.height*0.02) {
                // Dragged up
                // clock
                angle = 0.05;
            }
        }
        else if (velocity.x < -self.view.bounds.size.width*0.15)
        {
            // Dragged left
            if(velocity.y > self.view.bounds.size.height*0.02) {
                // Dragged down
                // clock
                angle = 0.05;
            }
            else if (velocity.y < -self.view.bounds.size.height*0.02) {
                // Dragged up
                // counter clock
                angle = -0.05;
            }
        }
        [self applyRotationToView:gesture.view byAngle:angle];
    }
}

- (void) applyRotationToView:(UIView*)rotationView byAngle:(CGFloat)angle {
    rotationView.transform = CGAffineTransformRotate(rotationView.transform, angle);
}

Animations are still not as smooth as one would expect, however I have managed to reduce the annoying flicker.

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