Search code examples
iosobjective-crotationtranslationcgaffinetransform

Translating a UIView after rotating


I'm trying to translate a UIView that has been either rotated and/or scaled using touches from the user. I try to translate it with user input as well:

- (void)handleObjectMove:(UIPanGestureRecognizer *)recognizer
{
    static CGPoint lastPoint;
    UIView *moveView = [recognizer view];
    CGPoint newCoord = [recognizer locationInView:playArea];

    // Check if this is the first touch
    if( [recognizer state]==UIGestureRecognizerStateBegan )
    {
        // Store the initial touch so when we change positions we do not snap
        lastPoint = newCoord;
    }

    // Create the frame offsets to use our finger position in the view.
    float dX = newCoord.x;
    float dY = newCoord.y;

    dX-=lastPoint.x;
    dY-=lastPoint.y;

    // Figure out the translation based on how we are scaled
    CGAffineTransform transform = [moveView transform];
    CGFloat xScale = transform.a;
    CGFloat yScale = transform.d;

    dX/=xScale;
    dY/=yScale;

    lastPoint = newCoord;

    [moveView setTransform:CGAffineTransformTranslate( transform, dX, dY )];

    [recognizer setTranslation:CGPointZero inView:playArea];
}

But when I touch and move the view it gets translated in all different weird ways. Can I apply some sort of formula using the rotation values to translate properly?


Solution

  • The best solution I've found with having to use the least amount of math was to store the original translation, rotation, and scaling values separately and redo the transform when they were changed. My solution was to subclass a UIView with the following properties:

    @property (nonatomic) CGPoint translation;
    @property (nonatomic) CGFloat rotation;
    @property (nonatomic) CGPoint scaling;
    

    And the following functions:

    - (void)rotationDelta:(CGFloat)delta
    {
        [self setRotation:[self rotation]+delta];
    }
    
    - (void)scalingDelta:(CGPoint)delta
    {
        [self setScaling:
         (CGPoint){ [self scaling].x*delta.x, [self scaling].y*delta.y }];
    }
    
    - (void)translationDelta:(CGPoint)delta
    {
        [self setTranslation:
         (CGPoint){ [self translation].x+delta.x, [self translation].y+delta.y }];
    }
    
    - (void)transformMe
    {
        // Start with the translation
        CGAffineTransform transform = CGAffineTransformMakeTranslation( [self translation].x, [self translation].y );
        // Apply scaling
        transform = CGAffineTransformScale( transform, [self scaling].x, [self scaling].y );
        // Apply rotation
        transform = CGAffineTransformRotate( transform, [self rotation] );
    
        [self setTransform:transform];
    }
    
    - (void)setScaling:(CGPoint)newScaling
    {
        scaling = newScaling;
        [self transformMe];
    }
    
    - (void)setRotation:(CGFloat)newRotation
    {
        rotation = newRotation;
        [self transformMe];
    }
    
    - (void)setTranslation:(CGPoint)newTranslation
    {
        translation = newTranslation;
        [self transformMe];
    }
    

    And to use the following in the handlers:

    - (void)handleObjectPinch:(UIPinchGestureRecognizer *)recognizer
    {
        if( [recognizer state] == UIGestureRecognizerStateEnded
            || [recognizer state] == UIGestureRecognizerStateChanged )
        {
            // Get my stuff
            if( !selectedView )
                return;
    
            SelectableImageView *view = selectedView;
    
            CGFloat scaleDelta = [recognizer scale];
            [view scalingDelta:(CGPoint){ scaleDelta, scaleDelta }];
    
            [recognizer setScale:1.0];
        }
    }
    
    - (void)handleObjectMove:(UIPanGestureRecognizer *)recognizer
    {
        static CGPoint lastPoint;
        SelectableImageView *moveView = (SelectableImageView *)[recognizer view];
        CGPoint newCoord = [recognizer locationInView:playArea];
    
        // Check if this is the first touch
        if( [recognizer state]==UIGestureRecognizerStateBegan )
        {
            // Store the initial touch so when we change positions we do not snap
            lastPoint = newCoord;
        }
    
        // Create the frame offsets to use our finger position in the view.
        float dX = newCoord.x;
        float dY = newCoord.y;
    
        dX-=lastPoint.x;
        dY-=lastPoint.y;
        lastPoint = newCoord;
    
        [moveView translationDelta:(CGPoint){ dX, dY }];
    
        [recognizer setTranslation:CGPointZero inView:playArea];
    }
    
    - (void)handleRotation:(UIRotationGestureRecognizer *)recognizer
    {
        if( [recognizer state] == UIGestureRecognizerStateEnded
           || [recognizer state] == UIGestureRecognizerStateChanged )
        {
            if( !selectedView )
                return;
    
            SelectableImageView *view = selectedView;
    
            CGFloat rotation = [recognizer rotation];
            [view rotationDelta:rotation];
    
            [recognizer setRotation:0.0];
        }
    }