UIImageView is floating around on screen after switching view - iphone

I'm trying to make a compass.
Basically I use an image of a compass needle as the directions-thing for the compass. It's animating fine around the center of the compass UNTIL I switch view, or add another view (ex. UIActionView or switch to another view controller):
- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading{
float oldRadian = (-manager.heading.trueHeading *M_PI /180.0f);
float newRadian = (-newHeading.trueHeading *M_PI /180.0f);
CABasicAnimation *animation;
animation=[CABasicAnimation animationWithKeyPath:#"transform.rotation"];
animation.fromValue = [NSNumber numberWithFloat:oldRadian];
animation.toValue = [NSNumber numberWithFloat:newRadian];
animation.duration = 0.5f;
[compassArrow.layer addAnimation:animation forKey:#"animateMyRotation"];
compassArrow.transform = CGAffineTransformMakeRotation(newRadian);
}
After this I'm switching to another view controller that is the compassArrow imageview that was animating & started floating around.
Any ideas why this happens??

Set Compass arrow center as your Compass center
compassArrow.center = CompassImage.center;
Hope this Helps you

For the compassArrow imageView set the anchor point. so that it will rotate according to the anchor point.
use compassArrow.layer.anchorPoint = CGPointMake(0.5, 0.5);
hope i will help you.....

Get your image view frame first. For that you can type NSLog(#"%f %f %f %f", compassArrow.frame.origin.x,compassArrow.frame.origin.y,compassArrow.frame.size.width,compassArrow.frame.size.height); in viewDidLoad.
Note down the frame. Then in viewWillAppear method, add the line.
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:YES];
[compassArrow setFrame:CGRectMake(CMPASSARROWFRAME.x, CMPASSARROWFRAME.y, CMPASSARROWFRAME.width, CMPASSARROWFRAME.height)];
}
Note this 'CMPASSARROWFRAME' is the frame of your compassArrow frame.

please check the frame before and after the switch and see whether they are same.If they are not same then it means that you are setting its frame wrongly somewhere .If the frame is the same before and after switching view then the problem is with layer transformation.Somewhere or the other you might have changed the anchor point or the layer location.
find out where it gets updated,use break points,follow the control flow and track the place where frame gets updates.You can find the step into button above the debug console in xcode.

I solved it using the removeFromSuperlayer-method before I change view. Problem was that I created multiple animations for the same key (#"animateMyRotation").
[compassArrow.layer removeFromSuperlayer];

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

Stop CABasicAnimation at specific point

I'm using a rotation animation created with CABasicAnimation. It rotates a UIView over 2 seconds. But I need to be able to stop it when the UIView is touched. If I remove the animation the view is in the same position as before the animation started.
Here's my animation code:
float duration = 2.0;
float rotationAngle = rotationDirection * ang * speed * duration;
//rotationAngle = 3*(2*M_PI);//(double)rotationAngle % (double)(2*M_PI) ;
CABasicAnimation* rotationAnimation;
rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
rotationAnimation.toValue = [NSNumber numberWithFloat: rotationAngle ];
rotationAnimation.duration = duration;
rotationAnimation.cumulative = YES;
rotationAnimation.removedOnCompletion = NO;
rotationAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
rotationAnimation.fillMode = kCAFillModeForwards;
rotationAnimation.delegate = self;
[self.view.layer addAnimation:rotationAnimation forKey:#"rotationAnimation"];
How can I stop the UIView's rotation right where it is, when it's touched? I know how to manage the touch part, but I can't figure out how to stop the view at the animation's current angle.
Solution:
I solved the problem by getting the angle of the presentation layer, removing the animation and setting the view's transform. Here's the code:
[self.view.layer removeAllAnimations];
CALayer* presentLayer = self.view.layer.presentationLayer;
float currentAngle = [(NSNumber *)[presentLayer valueForKeyPath:#"transform.rotation.z"] floatValue];
self.view.transform = CGAffineTransformMakeRotation(currentAngle);
Good question! For this, it's helpful to know the Core Animation architecture.
If you check out the diagram in the Core Animation Programming Guide that describes the Core Animation Rendering Architecture, you can see that there's three trees.
You have the model tree. That's where you set the values of what you want to happen. Then there's the presentation tree. That's what is pretty much happening as far as the runtime is concerned. Then, finally is the render tree. That's what the user sees.
In your case, you want to query the values of the presentation tree.
It's easy to do. For the view that you have attached the animation, get the layer and for that layer, query the presentationLayer's values. For example:
CATransform3D myTransform = [(CALayer*)[self.view.layer presentationLayer] transform];
There's no way to "pause" an animation mid flow. All you can do is query the values, remove it, and then re-create it again from where you left off.
It's a bit of a pain!
Have a look at some of my other posts where I go into this in a bit more detail, e.g.
Restoring animation where it left off when app resumes from background
Don't forget also that when you add an animation to a view's layer, you aren't actually changing the underlying view's properties. So what happens? We'll you get weird effects where the animation stops and you see the view in it's original position.
That's where you need to use the CAAnimation delegates. Have a look at my answer to this post where I cover this:
CABasicAnimation rotate returns to original position
You need to set the rotation to the rotation of the presentationLayer and then remove the animation from the layer. You can read about the presentation layer in my blog post about Hit testing animating layers.
The code to set the final rotation would be something like:
self.view.layer.transform = [(CALayer*)[self.view.layer presentationLayer] transform];
[self.view.layer removeAnimationForKey:#"rotationAnimation"];

Antialiasing edges of UIView after transformation using CALayer's transform

I have a UIView object that rotates using CALayer's transform:
// Create uiview object.
UIImageView *block = [[UIImageView alloc] initWithFrame....]
// Apply rotation.
CATransform3D basicTrans = CATransform3DIdentity;
basicTrans.m34 = 1.0/-distance;
blockImage.layer.transform = CATransform3DRotate(basicTrans, rangle, 1.0f, 0.0f, 0.0f);
After rotating the edges of the object are not antialiasing. I need to antialias them.
Help me, please. How can it be done?
One way to do this is by placing the image inside another view that's 5 pixels bigger. The bigger view should have a transparent rasterized border that will smooth the edges of the UIImageView:
view.layer.borderWidth = 3;
view.layer.borderColor = [UIColor clearColor].CGColor;
view.layer.shouldRasterize = YES;
view.layer.rasterizationScale = [[UIScreen mainScreen] scale];
Then, place your UIImageView inside this parent view and center it (With 2.5 pixels around each edge).
Finally, rotate the parent view instead of the image view.
It works very well - you can also encapsulate the whole thing in class that creates the hierarchy.
Simply add this key-value pair to your Info.plist: UIViewEdgeAntialiasing set to YES.
check allowsEdgeAntialiasing property of CALayer.
block.layer.allowsEdgeAntialiasing = YES; // iOS7 and above.
I had a similar issue when rotating around the z-axis. Setting shouldRasterize = YES prevented the jagged edges however it came at a performance cost. In my case I was re-using the views (and its layers) and keeping the shouldRasterize = YES was slowing things down.
The solution was, to turn off rasterization right after I didn't need it anymore. However since animation runs on another thread, there was no way of knowing when the animation was complete...until I found out about an extremely useful CATransaction method. This is an actual code that I used and it should illustrate its use:
// Create a key frame animation
CAKeyframeAnimation *wiggle = [CAKeyframeAnimation animationWithKeyPath:#"transform"];
NSInteger frequency = 5; // Higher value for faster vibration
NSInteger amplitude = 25; // Higher value for lower amplitude
// Create the values it will pass through
NSMutableArray *valuesArray = [[NSMutableArray alloc] init];
NSInteger direction = 1;
[valuesArray addObject:#0.0];
for (NSInteger i = frequency; i > 0; i--, direction *= -1) {
[valuesArray addObject:#((direction * M_PI_4 * (CGFloat)i / (CGFloat)amplitude))];
}
[valuesArray addObject:#0.0];
[wiggle setValues:valuesArray];
// Set the duration
[wiggle setAdditive:YES];
[wiggle setValueFunction:[CAValueFunction functionWithName:kCAValueFunctionRotateZ]];
[wiggle setDuration:0.6];
// Turn on rasterization to prevent jagged edges (anti-aliasing issues)
viewToRotate.layer.shouldRasterize = YES;
// ************ Important step **************
// Very usefull method. Block returns after ALL animations have completed.
[CATransaction setCompletionBlock:^{
viewToRotate.layer.shouldRasterize = NO;
}];
// Animate the layer
[viewToRotate.layer addAnimation:wiggle forKey:#"wiggleAnimation"];
worked like a charm for me.
I have not tried using this with implicit animations (i.e. animations that happen due to value change in animatable property for a non-view associated layer), however I would expect it to work as long as the CATransaction method is called before the property change, just as a guarantee the block is given to CATransaction before an animation starts.

(iphone) how to set view.center when detaching a view from scroll view and adding it to another view?

I'd like to move a view from a scrollview to a uiview.
I'm having trouble changing it's center(or frame) so that it remains in the same position in screen (but in a different view, possibly the superview of scrollview).
How should I convert the view's center/frame?
Thank you.
EDIT:
CGPoint oldCenter = dragView.center;
CGPoint newCenter = [dragView convertPoint: oldCenter toView: self.navigationView.contentView];
dragView.center = newCenter;
[self.navigationView.contentView addSubview: dragView];
I can also use (NSSet*) touches since i'm in touchesBegan:
I was having hard time to make it work but the doc wasn't so clear to me.
You can use convertPoint:toView: method of UIView. It is used to convert a point from one view's coordinate system to another. See Converting Between View Coordinate Systems section of UIView class reference. There are more methods available.
-edit-
You are using the wrong point when calling convertPoint: method. The given point should be in dragView's coordinate system where as dragView.center is in its superview's coordinate system.
Use the following point and it should give you the center of dragView in its own coordinate system.
CGPoint p;
p = CGPointMake(dragView.bounds.size.width * 0.5, dragView.bounds.size.height * 0.5);

Drawing and Animating with UIView or CALayer?

i am trying to implement a graph which a uiview draws. There are 3 UIViews to animate a left/right slide. The problem is, that i can't cancel the UIView animation. So I replaced the UIViews by CALayer. Now, the question is if CALayer is appicable for this? Is it normal to draw on a CALayer like this? And why is a CALayer so slow when I manipulate the frame properties.
CGRect frame = curve.frame;
frame.origin.x -= diffX;
[curve setFrame:frame];
Is there a alternativ?
P.S. I am a german guy. Sorry for mistakes.
I got the animation with CATransaction, but now I will animate a x move with CABasicAnimation. That's no problem expect that the position of the layer go back to the previous x.
CABasicAnimation *theAnimation;
theAnimation = [CABasicAnimation animationWithKeyPath:#"position"];
theAnimation.delegate = self;
theAnimation.duration = 1.0;
theAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
CGPoint position = [[curves objectAtIndex:i]position];
position.x = [[curves objectAtIndex:i]position].x - diffX;
[theAnimation setToValue:[NSValue valueWithCGPoint:position]];
[[curves objectAtIndex:i] addAnimation:theAnimation forKey:[NSString stringWithFormat: #"translate.x.%d", index]];
The position changes the position (e.g. xStart = 100, xEnd = 200), but when the animation ends the layer goes back to the beginning x (e.g. x = 100).
Is that normal? How can I solve this problem, that the end position doesn't change anymore?
I tried to changed the removeOnComplete property but that didn't effect.
Hope for help.
Markus
Not sure what you mean by 'slow', but setting the frame of a CALayer in this way uses 'implicit animation'. That is, it will animated the transition from the old frame to the new frame. You can turn this off:
[CATransaction begin];
[CATransaction setValue: (id) kCFBooleanTrue forKey: kCATransactionDisableActions];
[curve setFrame:frame];
[CATransaction commit];
However, this is usually considered an advantage of CALayer. You way want to just use UIViews here, which will not, by default, animate transitions such as this.
Instead of setting the destination position in theAnimation, just set the position property of the thing you want to move.
When you addAnimation:theAnimation, you're setting the "visual style" of any changes to the keyPath property you specified.
When you change the position of the object that the animation is attached to, say from (0,0) to (500,500), CoreAnimation will animate the change for you. The theAnimation object doesn't need the start and end positions, since the underlying object has them.