Consecutive animations using nested animation blocks - objective-c

I'm looking for a way to implement consecutive animations using nested animation blocks.
Somewhat complicated by happening inside a UIScrollView, the size of three UIImageViews (there are many images, and as I scroll through them I constantly swapping out the images in the UIImageViews).
When a scroll is finished, I want to switch out the image in the (visible) middle UIImageView, three times, then back to the original view. I'm trying it thus:
- (void) doAnimation {
// get the animation frames, along with the current image
NSString *swap1 = #"first.png";
NSString *swap2 = #"second.png";
UIImage *original = currentPage.image;
UIViewAnimationOptions myOptions = UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionAllowUserInteraction;
[UIView animateWithDuration:2.0 delay:2.0 options:myOptions
animations:^{ [currentPage setImage:[UIImage imageNamed:swap1]]; }
completion:^(BOOL finished) {
[UIView animateWithDuration:2.0 delay:2.0 options:myOptions
animations:^{ [currentPage setImage:[UIImage imageNamed:swap2]]; }
completion:^(BOOL finished) {
[UIView animateWithDuration:2.0 delay:2.0 options:myOptions
animations:^{ [currentPage setImage:[UIImage imageNamed:swap1]]; }
completion:^(BOOL finished) {
[currentPage setImage:original]; }]; }]; }];
}
When I run this, there is no duration, no delay, it all happens at once, almost too fast for the eye to see. Could this be because "currentPage" is a UIImageView? (Similar to this question?)

There's no delay because UIImageView.image isn't an animateable property. As such, the UIView animation machinery will have no animations to set up and will just call your completion block immediately.
What sort of animation did you expect? You can attach a CATransition object to the underlying layer to get a simple cross-fade, Just use [imageView.layer addAnimation:[CATransition animation] forKey:nil] to get the crossfade with the default values (you can customize the timing by modifying properties of the CATransition before attaching it to the layer). To achieve the subsequent animations, you can either use the delegate property of CAAnimation (CATransition's superclass) to learn when it's done and fire your second one, or you could just use -performSelector:withObject:afterDelay: to start your next animation step after a user-defined delay. The delegate method is going to be more accurate with regards to timing, but the performSelector method is a bit easier to write. Sadly, CAAnimation doesn't support a completion block.

Another approach for you to transition from one image view to another is by using the block animation function transitionFromView:toView:duration:options:completion as discussed in "Creating Animated Transitions Between Views". You would do this instead of animateWithDuration to change images.

Related

Terminate a UIView transition animation

I am slowly animating a UIImageView from one image to another. Like so:
[UIView transitionWithView:self.backgroundView duration:30 options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
self.backgroundView.image = endImage;
} completion:nil];
Is there some way to terminate the animation partway through, in response to an event?
A certain Tim Oliver tells me this is how you do it:
[self.backgroundView.layer removeAllAnimations];
You can also retrieve the frame the animation ended on using self.layer.presentationLayer.frame, if you need to freeze the view in the partially animated state.

UIView animation stop when UIView willDisappear

I am not much into iOS animation. What I am trying to achieve is a simple message view that slide vertical from bottom of screen to a given y, then after few instants the UIView rollback in vertical to go off screen.
[UIView animateWithDuration:0.5f
animations:^{
self.messageView.frame=CGRectMake(x, y -80, width, height);
}
completion:^(BOOL finished){
if (finished) {
[UIView animateWithDuration:0.5f delay:2.0f options:UIViewAnimationOptionCurveLinear
animations:^{
self.messageView.frame=CGRectMake(x, y + 80, width, height);
}
completion:^(BOOL finished){
// do something...
}
];
}
} ];
This is working fine, but I am having a problem using this mechanism in a iOS UITabBar application: when I change tab, the animation stop, I can infact see that "finished" completion is "false". Therefore the second block is not called, and the message view stays on.
Here are the questions:
my first concern is to understand if the code I have written is correct, regarding the nested animations.
I could solve by ignoring 'finished' and execute code anyway, but I don't feel it is a good idea
within the last completion block, I have put some programming logic, basically I am restoring few UIButtons state, and some other little UI change. At this point I don't know if it is a good idea, it seems not, but how can let the UI knows that the message view has disappeared. NSNotification and KVO seems a bad idea when fast responsive UI change are involved.
You can stop all animations for a layer by sending the layer a removeAllAnimations message.
[sel.view removeAllAnimations];

XCode/Objective-C: Simultaneous animations for instances of UIView?

