I am performing an animation when one of my ViewControllers loads. I am calling it in the viewDidAppear method. It looks like this.
[UIView animateKeyframesWithDuration:2.5f delay:0.0f options:0 animations:^{
[UIView addKeyframeWithRelativeStartTime:0.0f relativeDuration:1.0f animations:^{
CGRect rocketFrame = _rocketImageView.frame;
rocketFrame.origin.y = rocketFinishPosition;
_rocketImageView.frame = rocketFrame;
CGRect flameFrame = _flameImageView.frame;
flameFrame.origin.y = rocketFinishPosition + rocketFrame.size.height - 3;
_flameImageView.frame = flameFrame;
_flameImageView.hidden = false;
CGRect earthFrame = _earthImageView.frame;
earthFrame.origin.y = earthFinishPosition;
_earthImageView.frame = earthFrame;
CGRect moonFrame = _moonImageView.frame;
moonFrame.origin.y = moonFinishPosition;
_moonImageView.frame = moonFrame;
}];
[UIView addKeyframeWithRelativeStartTime:0.0f relativeDuration:0.6f animations:^{
_flameImageView.transform = CGAffineTransformMakeScale(1.f, 1.f);
}];
} completion:nil];
The issue is when I first load the ViewController, the animation plays but then when finished it snaps back to the starting position. However if I navigate away from the ViewController and back again, the animation plays and the changes persist.
My Question
Are my subviews being played out twice?
When can I be certain that my ViewController has finished loading?
Assuming not-autolayout, in the completion block, set the view properties to the final, desired states...
// taking the rocket frame as an example:
CGRect rocketFrame = _rocketImageView.frame; // do this with the other frames, too
[UIView animateKeyframesWithDuration:2.5f delay:0.0f options:0 animations:^{
[UIView addKeyframeWithRelativeStartTime:0.0f relativeDuration:1.0f animations:^{
rocketFrame.origin.y = rocketFinishPosition;
_rocketImageView.frame = rocketFrame;
CGRect flameFrame = _flameImageView.frame;
flameFrame.origin.y = rocketFinishPosition + rocketFrame.size.height - 3;
_flameImageView.frame = flameFrame;
_flameImageView.hidden = false;
CGRect earthFrame = _earthImageView.frame;
earthFrame.origin.y = earthFinishPosition;
_earthImageView.frame = earthFrame;
CGRect moonFrame = _moonImageView.frame;
moonFrame.origin.y = moonFinishPosition;
_moonImageView.frame = moonFrame;
}];
[UIView addKeyframeWithRelativeStartTime:0.0f relativeDuration:0.6f animations:^{
_flameImageView.transform = CGAffineTransformMakeScale(1.f, 1.f);
}];
} completion:^(BOOL finished) {
// do this with the other frames, too
_rocketImageView.frame = rocketFrame;
}];
Related
I'm developing an IOS app using objective-c. I came across with an instability problem when animating a view's constraints. The animation works well regarding the final position of the frame, but randomly, the trailing(right) constraint is omitted for the first animation block(left constraint never reach to -20 constant value), however leading(left), top and bottom constraints are working as expected.
Please see the video I uploaded. In the first tap, the animation is working as expected, but in the second tap the issue occurs. Please advice.
Video showing the problem
- (void)animateTransition:(nonnull id<UIViewControllerContextTransitioning>)transitionContext {
// 1
UIView *containerView = transitionContext.containerView;
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
// 2
[containerView addSubview:toViewController.view];
if([toViewController isKindOfClass:[ImageViewController class]]){
ImageViewController *vcImage = (ImageViewController*)toViewController;
vcImage.constraintLeft.constant = 20;
vcImage.constraintTop.constant = self.selectedCardFrame.origin.y + 60.0;
vcImage.constraintRight.constant = 20.0;
vcImage.constraintBottom.constant = 0;//self.CollectionView.bounds.size.height - (self.selectedCardFrame.origin.y + self.selectedCardFrame.size.height);
[toViewController.view layoutIfNeeded];
NSTimeInterval duration = [self transitionDuration:transitionContext];
[toViewController.view layoutIfNeeded];
[UIView animateWithDuration:duration animations:^{
//First animation block
vcImage.constraintRight.constant = -20.0;
vcImage.constraintLeft.constant = -20.0;
vcImage.constraintTop.constant = -20.0;
vcImage.constraintBottom.constant = 0;
[toViewController.view layoutIfNeeded];
//toViewController.configureRoundedCorners(shouldRound: false)
} completion:^(BOOL finished) {
//Second animation block
[UIView animateWithDuration:duration animations:^{
vcImage.constraintLeft.constant = 0;
vcImage.constraintTop.constant = 0.0;
vcImage.constraintRight.constant = 0;
vcImage.constraintBottom.constant = 0;
[toViewController.view layoutIfNeeded];
//toViewController.configureRoundedCorners(shouldRound: false)
} completion:^(BOOL finished) {
[transitionContext completeTransition:finished];
}];
}];
}
}
Updated code after #Sh_Khan 's comment.
- (void)animateTransition:(nonnull id<UIViewControllerContextTransitioning>)transitionContext {
// 1
UIView *containerView = transitionContext.containerView;
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
// 2
[containerView addSubview:toViewController.view];
if([toViewController isKindOfClass:[ImageViewController class]]){
ImageViewController *vcImage = (ImageViewController*)toViewController;
NSTimeInterval duration = [self transitionDuration:transitionContext];
[UIView animateWithDuration:0 animations:^{
vcImage.constraintLeft.constant = 20;
vcImage.constraintTop.constant = self.selectedCardFrame.origin.y + 60.0;
vcImage.constraintRight.constant = 20.0;
vcImage.constraintBottom.constant = 0;
[toViewController.view layoutIfNeeded];
} completion:^(BOOL finished) {
[UIView animateWithDuration:duration animations:^{
//First animation block
vcImage.constraintRight.constant = -20.0;
vcImage.constraintLeft.constant = -20.0;
vcImage.constraintTop.constant = -20.0;
vcImage.constraintBottom.constant = 0;
[toViewController.view layoutIfNeeded];
//toViewController.configureRoundedCorners(shouldRound: false)
} completion:^(BOOL finished) {
//Second animation block
[UIView animateWithDuration:duration animations:^{
vcImage.constraintLeft.constant = 0;
vcImage.constraintTop.constant = 0.0;
vcImage.constraintRight.constant = 0;
vcImage.constraintBottom.constant = 0;
[toViewController.view layoutIfNeeded];
//toViewController.configureRoundedCorners(shouldRound: false)
} completion:^(BOOL finished) {
[transitionContext completeTransition:finished];
}];
}];
}];
}
}
Before setting your imageView try reorienting the photo. Something tells me the image view is having a hard time determining the orientation of the image. I had this happen during a transition once and would explain why you are seeing the behavior only some of the time. I am a bit rusty with Objective C but give this a shot and use it to before setting your imageView image.
-(UIImage*)normalizeImage:(UIImage*)currentImage {
if (currentImage.imageOrientation == UIImageOrientationUp){
return currentImage;
}
UIGraphicsBeginImageContextWithOptions(currentImage.size, NO, currentImage.scale);
[currentImage drawInRect:CGRectMake(0, 0, currentImage.size.width, currentImage.size.height)];
UIImage *_newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return _newImage;
}
I am doing page swipe animation with 2 UIWebViews to render book page effect.
Swipe is fired on next and previous click.
Animation works fine until the next or previous button is clicked multiple times in succession.
Here's what I am doing:
display webview1 by default
on right swipe, make webview1 frame x origin negative to hide it. And make webview2 frame x origin as 0 to display it.
similarly for left swipe.
Here is my code to swipe:
-(void)pageChange
{
#try{
[UIView commitAnimations];
NSString *finalPath;
finalPath = [NSString stringWithFormat:#"%#/%#",self.strBookPath, [arrHtmlPages objectAtIndex:currentPage]];
NSData *htmlData = [NSData dataWithContentsOfFile:finalPath];
__block CGRect basketTopFrame = webViewPage.frame;
[UIView animateWithDuration:DURATION delay:0.0f options:UIViewAnimationOptionCurveEaseOut animations:^{
if (_webview2.frame.origin.x == self.view.frame.size.width) {
basketTopFrame.origin.x = -(self.view.frame.size.width);
[webViewPage setFrame:basketTopFrame];
basketTopFrame.origin.x = self.view.frame.size.width;
_webview2.frame = basketTopFrame;
[_webview2 loadData:htmlData MIMEType:#"text/html" textEncodingName:#"UTF-8" baseURL:[NSURL fileURLWithPath:self.strBookPath isDirectory:YES]];
[UIView animateWithDuration:DURATION delay:0.0 options:UIViewAnimationOptionCurveEaseIn animations:^{
//basketTopFrame = webViewPage.frame;
basketTopFrame.origin.x = 0;
_webview2.frame = basketTopFrame;
//NSLog(#"web1: %f",webViewPage.frame.origin.x);
//NSLog(#"web2 : %f", _webview2.frame.origin.x);
} completion:^(BOOL finished){
basketTopFrame.origin.x = self.view.frame.size.width;
webViewPage.frame=basketTopFrame;
}];
} else {
basketTopFrame.origin.x = -(self.view.frame.size.width);
[_webview2 setFrame:basketTopFrame];
basketTopFrame.origin.x = self.view.frame.size.width;
webViewPage.frame = basketTopFrame;
[webViewPage loadData:htmlData MIMEType:#"text/html" textEncodingName:#"UTF-8" baseURL:[NSURL fileURLWithPath:self.strBookPath isDirectory:YES]];
[UIView animateWithDuration:DURATION delay:0.0 options:UIViewAnimationOptionCurveEaseIn animations:^{
//basketTopFrame = webViewPage.frame;
basketTopFrame.origin.x = 0;
webViewPage.frame = basketTopFrame;
//NSLog(#"web1 in: %f",webViewPage.frame.origin.x);
//NSLog(#"web2 in: %f", _webview2.frame.origin.x);
} completion:^(BOOL finished){
basketTopFrame.origin.x = self.view.frame.size.width;
_webview2.frame=basketTopFrame;
}];
}
} completion:^(BOOL finished){
NSLog(#"web1 in: %f",webViewPage.frame.origin.x);
NSLog(#"web2 in: %f", _webview2.frame.origin.x);
}];
}#catch(NSException *e){
NSLog(#"exception : %#",e.description);
}
}
This code is just for right swipe. Left swipe also has similar code.
- (IBAction)btnNext_click:(id)sender {
[self swipeRight:nil]; //this calls pageChange()
}
When the above method is fired many times with very very short interval(user clicks on it rigorously), then both the webviews get frame x origin as width of device(in my case 768), and both get hidden.
Where am I getting wrong? How do I solve this?
There is easy solution for this: Disable buttons during animation. Look for userInteractionEnabled property for buttons and set it to NO during animation. When animation finishes, enable it again. It is very convenient with block based animations as you will enable it in completion handler. Hope this helps.
I have a problem rotating my UIView on iOS. When I start the animation to rotate, my view always jumps to a new position and starts rotating there.
my transformations:
originalState = myRotatingView.transform;
startAnimation = CGAffineTransformRotate(originalState, degreesToRadians(-50));
forwardAnimation = CGAffineTransformRotate(originalState, degreesToRadians(50));
backwardAnimation = CGAffineTransformInvert(forwardAnimation);
my methods:
- (void) stopAnimation {
[UIView animateWithDuration:0.5 delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
myRotatingView.transform = originalState;
} completion:nil];
rotatingViewState = STOPPED;
}
- (void) startAnimation {
CGAffineTransform transform;
if (rotatingViewState == STOPPED) {
rotatingViewState = FORWARD;
transform = startAnimation;
} else {
if (rotatingViewState == BACKWARDS) {
rotatingViewState = FORWARD;
transform = backwardAnimation;
} else {
rotatingViewState = BACKWARDS;
transform = forwardAnimation;
}
}
[UIView animateWithDuration:1.0 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
myRotatingView.transform = transform;
} completion:^(BOOL finished){
if(finished) {
[self startAnimation];
}
}];
}
This is what it looks like: http://www.youtube.com/watch?v=UR_vYAjhprE
Try:
originalState = myRotatingView.transform;
startAnimation = CGAffineTransformRotate(originalState, degreesToRadians(-50));
forwardAnimation = CGAffineTransformRotate(startAnimation, degreesToRadians(50));
backwardAnimation = CGAffineTransformRotate(forwardAnimation, degreesToRadians(-50));
and pass UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionBeginFromCurrentState as options argument to the animateWithDuration:delay:options:animations:completion method.
I found the problem. I had to deactivate "Use Autolayout" in the File Inspector of my storyboard.
btw: This could be related to iOS 7 and XCode 5.
In UIViewAnimation, so far i used a single image to animate from one place to another like this:
CGRect frame = button.frame;
CGRect frame1 = button1.frame;
frame.origin.x = frame1.origin.x;
frame.origin.y = frame1.origin.y;
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration: 3.0];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
[UIView animateWithDuration:3.0 animations:^{
[button setTransform:CGAffineTransformIdentity];
} completion:^(BOOL finished) {
}];
button.frame = frame;
[UIView commitAnimations];
Here I have nine images in an array:
redAppleArray = #[redApple1,redApple2,redApple3,redApple4,redApple5,redApple6,redApple7,redApple8,redApple9,redApple10];
I have text display random numbers.
My question is how to get the above text value and animate the image using UIViewAnimation? i.e., if text =3, 3 redApple to animate .
To get the value from a label for example do:
int i = [[myLabel text] intValue];
And then use it to get the element in your array:
id *redApple = [redAppleArray objectAtIndex:i];
Try do something lilt this:
-(void)yourMethodToAnimateWithText:(NSString*)numberAsAText {
int = [numberAsAText intValue];
CGRect frame = button.frame;
CGRect frame1 = button1.frame;
frame.origin.x = frame1.origin.x;
frame.origin.y = frame1.origin.y;
// If you want to add some option use [UIView animateWithDuration:delay:option:animation:completion:]
[UIView animateWithDuration:3.0 animations:^{
//[button setTransform:CGAffineTransformIdentity];
button.frame = frame;
//Animate your images, text, etc.
} completion:^(BOOL finished) {
}];
}
Hope this is what you after.
I use the following block of code to slide a UIView down and when finished rotate another UIView.
The second part of the animation, the completion block is only performed once which means the 1st animation is not completed else it would reach the completion block.
On the iphone simulator it looks as if the 1st animation did finish...
can anyone help me figure this out?
my NSLog says:
finished 1st
started 2nd
finished 1st
finished 1st
finished 1st
.
.
.
- (IBAction) move
{
[UIView animateWithDuration:0.7 animations:^{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.7];
[UIView setAnimationRepeatCount:1];
[UIView setAnimationRepeatAutoreverses:NO];
CGPoint pos = movingtTable.center;
float moveDistance = 220.0;
if(!isViewVisible){
//expose the view
pos.y = pos.y+moveDistance;
//disable selection for xy table
xTable.userInteractionEnabled = NO;
yTable.userInteractionEnabled = NO;
//angle = M_PI;
}
else
{
pos.y = pos.y-moveDistance;
xTable.userInteractionEnabled = YES;
yTable.userInteractionEnabled = YES;
//angle = -M_PI;
}
isViewVisible = !isViewVisible;
movingtTable.center = pos;
NSLog(#"finished 1st");
}completion:^(BOOL finished){
NSLog(#"started 2nd");
[UIView animateWithDuration:0.4 animations:^{
//[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.4];
//[UIView setAnimationRepeatCount:1];
//[UIView setAnimationRepeatAutoreverses:NO];
arrows.transform = CGAffineTransformMakeRotation(angle);
}completion:^(BOOL finished){
angle = -angle;
}];
}];
Why are you trying to initialize another UIView animation inside the animateWithDuration block code? Update your code to the following and make sure you're not performing multiple animations of a single view at a time.
- (IBAction) move
{
[UIView animateWithDuration:0.7 animations:^{
CGPoint pos = movingtTable.center;
float moveDistance = 220.0;
if(!isViewVisible){
//expose the view
pos.y = pos.y+moveDistance;
//disable selection for xy table
xTable.userInteractionEnabled = NO;
yTable.userInteractionEnabled = NO;
//angle = M_PI;
}
else
{
pos.y = pos.y-moveDistance;
xTable.userInteractionEnabled = YES;
yTable.userInteractionEnabled = YES;
//angle = -M_PI;
}
isViewVisible = !isViewVisible;
movingtTable.center = pos;
NSLog(#"finished 1st");
}
completion:^(BOOL finished){
NSLog(#"started 2nd");
[UIView animateWithDuration:0.4 animations:^{
arrows.transform = CGAffineTransformMakeRotation(angle);
}completion:^(BOOL finished){
angle = -angle;
}];
}];
BTW: The block code requires some serious refactoring, if you ask me :)
You are mixing and matching paradigms and I believe that is causing the issue you are seeing. You are creating an animation block, but inside of that block you are creating a new animation routine with the 'old' paradigm for running UIView animations. Apple is leading people away from the old paradigm and I would encourage you to ONLY use blocks as well.
This is why the completion block only runs once, the UIView animateWith block code only runs once. However, your internal animation code runs multiple times.
Take out:
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.7];
[UIView setAnimationRepeatCount:1];
[UIView setAnimationRepeatAutoreverses:NO];
If you want your animation block to run several times, then use the full method:
animateWithDuration:delay:options:animations:completion:
Make the delay = 0, and set your options to UIViewAnimationOptionRepeat, or whatever you need to accomplish the number of cycles you want the block to complete.
Here is my suggestion assuming you want it to repeat:
- (IBAction) move
{
[UIView animateWithDuration:0.7
delay:0
options:UIViewAnimationOptionRepeat
animations:^{
CGPoint pos = movingtTable.center;
float moveDistance = 220.0;
if(!isViewVisible) {
//expose the view
pos.y = pos.y+moveDistance;
//disable selection for xy table
xTable.userInteractionEnabled = NO;
yTable.userInteractionEnabled = NO;
//angle = M_PI;
}
else {
pos.y = pos.y-moveDistance;
xTable.userInteractionEnabled = YES;
yTable.userInteractionEnabled = YES;
//angle = -M_PI;
}
isViewVisible = !isViewVisible;
movingtTable.center = pos;
NSLog(#"finished 1st");
}
completion:^(BOOL finished){
NSLog(#"started 2nd");
[UIView animateWithDuration:0.4
animations:^{
arrows.transform = CGAffineTransformMakeRotation(angle);
}
completion:^(BOOL finished){
angle = -angle;
}];
}];
}