Separate function for UIView animateWithDuration animations and completion parameters - ios

I am working with animations in iOS7 with objective-c. I am trying to use the animateWithDuration function which has the following definition:
[UIView animateWithDuration:(NSTimeInterval) animations:^(void)animations completion:^(BOOL finished)completion]
I can use this just fine, but it makes my code overly long because I have to put my animation and completion functions all in this declaration. I would like to create a separate function and pass it into the animation function call.
Specifically I would like to be able to have a separate completion function to use with multiple animations, which would also require the ability to pass it the parameter of a the specific view's id.
Could someone explain how to set up a function that can be passed into the animate function, and also what the '^' in the ^(void) and ^(BOOL) means?
Thanks

That ^ indicates a block (note that these are not functions). You can certainly do what you want. You would use:
returnType (^blockName)(parameterTypes) = ^returnType(parameters) {...};
So your code would look something like this:
void (^animations)() = ^{
// Do some animations.
};
void (^completion)(BOOL) = ^(BOOL finished){
// Complete.
};
[UIView animateWithDuration:1 animations:animations completion:completion];
FYI, this is a great reference for block syntax: http://goshdarnblocksyntax.com/

Don't overcomplicate things. Just use this method instead:
[UIView animateWithDuration:1.0 animations:^{
// your animations
}];
Next time you come across a block you have no use for, just put nil in the block.
[UIView animateWithDuration:1.0
animations:^{
// your animations
}
completion:nil];
The ^ means that you are declaring a block in Objective-C.
If you just want to make your method call shorter, you can do this:
void (^myCompletionBlock)(BOOL finished) = ^void(BOOL finished) {
// What you want to do on completion
};
[UIView animateWithDuration:1.0
animations:^{
// your animations
}
completion:myCompletionBlock];

Related

How to use GCD to control animation sequence

