How to set the initial orientation of an animated UIImageView? - iphone

I am programming a gauge display for an iphone application: a centered needle UIImageView against a fixed background UIImageView. I am new to iPhone programming, and don't know much about CoreAnimation, but with looking through examples and fiddling with my anchorPoint setting and the position of the needle image against the background in InterfaceBuilder, my needle is coming up with my desired rotation point properly centered against the background:
self.needleIV.layer.anchorPoint = CGPointMake( 0.5, 0.68 );
My test animations work perfectly (the needle remains properly centered in the gauge, and rotates around my desired rotation point, the anchor point), using this code:
CABasicAnimation *rotateAnimation = [CABasicAnimation animation];
rotateAnimation.keyPath = #"transform.rotation.z";
rotateAnimation.fromValue = [NSNumber numberWithFloat:DegreesToRadians( (rand() % 270 - 135) )];
rotateAnimation.toValue = [NSNumber numberWithFloat:DegreesToRadians( (rand() % 270 - 135) )];
rotateAnimation.duration = 4.0;
rotateAnimation.removedOnCompletion = NO;
// leaves presentation layer in final state; preventing snap-back to original state
rotateAnimation.fillMode = kCAFillModeBoth;
rotateAnimation.repeatCount = 0;
rotateAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
// Add the animation to the selection layer. This causes it to begin animating.
[self.needleIV.layer addAnimation:rotateAnimation forKey:#"rotateAnimation"];
The problem is that at application launch the needle comes up initially pointing straight up (which is how the needle looks in the image file), and I can't figure out how to give it an initial rotation value which doesn't mess up the animation code. (By "mess up" I mean the needle doesn't rotate on the correct axis, and becomes uncentered in the gauge).
I've tried setting layer.transform to a 3d rotation matrix rotated around the z axis, but this moves the needle off center. I've tried this code as well:
[self.needleIV.layer setValue:[NSNumber numberWithFloat:DegreesToRadians(-135.0)] forKeyPath:#"transform.rotation.z"];
which didn't work either (needle moved off anchorPoint center), and I had high hopes since it's apparently using the same transform to the working animation code.
I also tried just putting the working animation snippet in my view controller's viewDidLoad function to "animate" the needle to it's start location, but it doesn't do anything at all -- apparently the animation doesn't run until the image is actually up and on the screen, and I've confirmed in the debugger that the screen is still black in viewDidLoad.
At this point I'm about to try just hacking in a timer to run an initial animation "long enough" after the app starts to set an animation that rotates the needle properly, but I'd really like to understand what's going wrong here, and why the animation is apparently rotating around a different axis than is my other attempts at pre-rotation/pre-animation.

The anchorPoint is relative to the bounds of the parent. Is it possible the bounds of the parent layer/view change after viewDidLoad?
You should be able to do all this using the UIView center and transform properties, which are 2D so implicitly rotate about the z axis.

Related

Rotating UIImageView Moves the Image Off Screen

I have a simple rotation gesture implemented in my code, but the problem is when I rotate the image it goes off the screen/out of the view always to the right.
The image view that is being rotated center X gets off or increases (hence it going right off the screen out of the view).
I would like it to rotate around the current center, but it's changing for some reason. Any ideas what is causing this?
Code Below:
- (void)viewDidLoad
{
[super viewDidLoad];
CALayer *l = [self.viewCase layer];
[l setMasksToBounds:YES];
[l setCornerRadius:30.0];
self.imgUserPhoto.userInteractionEnabled = YES;
[self.imgUserPhoto setClipsToBounds:NO];
UIRotationGestureRecognizer *rotationRecognizer = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:#selector(rotationDetected:)];
[self.view addGestureRecognizer:rotationRecognizer];
rotationRecognizer.delegate = self;
}
- (void)rotationDetected:(UIRotationGestureRecognizer *)rotationRecognizer
{
CGFloat angle = rotationRecognizer.rotation;
self.imageView.transform = CGAffineTransformRotate(self.imageView.transform, angle);
rotationRecognizer.rotation = 0.0;
}
You want to rotate the image around it's center, but that's not what it is actually happening. Rotation transforms take place around the origin. So what you have to do is to apply a translate transform first to map the origin to the center of the image, and then apply the rotation transform, like so:
self.imageView.transform = CGAffineTransformTranslate(self.imageView.transform, self.imageView.bounds.size.width/2, self.imageView.bounds.size.height/2);
Please note that after rotating you'll probably have to undo the translate transform in order to correctly draw the image.
Hope this helps
Edit:
To quickly answer your question, what you have to do to undo the Translate Transform is to subtract the same difference you add to it in the first place, for example:
// The next line will add a translate transform
self.imageView.transform = CGAffineTransformTranslate(self.imageView.transform, 10, 10);
self.imageView.transform = CGAffineTransformRotate(self.imageView.transform, radians);
// The next line will undo the translate transform
self.imageView.transform = CGAffineTransformTranslate(self.imageView.transform, -10, -10);
However, after creating this quick project I realized that when you apply a rotation transform using UIKit (like the way you're apparently doing it) the rotation actually takes place around the center. It is only when using CoreGraphics that the rotation happens around the origin. So now I'm not sure why your image goes off the screen. Anyway, take a look at the project and see if any code there helps you.
Let me know if you have any more questions.
The 'Firefox' image is drawn using UIKit. The blue rect is drawn using CoreGraphics
You aren't rotating the image around its centre. You'll need correct this manually by translating it back to the correct position

Smooth aspect change during orientation change

When the screen of the iPhone orientation changes, I adjust my projection matrix of my 3D rendering to the new aspect value. However, doing this in either willRotateToInterfaceOrientation or didRotateFromInterfaceOrientation would cause the aspect ratio being wrong during the transition, because at the beginning of the animation, the screen size is still the same as before and is changed to the new bounds gradually while being rotated. Therefore I want the aspect value used for my 3D projection matrix to change gradually as well. To achieve this, I retrieve start time and duration for the rotation animation in willAnimateRotationToInterfaceOrientation:
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
_aspect.isChanging = YES;
_aspect.startedChanging = [[NSDate date] retain];
_aspect.changeDuration = duration;
_aspect.oldValue = self.renderer.aspect;
_aspect.currentValue = fabsf(self.view.bounds.size.width / self.view.bounds.size.height);
}
Note that the view bound size is already set to the new value that will be valid after the animation.
Then, in update, I do the following:
- (void)update
{
if (_aspect.isChanging) {
float f = MIN(1, [[NSDate date] timeIntervalSinceDate:_aspect.startedChanging] / _aspect.changeDuration);
self.renderer.aspect = _aspect.oldValue * (1-f) + _aspect.currentValue * f;
} else {
self.renderer.aspect = _aspect.currentValue;
}
[self.renderer update];
}
This already works quite well, but the render aspect change does not match the actual aspect change, which is because I'm interpolating it linearly. Therefore I tried to match the easing of the actual aspect change by throwing math functions at the problem: The best result I could get was by adding the following line:
f = 0.5f - 0.5f*cosf(f*M_PI);
This results in almost no visible stretching of the image during the rotation, however if you look closely, it still seems to be a bit unmatched somewhere in between. I guess, the end user won't notice it, but I'm asking here if there might be a better solution, so these are my questions:
What is the actual easing function used for the change in aspect ratio during the rotation change animation?
Is there a way to get the actual width and height of the view as it is displayed during the orientation change animation? This would allow me to retrieve the in-between aspect directly.
On the first bullet point, you can use CAMediaTimingFunction (probably with kCAMediaTimingFunctionEaseInEaseOut) to get the control points for the curve that defines the transition. Then just use a cubic bezier curve formula.
On the second bullet point, you can use [[view layer] presentationLayer] to get a version of that view's CALayer with all current animations applied as per their current state. So if you check the dimensions of that you should get the then current values — I guess if you act upon a CADisplayLink callback then you'll be at most one frame behind.

Main view rotates from center point to 360 degree but inner views are also being rotated

I am rotating main view with 360 degrees, and I have subviews added inside main view, everything works correctly, but with one issue.
What I want to do is when I rotate main view, inner views should not lost their frames/position. Right now, when I rotate main view with infinte repeat count and dynamically if I add subview inside main view, it goes into proper position, but it does not retain its frame.
For example, I am implementing orbit, and for that, I have used entire transparent view as orbit and orbit is rotated from center point to 360 degree infinite times, and User can add many planets as he wants onto orbit, so when planets added on orbit, planets do not retain its frame. Can you suggest any idea?
Thanks in advance.
Well it sounds like you need to add a rotating animation for every subview that you add in your main view. If the main view rotates clockwise your subviews will need to rotate around their center in a counter-clockwise direction.
I guess you're trying to keep the subviews' orientations while rotating.
If I were you, I'd use CAAnimation instead of using a view to rotate.
You may add the animation to every subview, try this:
CAKeyframeAnimation* animation;
animation = [CAKeyframeAnimation animation];
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL,imgview.layer.position.x,imgview.layer.position.y);
int p = [self getblank:tag];
float f = 2.0*M_PI - 2.0*M_PI *p/PHOTONUM;
float h = f + 2.0*M_PI *num/PHOTONUM;
float centery = self.view.center.y;
float centerx = self.view.center.x;
float tmpy = centery + RADIUS*cos(h);
float tmpx = centerx - RADIUS*sin(h);
imgview.center = CGPointMake(tmpx,tmpy);
CGPathAddArc(path,nil,self.view.center.x, self.view.center.y,RADIUS,f+ M_PI/2,f+ M_PI/2 + 2.0*M_PI *num/PHOTONUM,0);
animation.path = path;
CGPathRelease(path);
animation.duration = TIME;
animation.repeatCount = 1;
animation.calculationMode = #"paced";
return animation;
I assume you have a stored variable that represents the rotation of the "world", correct? If not, you should.
Then, for each image you add, also store a variable with it that represents its rotation to the world.
For example, if your world is rotated 180°, and you added a cup (which you want to appear right-side up when the world is upside-down) the cup's "offset" to the world rotation would be -180°.
Then, if the world is at 180° and you rotate your world by adding 90°, then the cup's new rotation value would be cup_rotate_offset + world_rotation, or 180°+270°, which is the same as saying 90°, and the top of the world would be facing left and the cup's top would be facing right.
You have to independently track the offset values for each added object.

Translating a view and the rotating it problem

I have a custom UIImageView, I can drag it around screen by making a translation with (xDif and yDif is the amount fingers moved):
CGAffineTransform translate = CGAffineTransformMakeTranslation(xDif, yDif);
[self setTransform: CGAffineTransformConcat([self transform], translate)];
Let's say I moved the ImageView for 50px in both x and y directions. I then try to rotate the ImageView (via gesture recognizer) with:
CGAffineTransform transform = CGAffineTransformMakeRotation([recognizer rotation]);
myImageView.transform = transform;
What happens is the ImageView suddenly moves to where the ImageView was originally located (before the translation - not from the moved position + 50px in both directions).
(It seems that no matter how I translate the view, the self.center of the ImageView subclass stays the same - where it was originally laid in IB).
Another problem is, if I rotate the ImageView by 30 deg, and then try to rotate it a bit more, it will again start from the original position (angle = 0) and go from there, why wouldn't it start from the angle 30 deg and not 0.
You are overwriting the earlier transform. To add to the current transform, you should do this –
myImageView.transform = CGAffineTransformRotate(myImageView.transform, recognizer.rotation);
Since you're changing the transform property in a serial order, you should use CGAffineTransformRotate, CGAffineTransformTranslate and CGAffineTransformScale instead so that you add to the original transform and not create a new one.

CAShapeLayer path disappears after animation - need it to stay in the same place

Thanks to some help on StackOverflow, I am currently animating a path in CAShapeLayer to make a triangle that points from a moving sprite to another moving point on the screen.
Once the animation completes, the triangle disappears from the screen. I am using very short durations because this code is being fired every .1 of second for each of the sprites. The result is the red triangle tracks correctly, but it rapidly flashes or isn't there entirely. When I crank up the the duration, I can the see the triangle stay longer.
What can I do to get the triangle to stay on the screen, with it's current path (the tovalue) until the method is called again to animate from the spot to the next spot? I tried setting the removedOnCompletion and removeAllAnimations, but to no avail.
Code below:
-(void)animateConnector{
//this is the code that moves the connector from it's current point to the next point
//using animation vs. position or redrawing it
//set the newTrianglePath
newTrianglePath = CGPathCreateMutable();
CGPathMoveToPoint(newTrianglePath, nil, pointGrid.x, pointGrid.y);//start at bottom point on grid
CGPathAddLineToPoint(newTrianglePath, nil, pointBlob.x, (pointBlob.y - 10.0));//define left vertice
CGPathAddLineToPoint(newTrianglePath, nil, pointBlob.x, (pointBlob.y + 10.0));//define the right vertice
CGPathAddLineToPoint(newTrianglePath, nil, pointGrid.x, pointGrid.y);//close the path
CGPathCloseSubpath(newTrianglePath);
//NSLog(#"PointBlob.y = %f", pointBlob.y);
CABasicAnimation *connectorAnimation = [CABasicAnimation animationWithKeyPath:#"path"];`enter code here`
connectorAnimation.duration = .007; //duration need to be less than the time it takes to fire handle timer again
connectorAnimation.removedOnCompletion = NO; //trying to keep the the triangle from disappearing after the animation
connectorAnimation.fromValue = (id)trianglePath;
connectorAnimation.toValue = (id)newTrianglePath;
[shapeLayer addAnimation:connectorAnimation forKey:#"animatePath"];
//now make the newTrianglePath the old one, so the next animation starts with the new position 2.9-KC
self.trianglePath = self.newTrianglePath;
}
The issue is the fillMode on your animation. The default for fillMode is "kCAFillModeRemoved" which will remove your animation when completed.
Do this:
connectorAnimation.fillMode = kCAFillModeForwards;
This should do it.