I've got a custom UIView to show a tiled image.
- (void)drawRect:(CGRect)rect
{
...
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClipToRect(context,
CGRectMake(0.0, 0.0, rect.size.width, rect.size.height));
CGContextDrawTiledImage(context, imageRect, imageRef);
...
}
Now I am trying to animate the resize of that view.
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.3];
// change frame of view
[UIView commitAnimations];
What I would expect is for the tiled area just to grow, leaving the tile sizes constant. What happens instead is that while the area grows the original content of the view is scaled to the new size. At least during the animation. So the at the beginning and the end of the animation all is good. During the animation the tiles are distorted.
Why is CA trying to scale? How can I prevent it from doing so? What did I miss?
If Core Animation had to call back to your code for every animation frame it would never be as fast as it is, and animation of custom properties has been a long requested feature and FAQ for CA on the Mac.
Using UIViewContentModeRedraw is on the right track, and is also the best you'll get from CA. The problem is from the UIKit point of view the frame only has two values: the value at the beginning of the transition and the value at the end of the transition, and that's what you're seeing. If you look at the Core Animation architecture documents you'll see how CA has a private representation of all layer properties and their values changing over time. That's where the frame interpolation is happening, and you can't be notified of changes to that as they happen.
So the only way is to use an NSTimer (or performSelector:withObject:afterDelay:) to change the view frame over time, the old fashioned way.
You might want to have a look at
http://www.nomadplanet.fr/2010/11/animate-calayer-custom-properties-with-coreanimation/
I was facing similar problem in a different context, I stumbled upon this thread and found Duncan's suggestion of setting frame in NSTimer. I implemented this code to achieve the same:
CGFloat kDurationForFullScreenAnimation = 1.0;
CGFloat kNumberOfSteps = 100.0; // (kDurationForFullScreenAnimation / kNumberOfSteps) will be the interval with which NSTimer will be triggered.
int gCurrentStep = 0;
-(void)animateFrameOfView:(UIView*)inView toNewFrame:(CGRect)inNewFrameRect withAnimationDuration:(NSTimeInterval)inAnimationDuration numberOfSteps:(int)inSteps
{
CGRect originalFrame = [inView frame];
CGFloat differenceInXOrigin = originalFrame.origin.x - inNewFrameRect.origin.x;
CGFloat differenceInYOrigin = originalFrame.origin.y - inNewFrameRect.origin.y;
CGFloat stepValueForXAxis = differenceInXOrigin / inSteps;
CGFloat stepValueForYAxis = differenceInYOrigin / inSteps;
CGFloat differenceInWidth = originalFrame.size.width - inNewFrameRect.size.width;
CGFloat differenceInHeight = originalFrame.size.height - inNewFrameRect.size.height;
CGFloat stepValueForWidth = differenceInWidth / inSteps;
CGFloat stepValueForHeight = differenceInHeight / inSteps;
gCurrentStep = 0;
NSArray *info = [NSArray arrayWithObjects: inView, [NSNumber numberWithInt:inSteps], [NSNumber numberWithFloat:stepValueForXAxis], [NSNumber numberWithFloat:stepValueForYAxis], [NSNumber numberWithFloat:stepValueForWidth], [NSNumber numberWithFloat:stepValueForHeight], nil];
NSTimer *aniTimer = [NSTimer timerWithTimeInterval:(kDurationForFullScreenAnimation / kNumberOfSteps)
target:self
selector:#selector(changeFrameWithAnimation:)
userInfo:info
repeats:YES];
[self setAnimationTimer:aniTimer];
[[NSRunLoop currentRunLoop] addTimer:aniTimer
forMode:NSDefaultRunLoopMode];
}
-(void)changeFrameWithAnimation:(NSTimer*)inTimer
{
NSArray *userInfo = (NSArray*)[inTimer userInfo];
UIView *inView = [userInfo objectAtIndex:0];
int totalNumberOfSteps = [(NSNumber*)[userInfo objectAtIndex:1] intValue];
if (gCurrentStep<totalNumberOfSteps)
{
CGFloat stepValueOfXAxis = [(NSNumber*)[userInfo objectAtIndex:2] floatValue];
CGFloat stepValueOfYAxis = [(NSNumber*)[userInfo objectAtIndex:3] floatValue];
CGFloat stepValueForWidth = [(NSNumber*)[userInfo objectAtIndex:4] floatValue];
CGFloat stepValueForHeight = [(NSNumber*)[userInfo objectAtIndex:5] floatValue];
CGRect currentFrame = [inView frame];
CGRect newFrame;
newFrame.origin.x = currentFrame.origin.x - stepValueOfXAxis;
newFrame.origin.y = currentFrame.origin.y - stepValueOfYAxis;
newFrame.size.width = currentFrame.size.width - stepValueForWidth;
newFrame.size.height = currentFrame.size.height - stepValueForHeight;
[inView setFrame:newFrame];
gCurrentStep++;
}
else
{
[[self animationTimer] invalidate];
}
}
I found out that it takes a long time to set the frame and the timer to complete its operation. It probably depends on how deep the view hierarchy is on which you call -setFrame using this approach. And probably this is the reason why the view is first re-sized and then animated to the origin in the sdk. Or perhaps, is there some problem in the timer mechanism in my code due to which the performance has hindered?
It works, but it is very slow, maybe because my view hierarchy is too deep.
As Duncan indicates, Core Animation won't redraw your UIView's layer's content every frame as it is resized. You need to do that yourself using a timer.
The guys at Omni posted a nice example of how to do animations based on your own custom properties that might be applicable to your case. That example, along with an explanation for how it works, can be found here.
If it is not a big animation or performance isn't an issue for the animation duration, you can use a CADisplayLink. It does smooth out the animation of the scaling of a custom UIView drawing UIBezierPaths for instance. Below is a sample code you can adapt for your image.
The ivars of my custom view contains displayLink as a CADisplayLink, and two CGRects: toFrame and fromFrame, as well as duration and startTime.
- (void)yourAnimationMethodCall
{
toFrame = <get the destination frame>
fromFrame = self.frame; // current frame.
[displayLink removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; // just in case
displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(animateFrame:)];
startTime = CACurrentMediaTime();
duration = 0.25;
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}
- (void)animateFrame:(CADisplayLink *)link
{
CGFloat dt = ([link timestamp] - startTime) / duration;
if (dt >= 1.0) {
self.frame = toFrame;
[displayLink removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
displayLink = nil;
return;
}
CGRect f = toFrame;
f.size.height = (toFrame.size.height - fromFrame.size.height) * dt + fromFrame.size.height;
self.frame = f;
}
Note that I haven't tested its performance, but it does work smoothly.
I stumbled upon this question when trying to animate a size change on a UIView with a border. I hope that others who similarly arrive here might benefit from this answer. Originally I was creating a custom UIView subclass and overriding the drawRect method as follows:
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(ctx, 2.0f);
CGContextSetStrokeColorWithColor(ctx, [UIColor orangeColor].CGColor);
CGContextStrokeRect(ctx, rect);
[super drawRect:rect];
}
This led to scaling problems when combining with animations as others have mentioned. The top and bottom of the border would grow too thick or two thin and appear scaled. This effect is easily noticeable when toggling on Slow Animations.
The solution was to abandon the custom subclass and instead use this approach:
[_borderView.layer setBorderWidth:2.0f];
[_borderView.layer setBorderColor:[UIColor orangeColor].CGColor];
This fixed the issue with scaling a border on a UIView during animations.
Related
I want to create an animation similar to app opens in iPhone in iOS7. In this animation it just shows that app is opening from which point and closing at same point.
Can anyone please help me?
#property (weak, nonatomic) IBOutlet UIImageView *bg;
#property (weak, nonatomic) IBOutlet UIImageView *cal;
…
bool nowZoomed = NO;
CGRect iconPosition = {16,113,60,60}; // customize icon position
- (CGRect)zoomedRect // just a helper function, to get the new background screen size
{
float screenWidth = UIScreen.mainScreen.bounds.size.width;
float screenHeight = UIScreen.mainScreen.bounds.size.height;
float size = screenWidth / iconPosition.size.width;
float x = screenWidth/2 - (CGRectGetMidX(iconPosition) * size);
float y = screenHeight/2 - (CGRectGetMidY(iconPosition) * size);
return CGRectMake(x, y, screenWidth * size, screenHeight * size);
}
- (IBAction)test
{
float animationDuration = 0.3f; //default
if (nowZoomed) // zoom OUT
{
[UIView animateWithDuration:animationDuration animations:^{ // animate to original frame
_cal.frame = iconPosition;
_bg.frame = UIScreen.mainScreen.bounds;
} completion:^(BOOL finished) {
[UIView animateWithDuration:animationDuration/2.0f animations:^{ // then fade out
_cal.alpha = 0.0f;
} completion:^(BOOL finished) {
_cal.hidden = YES;
}];
}];
}
else // zoom IN
{
_cal.alpha = 0.0f;
_cal.hidden = NO;
[UIView animateWithDuration:animationDuration/2.0f animations:^{ // fade in faster
_cal.alpha = 1.0f;
}];
[UIView animateWithDuration:animationDuration animations:^{ // while expanding view
_cal.frame = UIScreen.mainScreen.bounds;
_bg.frame = [self zoomedRect];
}];
}
nowZoomed = !nowZoomed;
}
you can test it, by creating a sample project like this:
make two screenshots from simulator like I did (homescreen and calendar view) or grab these two: homescreen / calendar
add 2 image views and 1 button into storyboard
make the background image view as big as the whole screen
and the other image view with this dimensions: {16,113,60,60}
create an IBOutlet for both (the very first two lines of code)
set the button action target to -(void)test
the storyboard picture (left) and animation transition (right)
I personally prefer to use CGAffineTransformMakeScale() and setting -[CALayer affineTransform] in this case.
affineTransform is super easy to use and comes with a few nice, implicit benefits from Core Animation. Examples being that does things like handling of changing the frame's origin for you implicitly and making it really easy to reset back to the initial size if needed -- you never lost it in the first place!
[UIView animateWithDuration:0.3 animations:^{
view.layer.affineTransform = CGAffineTransformMakeScale(10.0, 10.0); // To make a view larger:
otherView.layer.affineTransform = CGAffineTransformMakeScale(0.0, 0.0); // to make a view smaller
}];
and
// To reset views back to their initial size after changing their sizes:
[UIView animateWithDuration:0.3 animations:^{
view.layer.affineTransform = CGAffineTransformIdentity;
otherView.layer.affineTransform = CGAffineTransformIdentity;
}];
As far as I know, that animation is made using screenshots. It updates the frame of the view and simultaneously makes a smooth transition from the app logo to the screenshot from the app. I have imitated the opening of the iPod (music) application from the bottom right corner of the device to the screen size:
UIView * v = [[UIView alloc]init];
CGSize size = self.view.bounds.size;
CGRect frameInitial = CGRectMake(size.width - 30, size.height - 30, 20, 20);
CGRect frameFinal = CGRectMake(0,0, size.width, size.height);
[v setFrame:frameInitial];
Then use the lines below when you want to animate the frame size:
[UIView animateWithDuration:0.3f
delay:0.0f
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{
[v setFrame:frameFinal];
} completion:nil];
Edit: Did not realize that the zooming also included the background. The code below is not tested (I am not at work) so expect some defects and typos.
Imagine you have two layers on the view controller's view. Directly on the vc there is the app you want to be opened, lets call it finalView. And on the top layer there is the window with all apps, which will zoom and fade into your app, which is a view behind it. Lets call it firstView.
Initial cond: firstView has a frame of 320 x 480 (It is a window with all the app icons). It has an alpha of 1. finalView has the same frame and alpha, but it is behind firstView.
Final cond: finalView will still have the same frame and alpha. But firstView will zoom into bottom right corner (will have a huge frame) and fade out (alpha -> 0).
//Initial cond: (Or better yet use IB)
CGRect frameInitial = CGRectMake(0,0, self.view.size.width, self.view.size;
CGRect frameFinal = CGRectMake(self.view.size.width * -4 ,self.view.size.height * -5, self.view.size.width * -5,self.view.size.width * -6);
[v setFrame:frameInitial];
Then use the lines below when you want to animate the frame size:
[UIView animateWithDuration:0.3f
delay:0.0f
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{
[v setFrame:frameFinal];
} completion:nil];
I have small repo that uses a UICollectionViewFloatLayout to create the zoom effect, https://github.com/MichaelQuan/ios7ZoomEffect. It is still a work in progress but the basic idea is there
The layout code is:
#interface ExpandingCollectionViewLayout ()
#property (nonatomic, assign) CGRect selectedCellFrame;
#property (nonatomic, strong) NSIndexPath *selectedIndexPath;
#end
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSArray *layoutAttributes = [super layoutAttributesForElementsInRect:rect];
[layoutAttributes enumerateObjectsUsingBlock:^(UICollectionViewLayoutAttributes *obj, NSUInteger idx, BOOL *stop) {
[self _transformLayoutAttributes:obj];
}];
return layoutAttributes;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes *layoutAttributes = [super layoutAttributesForItemAtIndexPath:indexPath];
[self _transformLayoutAttributes:layoutAttributes];
return layoutAttributes;
}
- (void)_transformLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
{
if (self.selectedIndexPath != nil)
{
if ([layoutAttributes.indexPath isEqual:self.selectedIndexPath]) {
// set the frame to be the bounds of the collectionView to expand to the entire collectionView
layoutAttributes.frame = self.collectionView.bounds;
} else {
//scale = collectionView.size / cell_selected.size
//translate = (scale - 1)(cell_i.center - cell_selected.center) + (collectionView.center - cell_selected.center)
CGRect collectionViewBounds = self.collectionView.bounds;
CGRect selectedFrame = self.selectedCellFrame;
CGRect notSelectedFrame = layoutAttributes.frame;
// Calculate the scale transform based on the ratio between the selected cell's frame and the collection views bound
// Scale on that because we want everything to look scaled by the same amount, and the scale is dependent on how much the selected cell has to expand
CGFloat x_scale = collectionViewBounds.size.width / selectedFrame.size.width;
CGFloat y_scale = collectionViewBounds.size.height / selectedFrame.size.height;
CGAffineTransform scaleTransform = CGAffineTransformMakeScale(x_scale, y_scale);
// Translation based on how much the selected cell has been scaled
// translate based on the (scale - 1) and delta between the centers
CGFloat x_zoomTranslate = (x_scale - 1) * (CGRectGetMidX(notSelectedFrame) - CGRectGetMidX(selectedFrame));
CGFloat y_zoomTranslate = (y_scale - 1) * (CGRectGetMidY(notSelectedFrame) - CGRectGetMidY(selectedFrame));
CGAffineTransform zoomTranslate = CGAffineTransformMakeTranslation(x_zoomTranslate, y_zoomTranslate); //Translation based on how much the cells are scaled
// Translation based on where the selected cells center is
// since we move the center of the selected cell when expanded to full screen, all other cells must move by that amount as well
CGFloat x_offsetTranslate = CGRectGetMidX(collectionViewBounds) - CGRectGetMidX(selectedFrame);
CGFloat y_offsetTranslate = CGRectGetMidY(collectionViewBounds) - CGRectGetMidY(selectedFrame);
CGAffineTransform offsetTranslate = CGAffineTransformMakeTranslation(x_offsetTranslate, y_offsetTranslate);
// multiply translations first
CGAffineTransform transform = CGAffineTransformConcat(zoomTranslate, offsetTranslate);
transform = CGAffineTransformConcat(scaleTransform, transform);
layoutAttributes.transform = transform;
}
}
}
To expand a cell using this layout code, set both the selectedCellFrame and selectedIndexPath to the cell you want expanded and call performBatchUpdates:completion: on the collection view. To collapse set selectedCellFrame = CGRectNull and selectedIndexPath = nil and call performBatchUpdates:completion:
My app has to do a calculation and animation based on a choice the user makes and I need to update a UILabel before it is used in the animation, the problem is whenever I set the UILabel text it messes up the animation and I have no idea why. I have tried [self.view.layer removeAllAnimations]; before the changing the UILabel text and still no good. I even tried programmatically calling an IBAction and changing UILabel text from that block and it still messes up the animation. Nothing seems to work and it is definitely about timing as changing the UILabel text in viewDidLoad does work fine. I also checked if it is a Storyboard problem by changing another UILabel's text before the animation and the same problem occurs. If it helps at all here is the animation code:
-(IBAction) swipeDown:(UISwipeGestureRecognizer *)recognizer{
multiply.transform = CGAffineTransformMakeScale(1.0f, 1.0f);
subtract.transform = CGAffineTransformMakeScale(1.0f, 1.0f);
add.transform = CGAffineTransformMakeScale(1.0f, 1.0f);
divide.transform = CGAffineTransformMakeScale(1.0f, 1.0f);
circle.transform = CGAffineTransformMakeScale(1.0f, 1.0f);
signcircle.frame = CGRectMake(145.0f, 70.0f, signcircle.frame.size.width,signcircle.frame.size.height);
sign.frame = CGRectMake(152.0f, 68.0f, sign.frame.size.width,sign.frame.size.height);
type=#"multiply";
if(fivebutton.highlighted){
circle.transform = CGAffineTransformMakeScale(0.5f, 0.5f);
multiply.transform = CGAffineTransformMakeScale(1.35f, 1.35f);
multiply.frame = CGRectMake(120.0f, 248.0f, multiply.frame.size.width, multiply.frame.size.height);
[UIView animateWithDuration:.25f animations:^{
add.frame = CGRectMake(130.0f, 202.0f, add.frame.size.width, add.frame.size.height);
add.alpha=1.0;
subtract.frame = CGRectMake(82.0f, 255.0f, subtract.frame.size.width, subtract.frame.size.height);
subtract.alpha=1.0;
divide.frame = CGRectMake(178.0f, 255.0f, divide.frame.size.width, divide.frame.size.height);
divide.alpha=1.0;
multiply.transform = CGAffineTransformMakeScale(1.0f, 1.0f);
multiply.frame = CGRectMake(130.0f, 305.0f, multiply.frame.size.width, divide.frame.size.height);
multiply.alpha=1.0;
circle.transform = CGAffineTransformMakeScale(1.0f, 1.0f);
circle.alpha=1.0;
UIImage *downimage = [UIImage imageNamed: #"multiplydown.png"];
[multiply setImage:downimage];
onebutton.alpha=0.1;
one.alpha=0.1;
twobutton.alpha=0.1;
two.alpha=0.1;
threebutton.alpha=0.1;
three.alpha=0.1;
fourbutton.alpha=0.1;
four.alpha=0.1;
sixbutton.alpha=0.1;
six.alpha=0.1;
fivebutton.alpha=0.0;
sevenbutton.alpha=0.1;
seven.alpha=0.1;
eightbutton.alpha=0.1;
eight.alpha=0.1;
ninebutton.alpha=0.1;
nine.alpha=0.1;
zerobutton.alpha=0.1;
zero.alpha=0.1;
decimalbutton.alpha=0.1;
decimal.alpha=0.1;
equalsbutton.alpha=0.1;
equals.alpha=0.1;
}];
[UIView commitAnimations];
}
In this case the UILabel text change causes "add","subtract","divide" and "multiply" (UIImageView's) to merge together instead of spreading as the animation is supposed to.
-(void) fiveTapEnded{
var2.hidden=NO;
if ([type isEqual:#"add"]) {
NSLog(#"Is add");
}
if ([type isEqual:#"subtract"]) {
NSLog(#"Is subtract");
}
if ([type isEqual:#"multiply"]) {
var1.textAlignment = NSTextAlignmentRight;
var1.frame = CGRectMake(40.0f, 70.0f, var1.frame.size.width,var1.frame.size.height);
var2.frame = CGRectMake(145.0f, 70.0f, var2.frame.size.width,var2.frame.size.height);
[UIView animateWithDuration:.25f animations:^{
add.transform = CGAffineTransformMakeScale(0.5f, 0.5f);
add.alpha=0.0;
multiply.transform = CGAffineTransformMakeScale(1.5f, 1.5f);
multiply.alpha=0.0;
divide.transform = CGAffineTransformMakeScale(0.5f, 0.5f);
divide.alpha=0.0;
circle.transform = CGAffineTransformMakeScale(0.5f, 0.5f);
circle.alpha=0.0;
subtract.alpha=0.0;
var1.frame = CGRectMake(0.0f, 20.0f, var1.frame.size.width,var1.frame.size.height);
var2.frame = CGRectMake(185.0f, 20.0f, var2.frame.size.width,var2.frame.size.height);
signcircle.frame = CGRectMake(145.0f, 30.0f, signcircle.frame.size.width,signcircle.frame.size.height);
sign.frame = CGRectMake(152.0f, 26.5f, sign.frame.size.width,sign.frame.size.height);
result.frame = CGRectMake(8.0f, 100.0f, result.frame.size.width,result.frame.size.height);
signcircle.alpha=1.0;
sign.alpha=1.0;
var2.alpha=1.0;
result.alpha=1.0;
one.alpha=1.0;
onebutton.alpha=1.0;
twobutton.alpha=1.0;
two.alpha=1.0;
three.alpha=1.0;
threebutton.alpha=1.0;
fourbutton.alpha=1.0;
four.alpha=1.0;
sixbutton.alpha=1.0;
six.alpha=1.0;
five.alpha=1.0;
fivebutton.alpha=1.0;;
sevenbutton.alpha=1.0;
seven.alpha=1.0;
eightbutton.alpha=1.0;
eight.alpha=1.0;
ninebutton.alpha=1.0;
nine.alpha=1.0;
zerobutton.alpha=1.0;
zero.alpha=1.0;
decimalbutton.alpha=1.0;
decimal.alpha=1.0;
equalsbutton.alpha=1.0;
equals.alpha=1.0;
}];
[self.view.layer removeAllAnimations];
[self functionToBeCalled:self];
}
if ([type isEqual:#"divide"]) {
NSLog(#"Is divide");
}
}
In this case it causes "var1" and "var2" (UILabel's) to not move upwards.
I understand its confusing, I've been searching everywhere and can't find a similar problem so this was kind of my last resort, if anyone has any solution or tips that would be greatly appreciated.
If, by "messes up animation", you mean that it's going back to its starting location, then you probably have autolayout on. The autolayout constraints are automatically reapplied when you change the UILabel, and thus any animations that entail the changing of the frame will be thwarted. Bottom line, when using autolayout with constraints that dictate the layout, you should not be changing frame or center values manually.
That obviously leaves two possible remedies:
Simplest, you could turn off auto layout. Then the setting of the label won't be triggering any constraints to be applied. See NSTimer blocks other animations for very similar issue.
If you want to keep auto layout turned on, then you shouldn't be animating by changing the frame (or center) properties of controls, but rather you should create IBOutlet references for the appropriate constraints, and then animate them by changing the constant property. See Animating an image view to slide upwards
I've hit a wall here. I know how to move an Image using "CGAffineTransformMakeTranslation" and I also know how to scale an image using"CGAffineTransformMakeScale" but for the life of me, I can't seem to get one Image to do both of these and stay that way. It scales to the desired size for about a split second and then immediately reverts to its original size and moves to the desired location. What I need is for the image to get big, STAY big, and then move to a new location (while permanently staying its new size).
Here is what I've got going on in my .m file:
-(IBAction)PushZoomButton {
[UIWindow animateWithDuration:1.5
animations:^{
JustinFrame.transform = CGAffineTransformMakeScale(2.0, 2.0);
JustinFrame.transform = CGAffineTransformMakeTranslation(10.0, 10.0);}];
[UIWindow commitAnimations];}
Any help with this would be appreciated!
you can use CGAffineTransformConcat, for instance:
JustinFrame.transform = CGAffineTransformConcat(CGAffineTransformMakeScale(2.0, 2.0), CGAffineTransformMakeTranslation(10.0, 10.0));
You may need to adapt the translation to (5, 5) since you have doubled the scale
The second transform you set overrides the first one. You need to concat both transform actions into one, as Luis said. Another way of writing that would be:
CGAffineTransform transform = CGAffineTransformMakeScale(2.0, 2.0);
transform = CGAffineTransformTranslate(transform, 10, 10);
JustinFrame.transform = transform;
You may need to look into CoreAnimation, basically what UIView animation is controlling under the hood. If you set up a CAAnimation, then what you want to achieve is done with the fillMode property of the animation.
Here's some example code to make a UIView look like it's opening like a door (copy pasted some code I have, but perhaps you could modify it and find it useful):
- (void) pageOpenView:(UIView *)viewToOpen duration:(NSTimeInterval)duration pageTurnDirection:(PageTurnDirection) p{
// Remove existing animations before stating new animation
[viewToOpen.layer removeAllAnimations];
// Make sure view is visible
viewToOpen.hidden = NO;
// disable the view so it’s not doing anythign while animating
viewToOpen.userInteractionEnabled = NO;
float dir = p == 0 ? -1.0f : 1.0f; // for direction calculations
// create an animation to hold the page turning
CABasicAnimation *transformAnimation = [CABasicAnimation animationWithKeyPath:#"transform"];
transformAnimation.removedOnCompletion = NO;
transformAnimation.duration = duration;
transformAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
CATransform3D startTransform = CATransform3DIdentity;
if (p == NEXT_PAGE) {
// orig values
startTransform.m34 = 0.001f;
}else {
// orig values
startTransform.m34 = -0.001f;
}
// start the animation from the current state
transformAnimation.fromValue = [NSValue valueWithCATransform3D:startTransform];
// this is the basic rotation by 90 degree along the y-axis
CATransform3D endTransform = CATransform3DMakeRotation(3.141f/2.0f,
0.0f,
dir,
0.0f);
// these values control the 3D projection outlook
if (p == NEXT_PAGE) {
endTransform.m34 = 0.001f;
endTransform.m14 = -0.0015f;
}else {
endTransform.m34 = -0.001f;
endTransform.m14 = 0.0015f;
}
transformAnimation.toValue = [NSValue valueWithCATransform3D:endTransform];
// Create an animation group to hold the rotation
CAAnimationGroup *theGroup = [CAAnimationGroup animation];
// Set self as the delegate to receive notification when the animation finishes
theGroup.delegate = self;
theGroup.duration = duration;
// CAAnimation-objects support arbitrary Key-Value pairs, we add the UIView tag
// to identify the animation later when it finishes
[theGroup setValue:[NSNumber numberWithInt:[(BODBookPageView *)viewToOpen pageNum]] forKey:#"animateViewPageNum"]; //STEPHEN: We set the tag to the page number
[theGroup setValue:[NSNumber numberWithInt: p] forKey:#"PageTurnDirection"];
[theGroup setValue:[NSNumber numberWithBool:YES] forKey:#"isAnimationMidpoint"]; // i.e. is this the first half of page-turning or not?
// Here you could add other animations to the array
theGroup.animations = [NSArray arrayWithObjects:transformAnimation, nil];
theGroup.removedOnCompletion = NO; // THIS LINE AND THE LINE BELOW WERE CRUCIAL TO GET RID OF A VERY HARD TO FIND/FIX BUG.
theGroup.fillMode = kCAFillModeForwards; // THIS MEANS THE ANIMATION LAYER WILL STAY IN THE STATE THE ANIMATION ENDED IN, THEREBY PREVENTING THAT ONE FRAME FLICKER BUG.
// Add the animation group to the layer
[viewToOpen.layer addAnimation:theGroup forKey:#"flipViewOpen"];
}
I'm trying to make a slot machine animation where the reels spin. to do this, I'm using drawRect to draw images in a custom class that inherits from UIView. I'm using an nstimer to update the position of the images and calling [self setNeedsDisplay] to update the drawing. In the simulator, it looks very good, however, on the device, it is very laggy. I was wondering if i'm doing something wrong with my method of drawing or is there any better solutions.
- (void)drawRect:(CGRect)rect
{
[image1 drawInRect:CGRectMake(0, image1Position, 98, 80)];
[image2 drawInRect:CGRectMake(0, image2Position, 98, 80)];
[image3 drawInRect:CGRectMake(0, image3Position, 98, 80)];
}
- (void)spin
{
// move each position down by 10 px
image1Position -= MOVEMENT;
image2Position -= MOVEMENT;
image3Position -= MOVEMENT;
// if any of the position <= -60 reset to 180
if(image1Position < -50)
{
image1Position = 180;
}
if(image2Position < -50)
{
image2Position = 180;
}
if(image3Position < -50)
{
image3Position = 180;
}
[self setNeedsDisplay];
}
-(void)beginSpinAnimation
{
timer = [NSTimer scheduledTimerWithTimeInterval:SCROLL_TIME target:self selector:#selector(spin) userInfo:self repeats:YES];
}
My CoreAnimation Attempt with UIScrollView:
- (void) spinToNextReel
{
int y = self.contentOffset.y + 80;
// if the current >= last element reset to first position (-30)
if(y >= (80 *(elementCount+1) - 30))
{
y = -30;
}
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDelegate:self];
[UIView setAnimationDuration:SCROLL_TIME];
[UIView setAnimationCurve:UIViewAnimationCurveLinear];
self.contentOffset = CGPointMake(0, y);
[UIView commitAnimations];
if (!isSpinning && targetY == y)
{
NSLog(#"target is %d, y is %d", targetY, y);
if(timer)
{
[timer invalidate];
timer = nil;
}
[self playSound];
}
}
I would say research into CoreAnimation. It is made to do what you want to do here. It'll be much faster than what you are doing here.
As for it being slow, calling drawInRect isn't the fastest thing in the world. What is SCROLL_TIME?
You want to use CoreAnimation, it will be a lot easier, and more efficient. Having said that, if you insist on trying to manually animate this way you are doing a couple of thing wrong:
Do not attempt move a constant amount on fixed intervals, timer events can be delayed, and if they are that will result in your animation being uneven, since you are moving a constant amount per event, not per time interval. You should record the actual timestamp every time you animate, compare it to the previous timestamp, and move an appropriate number of pixels. This will result in even amounts of movement even if the events are delayed (effectively you will being dropping frames).
Do not use an NSTimer, use a CADisplayLink. That will tie your drawing to the native refresh rate of the system, and synchronize it with any other drawing that is going on.
I know I said it already, but learn and use CoreAnimation, you will be much happier.
This is my first question her so please be gentle:
I have followig animation code running smoothly on the simulator as well as on the real device (I am testng on iPhone 3GS 3.1.2).
Animation is a simple transition between the 2 views, something like book page flipping.
One diffrence betwen simulator an real device (The problem I cannot investigate - solve) is that on real device when animation finishes - after rotation has been done animated view blink (show for a split of second) for a moment before it goes hidden. On the simulator this 'unexpected' blink does not happen.
Here is the animation code:
-(void)flip{
UIView *animatedView;
// create an animation to hold the page turning
CABasicAnimation *transformAnimation = [CABasicAnimation animationWithKeyPath:#"transform"];
transformAnimation.removedOnCompletion = NO;
transformAnimation.delegate = self;
transformAnimation.duration = ANIMATION_TIME;
transformAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
// this is the basic rotation by 90 degree along the y-axis
CATransform3D endTransform = CATransform3DMakeRotation(3.141f/2.0f,
0.0f,
-1.0f,
0.0f);
// these values control the 3D projection outlook
endTransform.m34 = 0.001f;
endTransform.m14 = -0.0015f;
// start the animation from the current state
transformAnimation.fromValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
transformAnimation.toValue = [NSValue valueWithCATransform3D:endTransform];
animatedView = screenShot;
// Create an animation group to hold the rotation and possibly more complex animation in the future
CAAnimationGroup *theGroup = [CAAnimationGroup animation];
// Set self as the delegate to receive notification when the animation finishes
theGroup.delegate = self;
theGroup.duration = ANIMATION_TIME;
// CAAnimation-objects support arbitrary Key-Value pairs, we add the UIView tag
// to identify the animation later when it finishes
[theGroup setValue:[NSNumber numberWithInt:animatedView.tag] forKey:#"animated"];
// Here you could add other animations to the array
theGroup.animations = [NSArray arrayWithObjects:transformAnimation,nil];
theGroup.removedOnCompletion = NO;
// Add the animation group to the layer
if (animatedView.layer.anchorPoint.x != 0.0f)
{
animatedView.layer.anchorPoint = CGPointMake(0.0f, 0.5f);
float yy = animatedView.center.x - (animatedView.bounds.size.width / 2.0f);
animatedView.center = CGPointMake(yy, animatedView.center.y);
}
if(![animatedView isDescendantOfView:self.view])[self.view addSubview:animatedView];
screenShot.hidden = NO;
animatedView.hidden = NO;
[animatedView.layer addAnimation:theGroup forKey:#"flip"];
}
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag {
screenShot.hidden = YES;
}
Try setting theGroup's and / or transformAnimation's fillMode to kCAFillModeForwards:
theGroup.fillMode = kCAFillModeForwards;
This should cause your animation to persist once its active duration has completed. I've used this to remove an end-of-animation flicker before.
Not sure I understand completely what you are seeing but it's possible you're seeing something caused by the speed difference between the device and the simulator. The simulator is considerably faster than the device so something that "blinks" very quickly might do so too fast for the human eye to catch on the simulator.
Oof. That's annoying. I think it is that there's a split-second between when animation stops and when that line of code executes. If only there were an animationWillStop: method! You might try, instead of screenShot.hidden = YES, screenShot.alpha = 0. I doubt it will make any difference speed-wise, but it might be worth a shot? You could also fade out the view as part of the animation, but I don't think you want to do that.
The only other thing I can think of is to setup an NSTimer with an interval just under your animation time. Something like:
[NSTimer scheduledTimerWithTimeInterval:(ANIMATION_TIME - .001) target:self selector:#selector(hideTheView) userInfo:nil repeats:NO];
And then implement that hideTheView method, of course.