I have two IBOutlet variables connected to two instances of UIView in Interface Builder, called blankView and fadedBGView in my ViewController.
How can I set up this code so that the blankView instance will fade into fadedBGView, using the UIViewAnimationOptionTransitionCrossDissolve transition, and the two UIViews can simultaneously transform (move) from their current position (they have equal positions) to 0,0?
What's happening is the fade occurs, and then the fadedBGView view abruptly moves to 0,0.
In other words, I'd like to have the first UIView fade into the second, while simultaneously moving up the screen.
Hopefully this is clear enough to answer.
Best...SL
[UIView transitionFromView:blankView
toView:fadedBGView
duration:1.0
options:UIViewAnimationOptionTransitionCrossDissolve
completion:^(BOOL finished) {
[blankView removeFromSuperview];
}];
[UIView commitAnimations];
CGRect frame = fadedBGView.frame;
frame.origin.x = 0;
frame.origin.y = 0;
[UIView animateWithDuration:1.0 animations:^{
NSLog(#"UIView Transition/Move Called");
fadedBGView.frame = frame;
}];
iOS 4 and newer provides block-based animations, which your code is already using:
+[UIView animateWithDuration:animations:];
+[UIView animateWithDuration:animations:completion:];
Within the animation block, you can set multiple destination values. See Apple's UIView documentation for a reference to the animatable properties. So within one block, you can modify frames, and alpha (transparency) values of views. I'm not sure how the cross dissolve animation works, but if it's simply one view going from 0 to 1, and another view going from 1 to 0, that's easy to implement.
Instead of
UIViewAnimationOptionTransitionCrossDissolve
use
UIViewAnimationOptionTransitionCrossDissolve | UIViewAnimationOptionAllowAnimatedContent
UIViewAnimationOptionAllowAnimatedContent option allows additional animations during transition. Make sure your fadedBGView has correct starting frame.

Creating interruptible animation w/ completion block in Objective-C for iOS?

I am trying to create animation behavior wherein a view rotates 45 degrees clockwise and then adds a red circle when the user drags its center above a certain line. Dragging this same view back below the same line would revert it back to its original orientation and then remove the red circle. The view should take 3 seconds to rotate 45 degrees with animation curve UIViewAnimationCurveEaseInOut:
I have two functions that embody this behavior, - (void)viewHasMovedAboveLine:(UIView *)view and - (void)viewHasMovedBelowLine:(UIView *)view, that are called when the view is dragged above or below the line by the user. Both of these functions contain animations with completion handlers, i.e., + (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion. The completion handler adds or removes the red circle as appropriate.
I can get the animation to rotate correctly if the user happens to drag the view back across the line while it is in the middle of an animation by setting options: to UIViewAnimationOptionBeginFromCurrentState and by checking the value of view.layer.animationKeys.count prior to executing the animation and removing all animations if there are any currently executing via [view.layer removeAllAnimations].
However, the completion handler of the prior animation still seems to execute even when using [view.layer removeAllAnimations]. Is there a way to stop both the animation and its completion handler if that animation is currently executing?
I would prefer something more elegant than having to create private properties for each animation, e.g., #property (nonatomic) BOOL animation01IsCurrentlyExecuting and #property (nonatomic) BOOL animation02IsCurrentlyExecuting. The ideal solution would encompass a wide variety of animation scenarios containing both animation code and a completion handler.
ALSO: Is there a way to see how far the animation has progressed when it has been interrupted? I myself am more interested in timing (e.g., the animation was interrupted after 2.1 seconds) so that I can make sure that any further animations are properly timed.
The 'finished' parameter of a UIView animation block is very useful for this case.
[UIView animateWithDuration:0.5 animations:^{
//set your UIView's animatable property
} completion:^(BOOL finished) {
if(finished){
//the animation actually completed
}
else{
//the animation was interrupted and did not fully complete
}
}];
For finding out how long an animation progressed before being interrupted, some methods on NSDate could come in handy.
__block NSDate *beginDate = [NSDate new];
__block NSTimeInterval timeElapsed;
[UIView animateWithDuration:0.5 animations:^{
//your animations
beginDate = [NSDate date];
} completion:^(BOOL finished) {
if(finished){
//the animation actually completed
}
else{
//the animation was interrupted and did not fully complete
timeElapsed = [[NSDate date] timeIntervalSinceDate:beginDate];
NSLog(#"%f", timeElapsed);
}
}];

Moving a UIView

I am trying to move a UIView programmatically using the following method:
-(void)animatePlaybackLine: (int) currentBeat{
CGRect newFrame = CGRectMake(currentBeat*eighthNoteWidth, 0, eighthNoteWidth, 320);
[playbackLine setFrame:newFrame];
NSLog(#"Line animated to %i", currentBeat);
NSLog(#"New frame origin %f", playbackLine.frame.origin.x);
}
When the method is called, the NSLogs show that the variable currentBeat is incrementing as it should, and the frame of playbackLine (my UIView) appears to be moving as it should. However, the object on the screen doesn't move. I have also tried setting the bounds and the center instead of the frame, and all of them have similar results. I also tried using an animation instead of setting the frame, to no avail.
Is there something else I should be doing to make the image onscreen show the changing frame?
I'm guessing you're calling that method in a loop? iOS isn't going to redraw the screen until your code finishes executing. You can't loop like this and see iterative results.
To animate views, you should use the native support UIKit gives you. In particular, check out the Animations section in the UIView class reference.
Try this
[UIView animateWithDuration:0.3
delay:0.0
options: UIViewAnimationCurveEaseOut
animations:^{
datePickerView.frame = imageFrame;
}
completion:^(BOOL finished){
self.datePickerViewFlag = NO;
}];