Changing UILabel text messes up animation? - iphone

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

Related

animation similar to opening app in ios7

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:

creating a bar with core animation

So I am trying to create an animated bar graph using apple core animation. The bar is just basically a rectangular figure, which have a value of 0-100%. When it first appears I wanted it to show an animation going from 0 to x %. How can I draw a rectangular form like this?
UPDATE:
Most probably I will have a bar as an image, so I need to animate this image to a certain height...
If your requirements are really that simple, you could create a view, set its background color and adjust (or animate) its frame.width (or height) as needed.
Of course there are more elaborate ways to do this, but no need to over-engineer for a simple problem.
This should do exactly what you want:
- (UIImageView*)createNewBarWithValue:(float)percent atLocation:(CGPoint)location
{
UIImageView *newBar = [[[UIImageView alloc] initWithFrame:CGRectMake(location.x, location.y, 50, 200)] autorelease];
newBar.image = [UIImage imageNamed:#"bar.png"];
CABasicAnimation *scaleToValue = [CABasicAnimation animationWithKeyPath:#"transform.scale.y"];
scaleToValue.toValue = [NSNumber numberWithFloat:percent];
scaleToValue.fromValue = [NSNumber numberWithFloat:0];
scaleToValue.duration = 1.0f;
scaleToValue.delegate = self;
newBar.layer.anchorPoint = CGPointMake(0.5, 1);
[newBar.layer addAnimation:scaleToValue forKey:#"scaleUp"];
CGAffineTransform scaleTo = CGAffineTransformMakeScale( 1.0f, percent );
newBar.transform = scaleTo;
return newBar;
}

Animation on a layer that doesn't want to be removed

I have a mask that I've build thanks to a layer and an image. I want to move this layer with little "jumps" (without a nice translation animation).
The problem is that there is an animation happening whenever I move the frame of the mask layer. I've tried calling [maskLayer removeAllAnimations]; but it doesn't change anything.
In my code, I don't put any animation at all, so I don't understand where this animation comes from.
When I launch this function :
-(void) moveAlternativeMask:(CALayer*) maskLayer {
NSLog (#"moveAlternativeMask %#", NSStringFromCGRect(maskLayer.frame));
// Change the frame of the layer
CGRect originalFrame = maskLayer.frame;
CGFloat newYPos = maskLayer.frame.origin.y + 5.;
maskLayer.frame = CGRectMake (originalFrame.origin.x, newYPos, originalFrame.size.width, originalFrame.size.height);
// Try removing all animations on the layer
NSLog (#"Animations");
for (NSString* animationKey in maskLayer.animationKeys) {
NSLog(#" Animation With Key %#", animationKey);
}
NSLog (#"End animations");
[maskLayer removeAllAnimations];
NSLog (#"Animations after removing");
for (NSString* animationKey in maskLayer.animationKeys) {
NSLog(#" Animation With Key %#", animationKey);
}
NSLog (#"End animations after removing");
// Launch the animation with a small delay
[self performSelector:#selector(moveAlternativeMask:) withObject:maskLayer afterDelay:0.5];
}
I get this log :
----moveAlternativeMask {{0, 200}, {768, 643}} ----
Animations
Animation with key : position
End animations
Animations after removing
End animations after removing
---moveAlternativeMask {{0, 205}, {768, 643}}
Animations
Animation with key : position
End animations
Animations after removing
End animations after removing
---moveAlternativeMask {{0, 210}, {768, 643}}
Animations
Animation with key : position
End animations
Animations after removing
End animations after removing
So the layer is moved as I want, the only problem is that the "position" animation keep coming back...
Note that the CALayer was build like this :
UIImage *maskImage = [UIImage imageNamed:#"masque2.png"];
self.maskLayer = [CALayer layer];
self.maskLayer.contents = (id) maskImage.CGImage;
self.maskLayer.frame = CGRectMake(0, ORIGINAL_Y_POSITION, 768, 643);
[myView.layer setMask:self.maskLayer];
Hmm, very strange. I have 2 ideas:
Is it feasible to try doing this with a UIView instead?
Could you perhaps try removeFromSuperview and addSubview instead of just changing the frame?

iphone - Focus effect (just like UIAlertView)

I know title of my question is so bad, but I don't know how to describe it.
When an UIAlertView pops up, anything else on the screen (except the UIAlertView) becomes a bit darker but can be seen. I call this as Focus effect, because you will know clearly and directly that now the UIAlertView is the focus.
So how can I implement such a focus effect?
thanks
Just add a translucent view below the view you want to "focus" on.
Simple example:
UIView *shieldView = [[[UIView alloc] initWithFrame:myView.bounds] autorelease];
shieldView.backgroundColor = [UIColor colorWithWhite:0.0 alpha:0.7];
[myView.superview insertSubview:shieldView belowSubview:myView];
UIAlertView actually uses an image with a radial gradient instead of a simple color, in order to highlight the center of the view.
I know this post is a bit old but I thought it might help someone.
Use this code to generate the radial gradient background:
- (UIImage *)radialGradientImage:(CGSize)size start:(float)start end:(float)end centre:(CGPoint)centre radius:(float)radius{
UIGraphicsBeginImageContextWithOptions(size, YES, 1);
size_t count = 2;
CGFloat locations[2] = {0.0, 1.0};
CGFloat components[8] = {start, start, start, 1.0, end, end, end, 1.0};
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGGradientRef grad = CGGradientCreateWithColorComponents (colorSpace, components, locations, count);
CGColorSpaceRelease(colorSpace);
CGContextDrawRadialGradient (UIGraphicsGetCurrentContext(), grad, centre, 0, centre, radius, kCGGradientDrawsAfterEndLocation);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
CGGradientRelease(grad);
UIGraphicsEndImageContext();
return image;}
Define gradient in the .h file like so:
UIImageView *gradient;
Call your gradient like so:
- (void)addGradient{
CGSize size = self.view.bounds.size;
CGPoint centre = CGPointMake(self.view.bounds.size.width/2, self.view.bounds.size.height/2);
float startColor = 1.0f;
float endColor = 0.0f;
float radius = MIN(self.view.bounds.size.width/4, self.view.bounds.size.height/4);
gradient = [[UIImageView alloc] initWithImage:[self radialGradientImage:size
start:startColor
end:endColor
centre:centre
radius:radius]];
[gradient setBackgroundColor:[UIColor clearColor]];
[gradient setUserInteractionEnabled:YES];
[gradient setAlpha:0.6f];
[self.view addSubview:gradient];}
UIAlertView works like this. It fades in an alpha mask image to dim out the background. Once that animation is finished it starts the "bounce in" animation of the dialog.
So to reproduce it you need first to generate an alpha mask with a "bright spot" where your dialog will end up and fade that in. Then use a (few) frame animation(s) to get the bounce effect.
More info here: Creating a Pop animation similar to the presentation of UIAlertView
To make it better than "not good" you could ...
create a UIView in a nib (easiest if the part of your code where you need the effect is already utilising a nib) and then add a translucent graphic (with a 'focus' effect) to that view.
connect the UIView in the nib to an IBOutlet
fade in the graphic using an animation into view hierarchy (omz example shows this)

UIView scaling during animation

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.