Rotating CALayer of NSImageView 90 degrees - macos

I am trying to rotate the CALayer of an NSImageView. My problem is that the anchorPoint appears to be (0.0, 0.0) by default but I want it to be the center of the image (Apple's documentation indicates the default should be the center (0.5, 0.5), but it's not on my system OS X 10.7), When I modify the anchor point the origin of the center of the image shifts to the lower left corner.
Here's the code I am using to rotate the layer:
CALayer *myLayer = [imageView layer];
CGFloat myRotationAngle = -M_PI_2;
NSNumber *rotationAtStart = [myLayer valueForKeyPath:#"transform.rotation"];
CATransform3D myRotationTransform = CATransform3DRotate(myLayer.transform, myRotationAngle, 0.0, 0.0, 1.0);
myLayer.transform = myRotationTransform;
CABasicAnimation *myAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
myAnimation.duration = 1.0;
myAnimation.fromValue = rotationAtStart;
myAnimation.toValue = [NSNumber numberWithFloat:([rotationAtStart floatValue] + myRotationAngle)];
[myLayer addAnimation:myAnimation forKey:#"transform.rotation"];
myLayer.anchorPoint = CGPointMake(0.5, 0.5);
[myLayer addAnimation:myAnimation forKey:#"transform.rotation"];

It appears that changing the anchor point of something also changes it's position. The following snippet fixes the problem:
CGPoint anchorPoint = CGPointMake(0.5, 0.5);
CGPoint newPoint = CGPointMake(disclosureTriangle.bounds.size.width * anchorPoint.x,
disclosureTriangle.bounds.size.height * anchorPoint.y);
CGPoint oldPoint = CGPointMake(disclosureTriangle.bounds.size.width * disclosureTriangle.layer.anchorPoint.x,
disclosureTriangle.bounds.size.height * disclosureTriangle.layer.anchorPoint.y);
newPoint = CGPointApplyAffineTransform(newPoint,
[disclosureTriangle.layer affineTransform]);
oldPoint = CGPointApplyAffineTransform(oldPoint,
[disclosureTriangle.layer affineTransform]);
CGPoint position = disclosureTriangle.layer.position;
position.x -= oldPoint.x;
position.x += newPoint.x;
position.y -= oldPoint.y;
position.y += newPoint.y;
disclosureTriangle.layer.position = position;
disclosureTriangle.layer.anchorPoint = anchorPoint;

Related

How do I scale a NSView from the center using Facebook Pop animation?

Please note, this is a Mac OS X (NSView) related question.
I'm trying to use Facebook's POP animation to scale a NSView from the center (to 75% its size) and then back to 100%, however I can't get it to work. Given kPOPLayerScaleXY doesn't seem to work, I did the following but this gives me incorrect results (as it seems to scale down from the top left, and when zoomIn is false, it goes too large:
CGRect baseRect = CGRectMake(0, 0, 30, 24);
CGFloat scale = (zoomIn) ? 0.75 : 1.0;
CGFloat x = baseRect.origin.x;
CGFloat y = baseRect.origin.y;
CGFloat width = baseRect.size.width;
CGFloat height = baseRect.size.height;
if (zoomIn) {
width -= floorf((1.0 - scale) * width);
height -= floorf((1.0 - scale) * height);
x += floorf((width * (1.0f - scale)) / 2);
y += floorf((height * (1.0f - scale)) / 2);
}
CGRect scaleRect = CGRectMake(x, y, width, height);
[myView.layer pop_removeAllAnimations];
POPSpringAnimation *animation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerBounds];
animation.springBounciness = 8;
animation.toValue = [NSValue valueWithCGRect: scaleRect];
[myView.layer pop_addAnimation:animation forKey:#"zoom"];
I got it working finally by using two animations instead:
CGRect baseRect = CGRectMake(0, 0, 30, 24);
CGFloat scale = (zoomIn) ? 0.80 : 1.0;
CGFloat x = baseRect.origin.x;
CGFloat y = baseRect.origin.y;
if (zoomIn) {
x = floorf((baseRect.size.width * (1.0 - scale)) / 2);
y = floorf((baseRect.size.height * (1.0 - scale)) / 2);
}
[myView.layer pop_removeAllAnimations];
POPSpringAnimation *animation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerScaleXY];
animation.springBounciness = 8;
animation.toValue = [NSValue valueWithCGSize:CGSizeMake(scale, scale)];
[myView.layer pop_addAnimation:animation forKey:#"zoom"];
animation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerTranslationXY];
animation.springBounciness = 8;
animation.toValue = [NSValue valueWithCGPoint:CGPointMake(x, y)];
[myView.layer pop_addAnimation:animation forKey:#"translate"];

Scene Kit Animation

Anyone have any idea why I can't get this simple animation to work? It just doesn't play.
self.scene = [SCNScene scene];
SCNNode *sun = [SCNNode nodeWithGeometry:[SCNSphere sphereWithRadius:5.0]];
SCNNode *moon = [SCNNode nodeWithGeometry:[SCNSphere sphereWithRadius:2.0]];
moon.position = SCNVector3Make(7.0, 7.0, 7.0);
moon.pivot = CATransform3DMakeTranslation(-7.0, -7.0, -7.0);
CABasicAnimation *startAnim = [CABasicAnimation animationWithKeyPath:#"rotation"];
startAnim.duration = 5;
startAnim.repeatCount = MAXFLOAT;
startAnim.toValue =[NSValue valueWithSCNVector4:SCNVector4Make(0.0, 0.0, 0.0,1.0)];
startAnim.timingFunction =[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[moon addAnimation:startAnim forKey:#"rotate"];
[sun addChildNode:moon];
[[self.scene rootNode] addChildNode:sun];
I think the problem is...
startAnim.toValue =[NSValue valueWithSCNVector4:SCNVector4Make(0.0, 0.0, 0.0,1.0)];
Cheers
Indeed SCNVector4Make(0.0, 0.0, 0.0,1.0) is not a valid rotation.
"rotation" is an axis angle made with 4 floats {x y z w}.
the 3 first floats x y z represents the axis, w is the rotation in radian.
So (0.0, 0.0, 0.0) is not a valid axis (or in the best case it represents a null rotation)
I couldn't get your example working with the "rotation" keypath either, but I could using "transform". For example:
CABasicAnimation *startAnim = [CABasicAnimation animationWithKeyPath:#"transform"];
startAnim.duration = 5;
startAnim.repeatCount = MAXFLOAT;
startAnim.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(3.14, 0.0, 1.0, 0.0)];
startAnim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[moon addAnimation:startAnim forKey:#"transform"];

Xcode : Need help moving an image inside screen bounds

I followed a guide about dragging multiple images on screen with gesture recognizers however i have a problem. I want the images to be dragged inside the bounds of the screen which maybe iPad or iPhone. The code i am using estimates the image's center so half of the image is getting off screen when reaching the bounds. Could you please help me with this?
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer {
CGPoint translation = [recognizer translationInView:self.view];
recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x,
recognizer.view.center.y + translation.y);
[recognizer setTranslation:CGPointMake(0, 0) inView:self.view];
if (recognizer.state == UIGestureRecognizerStateEnded) {
CGPoint velocity = [recognizer velocityInView:self.view];
CGFloat magnitude = sqrtf((velocity.x * velocity.x) + (velocity.y * velocity.y));
CGFloat slideMult = magnitude / 200; //original divider 200
NSLog(#"magnitude: %f, slideMult: %f", magnitude, slideMult);
float slideFactor = 0.1 * slideMult /4; // Increase for more of a slide (original doesn't have a divider)
CGPoint finalPoint = CGPointMake(recognizer.view.center.x + (velocity.x * slideFactor),
recognizer.view.center.y + (velocity.y * slideFactor));
finalPoint.x = MIN(MAX(finalPoint.x, 0), self.view.bounds.size.width);
finalPoint.y = MIN(MAX(finalPoint.y, 0), self.view.bounds.size.height);
[UIView animateWithDuration:slideFactor*2 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
recognizer.view.center = finalPoint;
} completion:nil];
}
}
Well, i managed to find out the solution to my problem. I divided the image width and height by two. In my case the results were 100 and 70. The rest can be seen in code box :
finalPoint.x = MIN(MAX(finalPoint.x, 70), self.view.bounds.size.width-70);
finalPoint.y = MIN(MAX(finalPoint.y, 100), self.view.bounds.size.height-100);

Creating a UIImage from a rotated UIImageView

I have a UIImageView with an image in it. I have rotated the image prior to display by setting the transform property of the UIImageView to CGAffineTransformMakeRotation(angle) where angle is the angle in radians.
I want to be able to create another UIImage that corresponds to the rotated version that I can see in my view.
I am almost there, by rotating the image context I get a rotated image:
- (UIImage *) rotatedImageFromImageView: (UIImageView *) imageView
{
UIImage *rotatedImage;
// Get image width, height of the bounding rectangle
CGRect boundingRect = [self getBoundingRectAfterRotation: imageView.bounds byAngle:angle];
// Create a graphics context the size of the bounding rectangle
UIGraphicsBeginImageContext(boundingRect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
// Rotate and translate the context
CGAffineTransform ourTransform = CGAffineTransformIdentity;
ourTransform = CGAffineTransformConcat(ourTransform, CGAffineTransformMakeRotation(angle));
CGContextConcatCTM(context, ourTransform);
// Draw the image into the context
CGContextDrawImage(context, CGRectMake(0, 0, imageView.image.size.width, imageView.image.size.height), imageView.image.CGImage);
// Get an image from the context
rotatedImage = [UIImage imageWithCGImage: CGBitmapContextCreateImage(context)];
// Clean up
UIGraphicsEndImageContext();
return rotatedImage;
}
However the image is not rotated about its centre. I have tried all kinds of transforms concatenated with my rotate to get it to rotate around the centre but to no avail. Am I missing a trick? Is this even possible since I am rotating the context not the image?
Getting desperate to make this work now, so any help would be appreciated.
Dave
EDIT: I've been asked several times for my boundingRect code, so here it is:
- (CGRect) getBoundingRectAfterRotation: (CGRect) rectangle byAngle: (CGFloat) angleOfRotation {
// Calculate the width and height of the bounding rectangle using basic trig
CGFloat newWidth = rectangle.size.width * fabs(cosf(angleOfRotation)) + rectangle.size.height * fabs(sinf(angleOfRotation));
CGFloat newHeight = rectangle.size.height * fabs(cosf(angleOfRotation)) + rectangle.size.width * fabs(sinf(angleOfRotation));
// Calculate the position of the origin
CGFloat newX = rectangle.origin.x + ((rectangle.size.width - newWidth) / 2);
CGFloat newY = rectangle.origin.y + ((rectangle.size.height - newHeight) / 2);
// Return the rectangle
return CGRectMake(newX, newY, newWidth, newHeight);
}
OK - at last I seem to have done it. Any comments on the correctness would be useful... needed a translate, a rotate, a scale and an offset from the drawing rect position to make it work. Code is here:
CGAffineTransform transform = CGAffineTransformIdentity;
transform = CGAffineTransformTranslate(transform, boundingRect.size.width/2, boundingRect.size.height/2);
transform = CGAffineTransformRotate(transform, angle);
transform = CGAffineTransformScale(transform, 1.0, -1.0);
CGContextConcatCTM(context, transform);
// Draw the image into the context
CGContextDrawImage(context, CGRectMake(-imageView.image.size.width/2, -imageView.image.size.height/2, imageView.image.size.width, imageView.image.size.height), imageView.image.CGImage);
// Get an image from the context
rotatedImage = [UIImage imageWithCGImage: CGBitmapContextCreateImage(context)];

question about CGAffineTransformTranslate

CGRect rect1 = backgroundImageView.frame;
NSLog(#"%f,%f,%f,%f",rect1.origin.x,rect1.origin.y,
rect1.size.width,rect1.size.height);
angle = -90.0;
moveX = 0;
moveY = 0.0;
CGFloat degreesToRadians = M_PI * angle / 180.0;
CGAffineTransform landscapeTransform =
CGAffineTransformMakeRotation(degreesToRadians);
landscapeTransform =
CGAffineTransformTranslate(landscapeTransform, moveX, moveY);
[backgroundImageView setTransform:landscapeTransform];
rect1 = backgroundImageView.frame;
NSLog(#"%f,%f,%f,%f",rect1.origin.x,rect1.origin.y,
rect1.size.width,rect1.size.height);
the debug message output:
0.000000,0.000000,320.000000,480.000000
-80.000000,80.000000,480.000000,320.000000
why does the (x,y) changes to (-80,80)?
When you set the transform, the backgroundImageView.center does not change. If the parent view has already rotated, yout can do something like this after applying the transform.
backgroundImageView.center = backgroundImageView.superview.center;
Either way, for your purposes you should adjust the center and not translate the transform.

Resources