I have a button (actually four of them) in a specific position that I want to rotate and translate at the same time, therefore I opted to use CGAffineTransformMake and provide the transformation matrix.
However I noticed that if I want to translate x = -100, for instance, the button will first be instantly shifted x = +200 and then will animate it's transformation to x = -100.
Is there any way to make it translate without this shift to the opposite direction?
CGAffineTransform transform = CGAffineTransformMake(-1, 0, 0, -1, -100, 0); // Spins 180° and translates
[UIView beginAnimations:#"Show Menu" context:nil];
[UIView setAnimationDuration:2.4];
_menuButton1.transform = transform;
[_menuButton1 setAlpha:1.0];
[UIView commitAnimations];
You shouldn't be using that animation method. It is discouraged by Apple after iOS 4.0. https://developer.apple.com/library/ios/documentation/uikit/reference/uiview_class/UIView/UIView.html#//apple_ref/occ/clm/UIView/beginAnimations:context:
The preferred method is...
[UIView animateWithDelay:0.0
duration:2.4
options:0
animations:^() {
_menuButton1.transform = CGAffineTransformMake(-1, 0, 0, -1, -100, 0);
_menuButton1.alpha = 1.0;
}
completion:nil];
Will edit my answer when you reply to my comment.
OK, the cause for this shift is that you are trying to translate the view whilst using AutoLayout to constrain it.
I don't know why it causes an issue, but it does.
Your two options for fixing it are...
Turn of AutoLayout and then you code will work.
Keep AutoLayout enabled and learn how to animate constraints.
For the second option I can recommend the book "iOS6 by Tutorials" by Ray Wenderlich and his team.
http://www.raywenderlich.com/store/ios-6-by-tutorials
I used this to learn how to do AutoLayout properly.
You can start with one of the transforms, let's say translation:
CGAffineTransform transform = CGAffineTransformMakeTranslation(-100.0f, 0.0f);
And then add rotation to it:
transform = CGAffineTransformRotate(transform, (float)M_PI_2);
And finally set this transform to your view's transform property.
Related
I am new to ios development. i want different animation styles like flipboard animations.can any one give me some sample examples.
Thanks in advance
I don't have any experience in this, but after reading the docs, I think you're going to need a library for that. A quick Google search suggested:
Canvas (provides animations: bounce, slide, fade, fun)
MZFormSheetController
Pop
If you want to code your own custom animation, it appears that the apple documentation explains how.
You can use a library, but it is possible to transform the layer of a UIView in many ways, quite easily.
Say the view you want to animate is called view and is of type UIView
Here is how to animate it similar to a flip animation, where it rotates around the y-axis:
CALayer *layer = view.layer;
CATransform3D rotationAndPerspectiveTransform = CATransform3DIdentity;
rotationAndPerspectiveTransform = CATransform3DTranslate(rotationAndPerspectiveTransform, 0, 0, 20);
rotationAndPerspectiveTransform.m34 = 5.0 / -500;
rotationAndPerspectiveTransform = CATransform3DRotate(rotationAndPerspectiveTransform, angle* M_PI / 180.0f, 0.0f, 1.0f, 0.0f);
layer.transform = rotationAndPerspectiveTransform;
The key function here is CATransform3DRotate which rotates the layer in 3d.
You specify the axis around which to rotate with the last 3 parameters (x, y, z) which is (0,1,0) in this case, i.e. the y-axis.
Note that this won't produce an animation, but rather, will orient the layer using the provided axis and angle.
To animate the layer, you will have to incrementally change the angle, in another function (e.g. using NSTimer).
I am trying to replace one complex view with another complex view using different animations, like moving to left/right/top/bottom. First and second views contains 30-40 subviews (buttons). My code is like this:
oldView.alpha = 1;
newView.alpha = 0;
oldView.frame = CGRectMake(0, 0, width, height);
newView.frame = CGRectMake(0, -height, width, height);
// begin animation
// setting duration 0.3
// ...
oldView.alpha = 0;
newView.alpha = 1;
oldView.frame = CGRectMake(0, height, width, height);
newView.frame = CGRectMake(0, 0, width, height);
// commit animation
// ...
It works nice on iPhone Simulator and on iPhone 4S, but it lags on iPhone 4. By lag I mean 12-15 FPS.
How can I speed up this animation?
Should I use center property instead of frame?
Should I render my views to UIImageView's and animate them?
Should I layout my views in UIScrollView and call scrollRectToVisible:animated:?
And please, explain me why my animation code so slow? When my two views contains 10-20 buttons - there are no problems with speed...
You will need to post more code as setting a new frame shouldn't be too much of a performance hit. There are however things you can do with your view's layer or in the drawRect: that would slow drawing and animation down considerably.
For instance:
myView.layer.shadowOffset = CGSizeMake(0.0, 10.0);
myView.layer.shadowRadius = 10;
myView.layer.shadowOpacity = 0.40;
Which adds a nice drop shadow to the view will kill the animation. So in this instance I turn off the shadows when animating and back on again when done.
Without seeing more of your code it is impossible to say where the problem lies.
EDIT:
To speed up the rendering of shadows you can set the shouldRasterize property to YES. This forces the layer to create a bitmap of the shadow rather than trying to redraw it every frame.
Try one of the following:
change center instead of frame
use transform property instead of frame. oldView.transform = CGAffineTransformMakeTranslation(0, height);
Just to add, if you are using explicit animations yet still setting animatable properties, you'll want to disable implicit animations by using:
[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
This question has been asked before but in a slightly different way and I was unable to get any of the answers to work the way I wanted, so I am hoping somebody with great Core Animation skills can help me out.
I have a set of cards on a table. As the user swipes up or down the set of cards move up and down the table. There are 4 cards visible on the screen at any given time, but only the second card is showing its face. As the user swipes the second card flips back onto its face and the next card (depending on the swipe direction) lands in it's place showing its face.
I have set up my card view class like this:
#interface WLCard : UIView {
UIView *_frontView;
UIView *_backView;
BOOL flipped;
}
And I have tried flipping the card using this piece of code:
- (void) flipCard {
[self.flipTimer invalidate];
if (flipped){
return;
}
id animationsBlock = ^{
self.backView.alpha = 1.0f;
self.frontView.alpha = 0.0f;
[self bringSubviewToFront:self.frontView];
flipped = YES;
CALayer *layer = self.layer;
CATransform3D rotationAndPerspectiveTransform = CATransform3DIdentity;
rotationAndPerspectiveTransform.m34 = 1.0 / 500;
rotationAndPerspectiveTransform = CATransform3DRotate(rotationAndPerspectiveTransform, M_PI, 1.0f, 0.0f, 0.0f);
layer.transform = rotationAndPerspectiveTransform;
};
[UIView animateWithDuration:0.25
delay:0.0
options: UIViewAnimationCurveEaseInOut
animations:animationsBlock
completion:nil];
}
This code works but it has the following problems with it that I can't seem to figure out:
Only half of the card across the x-axis is animated.
Once flipped, the face of the card is upside down and mirrored.
Once I've flipped the card I cannot get the animation to ever run again. In other words, I can run the animation block as many times as I want, but only the first time will animate. The subsequent times I try to animate lead to just a fade in and out between the subviews.
Also, bear in mind that I need to be able to interact with the face of the card. i.e. it has buttons on it.
If anybody has run into these issues it would be great to see your solutions. Even better would be to add a perspective transform to the animation to give it that extra bit of realism.
This turned out to be way simpler than I thought and I didn't have to use any CoreAnimation libraries to achieve the effect. Thanks to #Aaron Hayman for the clue. I used transitionWithView:duration:options:animations:completion
My implementation inside the container view:
[UIView transitionWithView:self
duration:0.2
options:UIViewAnimationOptionTransitionFlipFromBottom
animations: ^{
[self.backView removeFromSuperview];
[self addSubview:self.frontView];
}
completion:NULL];
The trick was the UIViewAnimationOptionTransitionFlipFromBottom option. Incidentally, Apple has this exact bit of code in their documentation. You can also add other animations to the block like resizing and moving.
Ok, this won't be a complete solution but I'll point out some things that might be helpful. I'm not a Core-Animation guru but I have done a few 3D rotations in my program.
First, there is no 'back' to a view. So if you rotate something by M_PI (180 degrees) you're going to be looking at that view as though from the back (which is why it's upside down/mirrored).
I'm not sure what you mean by:
Only half of the card across the x-axis is animated.
But, it it might help to consider your anchor point (the point at which the rotation occurs). It's usually in the center, but often you need it to be otherwise. Note that anchor points are expressed as a proportion (percentage / 100)...so the values are 0 - 1.0f. You only need to set it once (unless you need it to change). Here's how you access the anchor point:
layer.anchorPoint = CGPointMake(0.5f, 0.5f) //This is center
The reason the animation only ever runs once is because transforms are absolute, not cumulative. Consider that you're always starting with the identity transform and then modifying that, and it'll make sense...but basically, no animation occurs because there's nothing to animate the second time (the view is already in the state you're requesting it to be in).
If you're animating from one view to another (and you can't use [UIView transitionWithView:duration:options:animations:completion:];) you'l have to use a two-stage animation. In the first stage of the animation, for the 'card' that is being flipped to backside, you'll rotate the view-to-disappear 'up/down/whatever' to M_PI_2 (at which point it will be 'gone', or not visible, because of it's rotation). And in the second stage, you're rotate the backside-of-view-to-disappear to 0 (which should be the identity transform...aka, the view's normal state). In addition, you'll have to do the exact opposite for the 'card' that is appearing (to frontside). You can do this by implementing another [UIView animateWithDuration:...] in the completion block of the first one. I'll warn you though, doing this can get a little bit complicated. Especially since you're wanting views to have a 'backside', which will basically require animating 4 views (the view-to-disappear, the view-to-appear, backside-of-view-to-disappear, and the backside-of-view-to-appear). Finally, in the completion block of the second animation you can do some cleanup (reset view that are rotated and make their alpha 0.0f, etc...).
I know this is complicated, so you might want read some tutorial on Core-Animation.
#Aaron has some good info that you should read.
The simplest solution is to use a CATransformLayer that will allow you to place other CALayer's inside and maintain their 3D hierarchy.
For example to create a "Card" that has a front and back you could do something like this:
CATransformLayer *cardContainer = [CATransformLayer layer];
cardContainer.frame = // some frame;
CALayer *cardFront = [CALayer layer];
cardFront.frame = cardContainer.bounds;
cardFront.zPosition = 2; // Higher than the zPosition of the back of the card
cardFront.contents = (id)[UIImage imageNamed:#"cardFront"].CGImage;
[cardContainer addSublayer:cardFront];
CALayer *cardBack = [CALayer layer];
cardBack.frame = cardContainer.bounds;
cardBack.zPosition = 1;
cardBack.contents = (id)[UIImage imageNamed:#"cardBack"].CGImage; // You may need to mirror this image
[cardContainer addSublayer:cardBack];
With this you can now apply your transform to cardContainer and have a flipping card.
#Paul.s
I followed your approach with card container but when i applt the rotation animation on card container only one half of the first card rotates around itself and finally the whole view appears.Each time one side is missing in the animation
Based on Paul.s this is updated for Swift 3 and will flip a card diagonally:
func createLayers(){
transformationLayer = CATransformLayer(layer: CALayer())
transformationLayer.frame = CGRect(x: 15, y: 100, width: view.frame.width - 30, height: view.frame.width - 30)
let black = CALayer()
black.zPosition = 2
black.frame = transformationLayer.bounds
black.backgroundColor = UIColor.black.cgColor
transformationLayer.addSublayer(black)
let blue = CALayer()
blue.frame = transformationLayer.bounds
blue.zPosition = 1
blue.backgroundColor = UIColor.blue.cgColor
transformationLayer.addSublayer(blue)
let tgr = UITapGestureRecognizer(target: self, action: #selector(recTap))
view.addGestureRecognizer(tgr)
view.layer.addSublayer(transformationLayer)
}
Animate a full 360 but since the layers have different zPositions the different 'sides' of the layers will show
func recTap(){
let animation = CABasicAnimation(keyPath: "transform")
animation.delegate = self
animation.duration = 2.0
animation.fillMode = kCAFillModeForwards
animation.isRemovedOnCompletion = false
animation.toValue = NSValue(caTransform3D: CATransform3DMakeRotation(CGFloat(Float.pi), 1, -1, 0))
transformationLayer.add(animation, forKey: "arbitrarykey")
}
Edited to better explain my problem
I am trying to perform a zoom operation using my custom view (not UIView). The view has translation, scale, rotate values. I use these as follows, between calls to glPushMatrix() and glPopMatrix().
- (void)transform
{
glTranslatef(translation.x + anchor.x, translation.y + anchor.y, 0.0f);
//glRotatef(-rotation * 57.2957795f, 0.0f, 0.0f, 1.0f);
glScalef(scale.x, scale.y, 1.0f);
glTranslatef(-anchor.x, -anchor.y, 0.0f);
}
I am trying to figure out how I should modify the anchor and/or translation values so that the zoom operation is relative to what appears on screen. At 1:1 scale I can simply use the raw screen coordinates as the anchor and perform the above transform. But when the view is already at some arbitrary scale/position, the anchor and/or translation needs to account for that.
So far this is what I've figured out:
1) Get the displacement from the center of scale to the view origin, in screen coordinates.
2) Scale this value so it's in the view's local coordinate system.
3) Now I have the new anchor for scaling. I set the view's anchor to this value.
This alone is not enough it seems. I think I am missing a translation component, or another variable that goes into the new anchor point. What am I missing?
I found a solution. I used a separate transform specifically for the zoom operation, in addition to the transform that defines the original view. I simply concat the two to get the result I wanted -- which was to ensure that the zoom is relative to the original view.
Some of the CGAffine animations make assumptions about the components of the matrix that you wouldn't expect.
For example,i found that CGAffineTransformMakeTranslation also effected the rotation of the View. For this reason i would recommend not using the 'Make' transforms if your concating many instances of CGAffineTransform
Also, in your above example i can see that you assume that your matrix understands that your two translates should occur at different times. A transform matrix is is a set of physical attributes about the object at any 1 point in time.
You want an object to move to -anchor
and scale
You want an object to move to back to
anchor
These should be considered as two different animations
You should do this with 'Key Frame Animation', here is an example: CGPath Animation
Alternatively, you could chain two CGAffineTransform methods together.
-(void)Anim1
{
[UIView beginAnimations:#"Anim1_Done" context:NULL];
[UIView setAnimationDuration:0.5];
[UIView setAnimationDelegate:self];
UIView setAnimationDidStopSelector:#selector(animationFinished:finished:context:)];
//DO STUFF HERE
[UIView commitAnimations];
}
- (void)animationFinished:(NSString *)animationID finished:(BOOL)finished context:(void *)context
{
if ([animationID isEqualToString:#"Anim1_Done"])
{
[self Anim2];
}
}
-(void)Anim2
{
[UIView beginAnimations:#"Anim2_Done" context:NULL];
[UIView setAnimationDuration:0.5];
[UIView setAnimationDelegate:self];
UIView setAnimationDidStopSelector:#selector(animationFinished:finished:context:)];
//DO STUFF HERE
[UIView commitAnimations];
}
Your transform intent still isn't exactly clear.
The CGAffineTransformMakeTranslation transform will create the translation matrix needed to get to a desired point, but as i mentioned in my other answer some of the make transforms make assumptions about other things like rotation, if your object hasn't been rotated and therefore cant be reset then this isn't a problem, other wise you can use the translation components of the matrix:
Transform.tx;
Transform.ty;
Edit: 3 separate animations should be done with 3 separate matrices:
baseMatrix = CGAffineTransformIdentity;
affineMatrix1 = CGAffineTransformTranslate(baseMatrix, -anchor.x, -anchor.y);
affineMatrix2 = CGAffineTransformScale(baseMatrix, scale.x, scale.y);
affineMatrix3 = CGAffineTransformTranslate(baseMatrix, anchor.x, anchor.y);
I have a view which I want to be scaled and translated to a new location by animating it. I tried to achieve it with the following code:
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:kDurationForFullScreenAnimation];
[[self animatingView] setFrame:finalRect];
[UIView commitAnimations];
The effect of this code is, the view first changes its content's size to the finalRect and then translates it to the new location. i.e. The scaling part is never animated. The view is just transformed to the new size and then translated.
This issue is already discussed in several other threads but none of them draw a conclusion. A solution does exist though, to use a timer and set the frame each time in the timer callback, but it has a performance drawback.
What is the most appropriate solution to this problem, also, why in first case this problem occur?
Thanks
Setting the frame does neither a scale nor a translation. You are either using the wrong terminology or you are using the wrong tool for the job. Scale and translate are both done using Core Graphics Affine transforms when you're looking to affect the UIView (as opposed to the layer which use Core Animation transforms).
To scale a view use
// 2x
[rotationView setTransform:CGAffineTransformMakeScale(2.0, 2.0)];
To translate, use
// Move origin by 100 on both axis
[rotationView setTransform:CGAffineTransformMakeTranslation(100.0, 100.0)];
To animate these, wrap them in an animation block. If you want to transform the view with both of these, then you need to concatenate them.
If you are not wanting scale and translation (transforms) at all, then what you mean is you want to change the view's bounds and position. These are changed with calls to
[view setBounds:newRect];
[view setCenter:newCenter];
Where newRect and newCenter are a CGRect and CGPoint respectively that represent the new position on the screen. Again, these need wrapped in an animation block.
Here is an example with block animation:
CGPoint newCenter = CGPointMake(100.0,100.0);
[UIView animateWithDuration: 1
delay: 0
options: (UIViewAnimationOptionCurveLinear | UIViewAnimationOptionAllowUserInteraction)
animations:^{object.center = newCenter ; object.transform = CGAffineTransformScale(CGAffineTransformIdentity, 2.0, 2.0);}
completion:^(BOOL finished) { }
];
Try this solution :
CGAffineTransform s = CGAffineTransformMakeScale(0.5f,0.5f);
CGAffineTransform t = CGAffineTransformMakeTranslation(100, 0);
v2.transform = CGAffineTransformConcat(t,s); // not s,t
this operation is not commutative. The order is the opposite of the order when using convenience functions for applying one transform to another.
And you can use this operation to : for example (remove the Scale) :
v2.transform =
CGAffineTransformConcat(CGAffineTransformInvert(s), v2.transform);