I have a View to show and hide to give users some hint.
The show and hide method look some like this:
-(void)show{
[UIView animateWithDuration:3.0f
animations:^{
//do something to show self to give hint;
self.frame = CGRectMake(0,0,0,0);
} completion:nil];
}
-(void)hide{
[UIView animateWithDuration:3.0f
animations:^{
//do something to hide self to give hint;
self.frame = CGRectMake(centerX,centerY,100,100);
} completion:nil];
}
when showing a new view, I must call hide method, and then show method. But the duration delay, 3.0f, will cause some error. I was using methods like this:
dispatch_async(dispatch_get_main_queue(), ^{
[view hide];
});
dispatch_async(dispatch_get_main_queue(), ^{
[view show];
});
I was calling show method right after hide method. Animations cannot execute as the sequence they are submitted to the queue. What I want is the show method executed exactly after the hide method completed. How can I control the order of these two methods.
I think I cannot use the completion handler cause I cannot assure where these two methods are called, or whether the view is shown when I called another show method or hide method.
If I am not clear, any suggestions? I will reedit my questions.
PS:
It's not just a flash. When next show method is called, I can not assure the last view is shown or hide and how long the last view is being shown, that is, if the view is being shown and the hide method has been called and already completed, then the show method is called, the result is right. If the view is being shown, another hint view need to be presented, I will call hide first, then show, since the main_queue is serial but the animation block is executed syncly, so the result is wrong. I am looking for is there some kind of lock in GCD that can help me execute a block after last queued block is completed rather than changing within show and hide method. cause there are many other calls to show and hide method with many different kinds of parameters, I need to fix many places in my code.
If you want to execute one task at a time in the order in which they are added to the queue, Use serial queue.
So you can use a serial queue to execute show and hide task at a time in the added order. Yeah, the main queue is ok for that.
However UIView -animateWithDuration:animations: method is kind of asynchronous call, the method returns immediately. So you need to wait until the completion block was called.
If you want to wait until some tasks were finished, Use dispatch group. But you should avoid to wait like that on the main queue. It blocks the main queue. Bad app.
Thus, you might need to use a serial queue and dispatch group as the following.
properties and initialize
#property (nonatomic, strong) dispatch_queue_t serialQueue;
#property (nonatomic, strong) dispatch_group_t group;
-(void)initQueue {
// create a serial queue
self.serialQueue = dispatch_queue_create("com.example.serialQueue", 0);
// create a dispatch group
self.group = dispatch_group_create();
}
a method that uses the serial queue and the dispatch group
-(void)animateSyncWithDuration:(NSTimeInterval)duration animations:(block_t)animations {
dispatch_async(self.serialQueue, ^{
/*
* This block is invoked on the serial queue
* This block would never be executed concurrently
*/
/*
* Enter the dispatch group
*/
dispatch_group_enter(self.group);
dispatch_async(dispatch_get_main_queue(), ^{
/*
* This block is invoked on the main queue
* It is safe to use UIKit
*/
[UIView animateWithDuration:duration animations:animations completion:^{
/*
* This completion block is invoked on the main queue
* Now leave the dispatch group
*/
dispatch_group_leave(self.group);
}];
});
/*
* Wait until leaving the dispatch group from the UIView animation completion block
* It means it blocks the serial queue
*/
dispatch_group_wait(self.group, DISPATCH_TIME_FOREVER);
});
}
show and hide
-(void)show{
[self animateSyncWithDuration:3.0f animations:^{
//do something to show self to give hint;
self.frame = CGRectMake(0,0,0,0);
}];
}
-(void)hide{
[self animateSyncWithDuration:3.0f animations:^{
//do something to hide self to give hint;
self.frame = CGRectMake(centerX,centerY,100,100);
}];
}
If what you wanted is one action (hide then show itself), you should make just one animation to do this instead of join two animations.
There are two possible solutions.
(1) use animation repeat and auto-reverse (need to reset back to original size in completion callback)
-(void) flash {
CGRect bounds = self.bounds;
[UIView animateWithDuration:1.0f
delay:0.0f
options:UIViewAnimationOptionAutoreverse |
UIViewAnimationOptionRepeat
animations:^{
[UIView setAnimationRepeatCount:1];
self.bounds = CGRectZero;
}
completion:^(BOOL finished) {
self.bounds = bounds;
}];
}
(2) use key frame animation
-(void) flash2 {
[UIView animateKeyframesWithDuration:1.0f
delay:0.0f
options:UIViewKeyframeAnimationOptionCalculationModeLinear
animations:^{
CGRect bounds = self.bounds;
[UIView addKeyframeWithRelativeStartTime:0.0
relativeDuration:0.5
animations:^{ self.bounds = CGRectZero; }];
[UIView addKeyframeWithRelativeStartTime:0.5
relativeDuration:0.5
animations:^{ self.bounds = bounds; }];
}
completion:nil];
}
i am using following way to put delay in function calling.
- (void) doAnimation : (double) delay {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Call your First function here");
});
double delayInSeconds = delay;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
NSLog(#"Call your second function here");
});
}
I may not be fully understanding the use case, but what I think you should be doing here is checking whether the hide operation actually needs to occur. Also, since the hide has an animation duration of 3 seconds in your code, you should create your method with a completion block, so you can do something similar to what I've written below in pseudocode:
- (void)hideIfNeededWithCompletionBlock:((^)())completionBlock {
if (self.isShowing) {
[self hideWithCompletionBlock:^(BOOL didHide) {
if (completionBlock) {
completionBlock();
}
}];
} else {
if (completionBlock) {
//We didn't need to hide anything, so we're done
completionBlock();
}
}
}
Then you can call it like so:
[self hideIfNeededWithCompletionBlock:^(){
[self show];
}];
You can do something similar with the show method if you need that flexibility.
Also, depending on your needs, you can make your method take a BOOL for whether to animate the show/hide and if you pass NO, use a duration of 0.0.
I think its working against the UIView animations API to start wrapping it in dispatch_async blocks when you can handle it all with the API provided.

