I successfully created an animation for splash screen with this code:
[super viewDidLoad];
AnimationimageView.animationImages = [NSArray arrayWithObjects:
[UIImage imageNamed:#"splash1.png"],
[UIImage imageNamed:#"splash2.png"],
[UIImage imageNamed:#"splash3.png"],
[UIImage imageNamed:#"splash4.png"], nil];
[AnimationimageView setAnimationRepeatCount:1];
AnimationimageView.animationDuration=3;
[AnimationimageView startAnimating];
I connected this code with my storyBoard.
My question is how I can know if the animation is finished in order to pass to the other view.
You can use the setAnimationDidStopSelector: like this:
[UIView setAnimationDidStopSelector: #selector(animationDidStop: finished: context: )];
And implement the selector:
- (void)animationDidStop: (NSString *)animationID finished:(NSNumber *)finished context: (void *)context
{
//your code here.
}
However based on Apple Docs:
You can specify an animation delegate in cases where you want to
receive messages when the animation starts or stops. After calling
this method, you should call the setAnimationWillStartSelector: and
setAnimationDidStopSelector: methods as needed to register appropriate
selectors. By default, the animation delegate is set to nil.
You primarily use this method to set the delegate for animation blocks
created using the begin/commit animation methods. Calling this method
from outside an animation block does nothing.
Use of this method is discouraged in iOS 4.0 and later. If you are
using the block-based animation methods, you can include your
delegate’s start and end code directly inside your block.
However Apple is NOT suggesting a different solution if you're not using blocks; and also in The Elements sample code, which is upgraded for iOS 6.0 SDK, and updated to adopt current best practices for Objective-C, Apple still uses setAnimationDidStopSelector:.
However there's a good explanation of this discouragement here, suggesting that it's probably because Apple is going to improve block animations much better, and may deprecate setAnimationDidStopSelector in the future.
So if you want to use blocks instead of the current approach, do the following:
[UIView animateWithDuration:1.0
delay: 0.0
options: UIViewAnimationOptionCurveEaseIn
animations:^{
aView.alpha = 0.0;
}
completion:^(BOOL finished){
// Do your setAnimationDidStopSelector stuff here!
}];
I'd take on another approach and use the block-based solutions now supported by UIView. Specifically the method
+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion
can achieve what you want with some modifying of your code. See the reference here link to apple docs
Lets try this code
call this function in viewDidLoad
[self performSelector:#selector(gotoNextView) withObject:nil afterDelay:3];
create a method
-(void)gotoNextView
{
// move to next view code
}
try + (void)setAnimationDidStopSelector:(SEL)selector
Related
I want to call a method after an MKMapView animates to a new MKMapCamera.
I started out by attaching the MKMapCamera using this method:
[self.map setCamera:cam animated:YES];
This method causes the animation but doesn't inform me when the animation finished.
I then tried implementing a callback method by using UIView animation blocks after seeing this SO post:
MKMapCamera *cam = [[MKMapCamera alloc] init];
cam.pitch = 75;
cam.altitude = 125;
[cam setCenterCoordinate:self.location.coordinate];
[UIView animateWithDuration:3.0f animations:^{
self.map.camera = cam;
} completion:^(BOOL finished) {
[self methodToImplement];
}];
The MKMapView still animates, however the methodToImplement is called at the same time.
Thanks!
It appears that the completion handler isn't working right for that case, so you'll need to use the MKMapViewDelegate method for region did change. See WWDC 2013 - Putting Map Kit in Perspective:
Okay now that I fired off this animation to go to the next camera I need to know when that animation completes so that I can then animate to the next camera in our stack.
Well you might think of using the completion handler here but it's going to trip you up.
I know it will.
Don't use that completion handler.
Instead you need to use MKMapViews delegate method which tells you when a region change is completed.
If you're not going to use a lot of different kind of animations, then you'll might be ok with simply using the mapView:regionDidChangeAnimated: method and checking the animated flag to call your 'methodToImplement' (the animated flag will only be true for region change that was due to animation calls, and not user input like dragging the map).
In my own project I have a more complex flow, so I needed a more flexible solution, so I opted to adding an NSMutableArray of NSBlockOperation objects as a property in my view controller containing the MKMapView. Each block operation corresponding to a would be completion handler that we can't use.
In mapView:regionDidChangeAnimated: I just pop the operations one by one and execute them:
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
if (animated) {
dispatch_async(dispatch_get_main_queue(), ^{
while (self.mapRegionAnimationBlocks.count > 0) {
NSBlockOperation* op = [self.mapRegionAnimationBlocks firstObject];
[self.mapRegionAnimationBlocks removeObjectAtIndex:0];
[op start];
}
});
});
and where ever I want to use a completion handler for that block, I just add to that array before calling the animation code:
dispatch_async(dispatch_get_main_queue(), ^{
NSBlockOperation* op = [NSBlockOperation blockOperationWithBlock:^{
[self methodToImplement];
}];
[self.mapRegionAnimationBlocks addObject:op];
});
[UIView animateWithDuration:3.0f animations:^{
self.map.camera = cam;
} completion:NULL];
Note that it's important to use NSMutableArray only from a single thread (e.g. the main thread), because it's not thread-safe.
My solution is a bit of hack, and one which should probably be wrapped in a category or subclass of MKMapView, but I haven't gotten around to that yet.
If you set your map view's delegate, you can then write a mapViewDidFinishLoadingMap:, which "Tells the delegate that the specified map view successfully loaded the needed map data."
- (void)mapViewDidFinishLoadingMap:(MKMapView *)mapView
{
// do whatever you want
}
If you want to know not only when the map data is loaded, but also when the rendering of the map is complete (in iOS7+), you can use mapViewDidFinishRenderingMap, which "Tells the delegate that the map view has finished rendering all visible tiles."
- (void)mapViewDidFinishRenderingMap:(MKMapView *)mapView fullyRendered:(BOOL)fullyRendered
{
// do whatever you want
}
For more information, see MKMapViewDelegate Protocol Reference.
It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 9 years ago.
I want to execute doSomethingElse after the animation is finished. Another constraint is that the animation code can be of different duration. How can I do that? thanks!
-(void) doAnimationThenSomethingElse {
[self doAnimation];
[self doSomethingElse];
}
This for example doesn't work:
animationDuration = 1;
[UIView animateWithDuration:animationDuration
animations:^{
[self doAnimation];
} completion:^(BOOL finished) {
[self doSomethingElse];
}
];
When you are not the author of the animation, you can get a callback when the animation ends by using a transaction completion block:
[CATransaction setCompletionBlock:^{
// doSomethingElse
}];
// doSomething
Use block animations:
[UIView animateWithDuration:animationDuration
animations:^{
// Put animation code here
} completion:^(BOOL finished) {
// Put here the code that you want to execute when the animation finishes
}
];
You need to be able to access the specific instances of the animations you're running in order to coordinate completion actions for each one. In your example, [self doAnimation] doesn't expose us to any animations, so your question cannot be "solved" with what you've provided.
There are few ways to achieve what you want, but it depends on what kind of animations you're dealing with.
As pointed out in other answers, the most common way to execute code after an animation on a view is to pass a completionBlock in: animateWithDuration:completion: Another way to handle property change animations is to set a CATransaction completion block within the scope of the transaction.
However, these particular methods are basically for animating property or hierarchy changes to views. It's the recommended way when your animations involve views and their properties, but it doesn't cover all the kinds of animations you might otherwise find in iOS. From your question, it's not clear as to what kind of animations you're using (or how, or why), but if you are actually touching instances of CAAnimations (a key frame animation or a group of animations), what you'll typically do is set a delegate:
CAAnimation *animation = [CAAnimation animation];
[animation setDelegate:self];
[animatedLayer addAnimation:animation forKeyPath:nil];
// Then implement the delegate method on your class
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
// Do post-animation work here.
}
The point is, how your completion handling is implemented depends on how your animation is implemented. In this case we can't see the latter, so we can't determine the former.
http://developer.apple.com/library/ios/#documentation/UIKit/Reference/UIView_Class/UIView/UIView.html
there is the UIView documentation, scroll down to the
Animating Views with Blocks
and
Animating Views
to see hyperlinks that jump you to different parts of the page that explain how to animate views, the ones that you want are under
"Animating Views with Blocks"
reading the names of the methods makes them self explanatory
From your comments I'd recommend you:
-(void) doAnimation{
[self setAnimationDelegate:self];
[self setAnimationDidStopSelector:#selector(finishAnimation:finished:context:)];
[self doAnimation];
}
- (void)finishAnimation:(NSString *)animationId finished:(BOOL)finished context:(void *)context {
[self doSomethingElse];
}
A user can initiate an animation with a swipe gesture. I want to block duplicate calls to the animation, to make sure that once the animation has started, it cannot be initiated again until it has completed -- which may happen if the user accidentally swipes multiple times.
I imagine that most people achieve this control using a boolean flag (BOOL isAnimatingFlag) in the manner shown at bottom. I've done things like this before in apps many times -- but I never feel 100% certain as to whether my flag is guaranteed to have the value I intend, since the animation uses blocks and it's unclear to me what thread my animation completion block is being run on.
Is this way (of blocking duplicate animations) reliable for multi-thread execution?
/* 'atomic' doesn't
* guarantee thread safety
* I've set up my flag as follows:
* Does this look correct for the intended usage?
*/
#property (assign, nonatomic) BOOL IsAnimatingFlag;
//…
#synthesize IsAnimatingFlag
//…
-(void)startTheAnimation{
// (1) return if IsAnimatingFlag is true
if(self.IsAnimatingFlag == YES)return;
/* (2) set IsAnimatingFlag to true
* my intention is to prevent duplicate animations
* which may be caused by an unwanted double-tap
*/
self.etiIsAnimating = YES;
// (3) start a new animation
[UIView animateWithDuration:0.75 delay:0.0 options:nil animations:^{
// animations would happen here...
} completion:^(BOOL finished) {
// (4) reset the flag to enable further animations
self.IsAnimatingFlag = NO;
}];
}
Disable the gesture if you don't want the user triggering it multiple times
- (void)startTheAnimation:(id)sender
{
[sender setEnabled:NO];
[UIView animateWithDuration:0.75 delay:0.0 options:nil animations:^{
// animations would happen here...
} completion:^(BOOL finished) {
[sender setEnabled:YES];
}];
}
Update
Gestures also have an enabled property so you could use the same idea as if it were a button and change it' enabled state
Animation completion block will always run on the main thread.
In the example in the UIView Class Reference you can see that [view removeFromSuperview] is called directly from the block. That's mean a completion block runs on the main thread as it's the only thread safe to call UI-releated methods.
So you are all good if you calling startTheAnimation only from the main thread. If you not you need to dispatch it on the main thread anyway because you call UI-releated methods in it.
If you need to call startTheAnimation from other threads than main thread you can do something like this:
-(void)startTheAnimation{
dispatch_async(dispatch_get_main_queue(), ^{
// Your code here
});
}
Of course, it's better from user experience point of view to, for example, disable a button or modify the UI in other ways to indicate that an animation is in progress. However, it's all the same code. Whatever you need to do you first need to disable it before an animation starts and the re-enable after it's finished.
Where you call this method, you could try using dispatch_once GCD function:
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[myThingy startTheAnimation];
});
I am using transitionFromView with UIViewAnimationOptionTransitionFlipFromLeft. Now I need to combine number of such transitions in the way so I know the completion of whole transformation.
What is the best way to do that? Can I use something like animateWithDuration, then put multiple transitionFromView in animations block? If yes, then would duration params of both messages interfere with each other? What would mean each of those duration param in that case?
Is there better way to flip multiple views with completion block for all?
You can use a CATransaction to do this easily.
First, add the QuartzCore framework to your target, if you haven't already.
Next, add #import <QuartzCore/QuartzCore.h> to the top of your .m file, if you haven't already.
Execute [CATransaction begin] before the statements that create your transitions. Then set the completion block for the new transaction. Next, create the animations. Finally, commit the transaction. Here's an example:
- (IBAction)flipButtonWasTapped:(id)sender {
[CATransaction begin]; {
[CATransaction setCompletionBlock:^{
NSLog(#"all animations complete!");
}];
[UIView transitionFromView:self.topFrontLabel
toView:self.topBackLabel duration:1
options:UIViewAnimationOptionTransitionFlipFromRight
completion:nil];
[UIView transitionFromView:self.bottomFrontLabel
toView:self.bottomBackLabel duration:1.5
options:UIViewAnimationOptionTransitionFlipFromRight
completion:nil];
} [CATransaction commit];
}
Note that you must set the completion block before you create any animations. The completion block only waits for animations that are added after it is set.
Note also that all methods of CATransaction are class messages. You don't get an object representing the transaction.
Apple rejected my app because:
3.3.1 Applications may only use Documented APIs in the manner prescribed by Apple and must not use or call any private APIs.
Applications must be originally written in Objective-C, C, C++, or
JavaScript as executed by the iPhone OS WebKit engine, and only code
written in C, C++, and Objective-C may compile and directly link
against the Documented APIs (e.g., Applications that link to
Documented APIs through an intermediary translation or compatibility
layer or tool are prohibited).
The non-public API that is included in your application is animationDidStop:finished:context:.
This is my method in which I am using the call to above mentioned method:
- (void)hideMsg
{
// Slide the view off screen
CGRect frame = self.view.frame;
int retractY;
int retractX;
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:.75];
retractY = -190;
retractX = 0;
frame.origin.y = retractY;
frame.origin.x = retractX;
self.view.frame = frame;
//to autorelease the Msg, define stop selector
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(animationDidStop:finished:context:)];
[UIView commitAnimations];
}
I'm using this method to display a sliding message after occurence of certain event.
But I have nowhere defined this method. When I tried to find it was only found in CAAnimation.h, UIView.h.
Has anybody encountered with the same problem? How did you fix it?
The whole point of setAnimationDidStopSelector: is that you are telling the system to call your own custom method when an animation completes. So, if you are going to pass in that selector you need to define that method in your class yourself:
- (void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context
{
// do whatever.
}
Note the documentation for setAnimationDidStopSelector: says you must use a selector of this form, but in reality you can also use a shorter one like mad-dog described. But, it's better to get the animationID and context and other items to examine.
You need to add the method to whatever class that code is in because you are passing self as the animation delegate.
They probably also have an internal UIView method of the same name for some reason, which is why you are being accused of using an undocumented API.
If you need to perform some action (like releasing objects) when the animation has finished you should define your own method then pass a selector for it to UIView setAnimationDidStopSelector.
For example:
-(void) messageSlideFinished {
// do some stuff here
}
Then when setting up the animation you would do
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(messageSlideFinished)];
animationDidStop is an iOS delegate. You should use another name for your own selector.