I'm experiencing some unusual behaviour when performing an animated transform on a UIImageView. The code in the method below makes the image appear like it's rocking from side-to-side:
-(void) shakeAnimation
{
float degrees = 30; //the value in degrees
imgShake.autoresizingMask = UIViewAutoresizingNone;
[UIView animateWithDuration:0.20f delay:0 options:(UIViewAnimationOptionCurveLinear | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat) animations:^{
imgShake.transform = CGAffineTransformMakeRotation(degrees*M_PI/180);
} completion:^(BOOL finished) {
imgShake.transform = CGAffineTransformMakeRotation(1*M_PI/180);
NSLog(#"Shake finished");
}];
}
The problem comes with where i call the method. If i call the method in viewDidAppear the animation seems to work perfectly...but for other reasons i need to call it in viewDidLoad. When i call the method from viewDidLoad the animation functions but not at the speed specified by animateWithDuration. It's much slower, probably 0.70f. Is there something i could be missing here?
You never need to call it in viewDidLoad. That's simply wrong and it won't work. Move the code into viewDidAppear. If you have reasons to put it into viewDidLoad, fix the reasons!
EDIT: You never know when viewDidLoad is called - it can even be called multiple times for one controller. Usually the problem with slow animations is caused by a collision between two animations. For example, if your controller is animated to the screen by a UINavigationController, your animation will collide with the "push" animation and they will be slow. That's why you are supposed to use viewDidAppear because when this method is called you know that the controller is displayed and the "appear" animation has ended.
Call the method using some delay.
[self performSelector:#selector(shakeAnimation) withObject: afterDelay:2.0]
Related
I'm wandering what is the correct way to do "long" actions in response to user events. For example, I have this slide to cancel block that animates itself off screen over 0.5 seconds. The [self coreDataAction] may take about 0.3 seconds itself.
I want to ensure that the action completes once the user sees the end of the animation ( I do not want the user to accidentally navigate to a different controller or close the app thinking that the action is done).
Where should I put the [self coreDataAction]; in this case? Above the block, within the block or in the completion block?
//should I put it here?
CGPoint slideToCancelCenter = slideToCancel.view.center;
[UIView animateWithDuration:0.5 animations:^{
self.goToSleepButton.center = slideToCancelCenter;
[UIView setAnimationDuration:0.5];
CGPoint sliderCenter = slideToCancel.view.center;
sliderCenter.y += slideToCancel.view.bounds.size.height;
slideToCancel.view.center = sliderCenter;
//should I put it here?
// [self coreDataAction];
} completion:^(BOOL finished) {
//should I put it here?
} ];
A better way to handle this might be to animate the view on-screen then start the coreDataAction in the completion handler. Once the coreDataAction method execution is complete you can call a method to animate the slide to cancel view off-screen.
Assuming [self coreDataAction] executes on the main thread, I would say you should put it on the first line to ensure that the method is complete by the time the animation is done.
I have a subclass of UIScrollView that I'm using for images slideshow, with infinite scrolling and circular slideshow.
I used to animate the transition in this way: (Because I wanted the transition to be slower)
[UIView animateWithDuration:1.0
delay:0 options:(UIViewAnimationCurveEaseOut)
animations:^{
self.scrollView.contentOffset = newOffset;}
completion:NULL];
And it worked just fine.
Then I watched the lecture "Advanced Scrolling Techniques" from WWDC 2011, and they recommend to implement infinite scrolling by overriding layoutSubviews.
So I changed my implementation and override layoutSubviews
Once I did that the transition animation stopped working.
If I comment out my custom layoutSubviews - It's working again!
Why??
What can I do to make my own scrolling animation while overriding layoutSubviews?
Thanks!
OK, I think I found the problem, and a possible fix.
The key to understand the problem is understanding that when you call
[UIView animateWithDuration...]
All the properties that you change inside the animation block, are changed immediately. They do not change as the animation actually executing (visually).
So in my case, I'm animating the contentOffset to my target value.
This operation invokes layoutSubviews immediately.
In my implementation of layoutSubviews I'm checking to see if enough movement has been accumulated. If so - I'm rearranging my ImageViews, and set the contentOffset to its initial value. Once I do that there is nothing to animate anymore, because nothing has changed!
Animate contentOffset to X:
calls setContentOffset to X:
invokes layoutSubview
Sets the contentOffset back to Y! (after rearranging the views)
Nothing to animate at this point...
Possible Fix
I'm not sure if it is the right way to do it, but its working working.
So what we need to do is to ensure that no changes will be made in layoutSubviews while we are in the middle of animation block.
So I created a BOOL instance variable named isAnimating, and set it appropriately in the animation block:
self.isAnimating = YES;
[UIView animateWithDuration:self.transitionDuration
delay:0
options:(UIViewAnimationOptionCurveEaseOut)
animations:^{
self.contentOffset = newOffset;
}
completion:^(BOOL finished) {
self.isAnimating = NO;
[self setNeedsLayout];
}];
Now in layoutSubviews we need to take isAnimating into account:
-(void) layoutSubviews
{
[super layoutSubviews];
if (self.isAnimating == NO)
{
[self rearrangeIfNecessary];
}
}
And it's working again!
i noticed some strange behavior. When i start a animation and change the View (the view will not dismissed!), the completion handler never get called.
[UIView animateWithDuration:0.1f
delay:0.0f
options:UIViewAnimationCurveEaseOut
animations:^(void){
[myView setHidden:YES];
myLabel.alpha = 0.0f;
someOtherView.frame = CGRectMake(130, bubbleBigRect.origin.y, 61, 65);
[button setHidden:YES];
}
completion:^(BOOL finished){
NSLog(#"Complete %d",finished);
[imageVIew setImage:[UIImage imageNamed:#"myPng.png"]];
}];
}
is there any solution for this?
Don't know where to write it, but I get the same thing that you have, but in my case the completion block was sometimes called. It might be same thing.
I found out that if in the animation block has nothing was animate- for example, if you set alpha=0 to uiview that is alpha is already 0, or you set content offset to UIScrollView to the same content offset (like in my case), the completion block not called.
Put this in your animation block, and put everything you want to do on completion in YOUR_SELECTOR method. You can now do whatever you want with your view and still have a way of executing something on completion!
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(YOUR_SELECTOR)];
In my case the view which I was animating has its frame set to (0,-568,320,568) and after animation I forget to change the frame to its required position i.e. (0,0,320,568) so the completion block was not being called. Changing the frame of the animated view did the job for me. So I can say if for instance the view has nothing to show(as the frame was not set in visible region). The completion block may not called.
Move your call to [myView setHidden:YES] from the animations block to the completion block. I think that will probably help. You can still set the alpha of myView to 0 during the animation block (if you want it to fade out), or before the entire call to -[UIView animateWithDuration:delay:options:animations:completion:] if you want it to disappear right away.
I just upgraded my iPhone 4 from iOS 4.2.1 to 4.3.2, and to XCode 4.0.2, and I am encountering some bizarre issues with uiview animations. When I first launch my app, code like this executes perfectly:
[UIView beginAnimations:#"fadeAlphaIn" context:nil];
[UIView setAnimationDuration:0.5f];
viewClue.alpha = 1.0f;
[UIView commitAnimations];
But then, after dismissing a presenting and then dismissing a modal view by the standard method:
[self presentModalViewController:more animated:YES];
and
[self dismissModalViewControllerAnimated:YES];
the first animation no longer works. Instead of fading in, for example, the viewClue view simply jumps from alpha = 0 to alpha = 1. Similarly, other animations altering other views' frame property just force the frame to jump from the initial to final value without animation. These animations worked fine before the modal view was presented and dismissed.
I understand that others have experienced animation issues with the upgrade to iOS 4.3.2, but the way the modal view disrupts animation seems very odd. Has anyone else experienced this problem? Any ideas as to a solution? I'm thinking of just adding the modal view as a subview and animation it as it hides and appears, but using the standard modal view method would be much preferred.
Thanks for your help,
James
EDIT: Some more code showing how the app's map is animated
-(void) viewMapfunc
{
AudioServicesPlaySystemSound(soundID);
if(mapvisible){
[UIView animateWithDuration:0.5
delay:0.1
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
map.frame = CGRectMake(0, 350, 320, 27);
mapscroll.frame = CGRectMake(0, 27, 320, 0);
}
completion:nil];
mapvisible = NO;
viewMapLabel.text = #"View Map";
}else {
[UIView animateWithDuration:0.5
delay:0.1
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
map.frame = CGRectMake(0, 50, 320, 300);
mapscroll.frame = CGRectMake(0, 27, 320, 300);
}
completion:nil];
mapvisible = YES;
viewMapLabel.text = #"Hide Map";
}
}
Try to check two things:
Do you commit all started animations? I got all kinds of strange effects after not committing one of them.
Do any animations take place in the same time? Especially with the same view.
Whether any animations take place right after changing properties. Something like:
-
view.alpha = 1;
[UIView beginAnimations:…];
view.alpha = 0;
[UIView commitAnimations:…];
In this example, view will not change it's alpha value from 1 to 0. It will change it instantly. To start an animation you have to extract animations block to another method and call it with performSelectorInMainThread:withObject:afterDelay:. Delay can be even 0.
I solved it by restarting my animation in my UIView subclass:
override func willMove(toWindow newWindow: UIWindow?) {
if newWindow != nil {
spinner.startSpinning() // Restart any animation here
}
}
In the end, I just removed all modal views and implemented them in other ways. For some reason, using modal views messed up animations. Makes no sense, but removing them fixed the problem. If anyone can enlighten me as to why this is going on, it might be nice for memory concerns...
I had the same issue. The root of my trouble was that my animation was being triggered by a notification, and I was adding an observer on each viewWillAppear, but forgot to remove in viewDidDisappear (remember that iOS 6 no longer calls viewDidUnload reliably).
Essentially, I was calling my animation function twice in quick succession, which was causing the visible irregularity. Hopefully this helps someone out down the line!
I've managed to solve this same issue in my own application.
I noticed while debugging that my UIImageViews which I was animating had different memory addresses before and after I pushed my modal view controller(s). At no other time did these UIImageViews switch their memory addresses.
I thought this might have been the root of the issue and it seems I was right.
My client's code had been allocating/initializing my View Controller's UIImageViews in
-viewDidAppear instead of in -viewDidLoad. Thus, every time I launched and dismissed a modal view controller my UIImageViews I was animating would get reinitialized.
Check for yourself if your map object's memory address is changing before and after you launch your modals, and if it is be sure to move your initialization logic to a more proper section of your code.
Hope this helps you!
Dexter
I was using UIView animateWithDuration: and I solved it by not using the completion block. This is code from a subclassed UIView. In the view controller's viewWillAppear: I set self.shouldAnimate to YES, and in the view controller's viewWillDisappear: I set self.shouldAnimate to NO.
-(void)continueRotate {
if (self.shouldAnimate) {
[self rotateRadarView:self.radarInner];
}
}
-(void)rotateRadarView:(UIView *)view {
[UIView animateWithDuration:0.6 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(continueRotate)];
[view setTransform:CGAffineTransformRotate(view.transform, M_PI_2)];
}completion:nil];
}
I am trying to dismiss a UIView that I have previously added using the same animation block with a fade from alpha 0 -> alpha 100 and [self.view addSubview:newInitiateWindow.view]; The animation block executes correctly when the window is created, but when it is being dismissed, it just halts for 0.75 seconds and then disappears without ever animating. Even if I remove the removeFromSuperview and release calls it doesn't animate the fade to transparent.
Here is the code that should be generating the animation:
[UIView transitionWithView:newInitiateWindow.view
duration:0.75
options:UIViewAnimationOptionCurveEaseIn
animations:^{
newInitiateWindow.view.alpha = 0;
}
completion:^(BOOL finished){
if(finished){
[newInitiateWindow.view removeFromSuperview];
[newInitiateWindow release];
newInitiateWindow=nil;
}
}
];
Were you literal with the 100? It's 1.0 for full alpha, maybe that's causing some issues.
It could be an intricacy with iOS and placement of the code, but it's likely something just being mistyped, could be something indirect with a spelling error
Transitions are supposed to add/remove subviews of the view listed in the transition. They're not supposed to modify properties of the view. You want +animateWithDuration:delay:options:animation:completion: instead.