cowfaboo
cowfaboo

Reputation: 709

How to animate CGAffineTransform smoothly as drag gesture is performed

I understand the various ways of detecting drag gestures just fine (currently I'm using a UIPanGestureRecognizer), but my understanding of transformations is limited, so I'm not sure if/how this is possible. Essentially, what I want to have is a scaling transformation applied to a UIView at the same pace (for lack of a better word) as the user performs a drag gesture elsewhere on the screen. In other words, as the user drags up, I want the size of my transforming UIView to increase proportionally to the position of that gesture, and if the user then starts dragging down, it should start to shrink proportionally.

Hopefully that makes sense. As a dummy example, just imagine a slider that you can adjust to change the size of a UIView in real time. Is there a good way to make those sorts of incremental and constant size updates with CGAffineTransform?

Upvotes: 1

Views: 2287

Answers (2)

Tom
Tom

Reputation: 696

I found the best approach is to use locationInView so that you can equate a location offset in pixels with a scale. For example, if the circle is placed in the centre of the view:

func dragDot(recognizer: UIPanGestureRecognizer) {

    let locationX = recognizer.location(in: self.view).x

    // Expand and contract the circle
    let locationXOffset = locationX - self.view.center.x

    // We need scale to be 1 when locationXOffset = circle radius
    let scale: CGFloat = locationXOffset / (self.widthOfCircle / 2)

    self.ring.transform = CGAffineTransform(scaleX: scale, y: scale)
}

If the circle isn't in the centre of the view then replace self.view.center.x with the initial location of the circle.

This method will work across all devices and screen resolutions and will avoid the requirement to calibrate a constant

Upvotes: 0

Rob
Rob

Reputation: 437452

In your pan gesture handler, you simply grab translationInView or locationInView, calculate a scale from that, and then update the transform accordingly. For example:

- (void)handlePan:(UIPanGestureRecognizer *)gesture
{
    static CGAffineTransform originalTransform;

    if (gesture.state == UIGestureRecognizerStateBegan)
    {
        originalTransform = self.viewToScale.transform;
    }
    else if (gesture.state == UIGestureRecognizerStateChanged)
    {
        CGPoint translation = [gesture translationInView:gesture.view];
        CGFloat scale = 1.0 - translation.y / 160.0;
        self.viewToScale.transform = CGAffineTransformScale(originalTransform, scale, scale);
    }
}

You can play around with the scale calculation depending upon precisely what you want to do, but hopefully you get the idea.

Personally, I'd rather use the pinch gesture recognizer for resizing (it's a UI that users have been trained on, it gives you the scale factor right out of the box, etc.), but whatever works for you. If you did a pinch gesture recognizer, it might look like:

- (void)handlePinch:(UIPinchGestureRecognizer *)gesture
{
    static CGAffineTransform originalTransform;

    if (gesture.state == UIGestureRecognizerStateBegan)
    {
        originalTransform = self.viewToScale.transform;
    }
    else if (gesture.state == UIGestureRecognizerStateChanged)
    {
        self.viewToScale.transform = CGAffineTransformScale(originalTransform, gesture.scale, gesture.scale);
    }
}

Upvotes: 3

Related Questions