Search code examples
objective-crotationscalecalayerdrag

How to drag a CALayer that is rotated and scaled?


I have an image in a CALayer (using the CALayers contents property). A CALayer has a transform property that can be use to scale and rotate. I can use that transform without difficulty when the CALayer is still.

But when I try to drag the CALayer (using mouseDragged:(NSEvent *)theEvent), then it all fells apart (the image gets flattened out as I drag).

-(void)mouseDragged:(NSEvent *)theEvent {
    CGPoint loc = [self convertPoint:theEvent.locationInWindow fromView:nil];
    CGPoint deltaLoc = ccpSub(loc, downLoc); // subtract two points
    CGPoint newPos = ccpAdd(startPos, deltaLoc); // adds two points
    [layer changePosition:newPos];
    ...
}

In the layer

-(void)changePosition:(CGPoint)newPos {
    //prevent unintended animation actions
    self.actions = @{@"position": [NSNull null]};

    CGSize size = self.frame.size;
    CGRect newRect = CGRectMake(newPos.x, newPos.y, size.width, size.height);
    self.frame = newRect;
}

The problem is that I use CALayer's frame to dynamically change the location of the CALayer as I drag, but when the rotation angle is not zero, then the frame is not longer parallel to x and y axis. What is the best way to fix this problem?


Solution

  • One way to do it is to regenerate transforms for each incremental movement.

    -(void)mouseDragged:(CGPoint)deltaLoc start:(CGPoint)startPos {
        CGPoint newPos = ccpAdd(startPos, deltaLoc);
        [self changePosition:newPos];
    }
    
    -(void)changePosition:(CGPoint)newPos {
        //prevent unintended animation actions
        self.actions = @{@"position": [NSNull null]};
    
        double angle = [[self valueForKeyPath: @"transform.rotation"] doubleValue];
        double scaleX = [[self valueForKeyPath: @"transform.scale.x"] doubleValue];
        double scaleY = [[self valueForKeyPath: @"transform.scale.y"] doubleValue];
    
        self.transform = CATransform3DIdentity;
    
        CGSize size = self.frame.size;
        CGRect newRect = CGRectMake(newPos.x, newPos.y, size.width, size.height);
        self.frame = newRect;
    
        [self applyTransformRotation:angle scale:ccp(scaleX, scaleY)];
    }
    
    -(void)applyTransformRotation:(double)angle scale:(CGPoint)scale {
        self.transform = CATransform3DMakeRotation(angle, 0, 0, 1);
        self.transform = CATransform3DScale(self.transform, scale.x, scale.y, 1);
    }
    

    I do not know if this is the most efficient way to do it, but it definitely works.