iphone Animation in SplashScreen

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

UIView animateWithDuration and user interaction

I am running an animation on the iPhone with the below recursive function. When I animate, user interaction is blocked (and when the animation is done, user interaction works). I have been trying to enable user interaction, and have tried
passing the flag UIViewAnimationOptionAllowUserInteraction to animateWithDuration.
defining a function called touchesBegan as of this website. This function is never called (and called in other views when I tap the screen).
running the animation on a different thread with dispatch_async and dispatch_sync as this SO answer specifies. I have tried several methods but am not even sure if it'll work.
putting a UIButton in to detect taps. The function it's linked to isn't called for ~1-2 seconds.
To me, that all sounds like user interaction isn't enabled. How can it be responsive while this animation is running?
This animation is rather long and complex -- it's the whole reason this app exists. Each longAndComplicatedCalculation takes about 1s and this function is called ~30 times.
- (void)startAnimation:(dispatch_block_t)block withUIBlock:(dispatch_block_t)uiBlock iteration:(int)N{
[UIView animateWithDuration:1.0
delay:0.0
options:(UIViewAnimationOptionCurveLinear | UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionAllowAnimatedContent)
animations:^(void) {
[block invoke];
[uiBlock invoke];
}
completion:^(BOOL finished) {
if(FINISHED_IF && N<N_MAX) {
__weak id weakSelf = self;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[weakSelf startAnimation:block withUIBlock:uiBlock iteration:N+1];
}];
}
}
];
}
This function is called with
[self startAnimation:^{
imageChange = [self longAndComplicatedCalculation];
} withUIBlock:^{
self.imageView.image = imageChange;
}
iteration:1];
You are calling both blocks (the block and the uiBlock) from the main thread. If your longAndComplicatedCalculation is blocking the thread the behaviour is normal. You should call your calculation in a separate thread and from there after finishing call the UIThread to initiate the animation.
To make it more clear, this is the way I would do it (without having your exact implementation and testing):
considering your image is declared as property:
#property (nonatomic, retain) UIImage *changedImage;
when you call the animation you can do it like
[NSThread detachNewThreadSelector:#selector(updateImage) toTarget:nil withObject:nil];
and in the function you do the calculation and afterwards call the animation on the UIThread:
- (void)updateImage {
self.changedImage = [self longAndComplicatedCalculation];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self doAnimationAndChangeImage];
}];
}
I hope this helps.

Programmatically linked UIView block-based animations hang at the first time fired on iOS

In my project, the program fire an animation when querying the database, when it receives the response data, it fires another animation automatically to display the data.
-(void)queryDatabase{
//...querying database ...
[UIView animateWithDuration:0.4
delay:0
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{
//...first animation block...
}
completion:^(BOOL finished){
//...first completion block...
}];
}
-(void)receivedResponse:(NSData *)responseData{
[UIView animateWithDuration:0.4
delay:0
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{
//...second animation block...
}
completion:^(BOOL finished){
//...second completion block...
}];
}
My problem is, when the program is initiated and fire the second animation at the first time received response, the "second animation block" is exactly executed, but the "second completion block" is not executed and the screen does not change until about 20 or more seconds passed. After that, when this cycle is invoked again, the second animation will always work correctly. How to fix?
Firstly, all UI code should execute on the main thread. You will get unexpected results if you don't abide by this. I've experienced nothing happening at all when I want some UI code to run, and apps hanging whenever I've mistakenly run UI code on a background thread. There are plenty of discussions on why UI code has to run on the main thread.
If you know you're receivedResponse method is going to execute on a thread other than the main thread, you can easily bring it back to the main thread using Grand Central Dispatch (GCD). GCD is a lot nicer to use than the performSelectorOnMainThread methods...
-(void)receivedResponse:(NSData *)responseData{
dispatch_async(dispatch_get_main_queue(), ^{
// put your UI code in here
});
}

How to flip multiple views at once?

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.

Resources