Cancel block in UIView animateWithDuration - cocoa-touch

- (void) startLoading {
[self blink];
}
- (void) blink {
[UIView animateWithDuration:0.5
delay: 0.0
options: UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionCurveEaseOut
animations:^{
//animate stuff
}
completion:^(BOOL finished){
[self blink];
}];
}
- (void) stopLoading {
//What goes here?
}
In my UIView's initWithFrame, I build some loader graphics then start the loader animation from [self startLoading].
The question is now, how do I stop this 'infinite loop'? or what goes in the stopLoading or dealloc method to nicely tear everything down?
When I ignore the fact that a completion block is there and just release my UIView from the super view, everything goes fine for some seconds (more than the 0.5 sec specified) then the app crashes with a message:
malloc: * mmap(size=2097152) failed (error code=12)
error: can't allocate region
** set a breakpoint in malloc_error_break to debug
I have a breakpoint in malloc_error_break and the culprit is the animation block.
I assume that the UIView was released by being removed from the super view and later on the completion block is executed, having a reference to self this is messaging a released object.
I can't find anything in the docs about canceling a 'queued' block.

To cancel, do what you would do with any looping operation you want to be able to cancel: set a flag which you check each time before looping. So:
- (void) stopLoading {
kCancel = YES;
}
And now your completion block looks like this:
completion:^(BOOL finished){
if (!kCancel)
[self blink];
}];
kCancel could be an ivar, a static global variable, whatever.

UIViewAnimationOptionRepeat has magic for u
[UIView animateWithDuration:0.5
delay: 0.0
options: UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionCurveEaseOut | UIViewAnimationOptionRepeat
animations:^{
//animate stuff
}
completion:^(BOOL finished){
NSLog(#"solved ");
}];

I just repeat the animation and kill it after some time with a dispatch block:
- (void)animate // Blink a view three times
{
// We need to stop the animation after a while (0.9 sec)
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 0.9 * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void)
{
[view.layer removeAllAnimations];
view.alpha = 0.0; // Animation clean-up
});
[UIView animateWithDuration:0.15 // 0.15*6=0.9: It will animate six times (three in reverse)
delay:0.0
options:UIViewAnimationOptionAutoreverse | UIViewAnimationOptionRepeat
animations:^{
view.alpha = 1.0; // Animation
}
completion:NULL];
}

Related

Nested UIView animateWithDuration:delay:options:animate:completion not delaying

// Animate moving the cards into position
[UIView animateWithDuration:0.5
delay:0.0
options:UIViewAnimationOptionCurveLinear
animations: ^{ card.frame = cardFrame; }
completion: ^ (BOOL finished) {
if (finished) {
[UIView animateWithDuration:1.0
delay:3.0
options:UIViewAnimationOptionTransitionFlipFromRight
animations: ^ { card.image = [UIImage imageNamed:#"imgBack.png"]; }
completion:NULL];
}
}];
for some reason the second delay just won't delay. and the animation continues instantly any help?
The problem you are having actually has nothing to do with nesting. UIImageView simply cannot animate the setImage: property, and hence the code is executed as soon as it is reached.
If you are OK with simply delaying setting the image property, then go ahead and use:
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
card.image = newImage;
});
If you are looking to animate the image changing, you might wanna look into CATransition, and apply a transition effect to images changing within the image view.

how to spin an image continuously

I have an wheel image in .png format, I want to know how can i animate so that it rotates continuously, I searched on stackoverflow and found certain snippets of code which helped me rotate my image but it wouldn't rotate continuously, it would just rotate for a few seconds and stop, the code as follows
the code in viewdidload
UIImageView *imageToMove =
[[UIImageView alloc] initWithImage:[UIImageimageNamed:#"horo_circle.png"]];
[self.view addSubview:imageToMove];
[self rotateImage:imageToMove duration:5.0
curve:UIViewAnimationCurveEaseIn degrees:180];
and the animation
- (void)rotateImage:(UIImageView *)image duration:(NSTimeInterval)duration
curve:(int)curve degrees:(CGFloat)degrees
{
// Setup the animation
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:duration];
[UIView setAnimationCurve:curve];
[UIView setAnimationBeginsFromCurrentState:YES];
// The transform matrix
CGAffineTransform transform =
CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(degrees));
image.transform = transform;
// Commit the changes
[UIView commitAnimations];
}
and the following lines after the import
#define M_PI 3.14159265358979323846264338327950288 /* pi */
#define DEGREES_TO_RADIANS(angle) (angle / 180.0 * M_PI)
You are better of doing this with a CABasicAnimation:
if ([self.spinnerOverlay animationForKey:#"SpinAnimation"] == nil) {
CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
animation.fromValue = [NSNumber numberWithFloat:0.0f];
animation.toValue = [NSNumber numberWithFloat: 2*M_PI];
animation.duration = 10.0f;
animation.repeatCount = INFINITY;
[self.spinnerOverlay.layer addAnimation:animation forKey:#"SpinAnimation"];
}
In this code I check whether the animation is all ready set, not need to set it again.
The spinnerOverlay is in your case the UIImageView you want to rotate.
To stop the animation:
[self.spinnerOverlay.layer removeAnimationForKey:#"SpinAnimation"];
The code bellow will spin a UIImageView called iconView continiously until rotate == NO.
The image will always return to its original position.
- (void)performRotationAnimated
{
[UIView animateWithDuration:0.5
delay:0
options:UIViewAnimationOptionCurveLinear
animations:^{
self.iconView.transform = CGAffineTransformMakeRotation(M_PI);
}
completion:^(BOOL finished){
[UIView animateWithDuration:0.5
delay:0
options:UIViewAnimationOptionCurveLinear
animations:^{
self.iconView.transform = CGAffineTransformMakeRotation(0);
}
completion:^(BOOL finished){
if (_rotate) {
[self performRotationAnimated];
}
}];
}];
}
This is a variation on the same theme.
(Total duration: 2 * 0.5 = 1 sec for a spin)
Works like magic!
Here is another way to do it with a timer.
In your viewController.h file, you'll need to declare your timer and image:
#interface ViewController : UIViewController
{
IBOutlet UIImageView *spinningImage;
NSTimer *spinTimer;
}
Next, in your ViewController.m, you add this code and can make the spin go faster by lowering the time interval or by increasing the transform interval.
-(void) viewDidLoad {
spinTimer = [NSTimer scheduledTimerWithTimeInterval:.01 target: self selector:#selector(spinVoid) userInfo:nil repeats:YES];
}
-(void) spinVoid {
spinningImage.transform = CGAffineTransformRotate(spinningImage.transform, 0.001);
}
Hope this helps!
Now that we're at iOS6, please consider switching to block based animations (that are available since iOS 4.0) rather than the classical method. Try this:
[UIView animateWithDuration:duration delay:0.0 options:UIViewAnimationOptionRepeat animations:^{
image.transform = CGAffineTransformMakeRotation(DEGREES_TO_RADIANS(degrees));
} completion:nil];
Using the method you've chosen for animations :
[UIView setAnimationRepeatCount:10000000];
or, you can specify the UIViewAnimationOptionRepeat flag in the blocks method animateWithDuration:delay:options:animations:completion:

Objective-C: Adding a delay

I'm trying to add a short delay to my app. It's just the splash screen fade out so it won't affect any other functionality in the app (since nothing else is running). I've tried a variety of approaches with no success. (This is happening in viewDidLoad of a viewController):
C's sleep:
...
//add the splash screen
[self.view addSubview:splashScreen];
sleep(3);
[self fadeOut:splashScreen];
NSObject's performSelector (thought this would work because doesn't UIViewController inherit from NSObject?)
[self performSelector:#selector(fadeOut:) afterDelay:3];
NSTimeInterval:
//wait 3 seconds
NSTimeInterval theTimeInterval = 3;
[self fadeOut:splashScreen withADelayOf:&theTimeInterval];
Here is fadeOut (written to work with the NSTimeInterval example)
- (void) fadeOut:(UIView *)viewToToggle withADelayOf:(NSTimeInterval* ) animDelay {
[UIView setAnimationDelay:*animDelay];
[UIView animateWithDuration:0.25 animations:^{
viewToToggle.alpha = 0.0;
}];
}
I get the fadeOut but not the delay. Can someone nudge me in the right direction. Thanks.
you can try dispatch_after or animateWithDuration:delay:options:animations:completion:
double delayInSeconds = 0.5;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
//yourcode
});
or
[UIView animateWithDuration:0.175 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
//your code
}completion:^(BOOL completed){
//animation completion execution
}];
You should rely on this method if you want to animate some properties of your view with some delay:
+(void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion
Have a look to the ref doc here.
So your code could be something like this:
[UIView animateWithDuration:0.25 delay:3.0 options: UIViewAnimationOptionCurveLinear animations:^{
viewToToggle.alpha = 0.0;
} completion: nil];
Tiguero and J2TheC both pointed me in the exact direction I needed to go:
Here's the code I used in case someone else needs the assist:
//add the splash screen
[self.view addSubview:splashScreen];
//fade it out with a delay
[UIView animateWithDuration:0.75 delay:3.0 options:UIViewAnimationOptionCurveEaseIn
animations:^{
splashScreen.alpha = 0.0;
}
completion:^ (BOOL finished) {
//do something on end
}];
Do One thing:
Make an uiimageview ref in the Appdelegate and in the didfinishLunching do this:
// Dont assign the main viewcontroller to the windwo here.
{
img = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"splash.png"]];
img.frame = CGRectMake(0, 0, 320, 480);
[self.window addSubview:img];
[NSTimer scheduledTimerWithTimeInterval:3 target:self selector:#selector(removeSplash) userInfo:nil repeats:NO];
[self.window makeKeyAndVisible];
}
- (void)removeSplash{
[img removerFromSuperview];
self.window.rootViewController = self.viewController;
}

Triggering a method after a delay without abusing [UIView animateWithDuration]?

I have a UIViewController that should control 2 UIImageViews to fade in.
I managed to animate them using the UIView animateWithDuration method, like this:
[UIView animateWithDuration:1.5
animations:^{
[mFrontpageTitle setAlpha:0];
[mFrontpageTitle setAlpha:1];
}
completion:^(BOOL finished){
[self AnimatePause];
}];
and
-(void)AnimatePause {
[UIView animateWithDuration:5.0
animations:^{
[mFrontpageTitle setAlpha:0.99];
[mFrontpageTitle setAlpha:1];
}
completion:^(BOOL finished){
[self AnimateAuthor];
}];
which fires the next animation. Now, creating a transition from .99 to 1.0 is not very clean.
Is there a better way to trigger a block or sending a message by just defining a duration?
thanks
Zuppa
NSTimeInterval delay = 1.5; //in seconds
[self performSelector:#selector(myMethod) withObject:nil afterDelay:delay];

I want to flash an UIImageview on the iPad, how do I do that?

I want to let a UIImageView flash several times.
Currently I don't know how to do that.
Actual code:
-(void)arrowsAnimate{
[UIView animateWithDuration:1.0 animations:^{
arrow1.alpha = 1.0;
arrow2.alpha = 1.0;
arrow3.alpha = 1.0;
NSLog(#"alpha 1");
} completion:^(BOOL finished){
[self arrowsAnimate2];
}];
}
-(void)arrowsAnimate2{
[UIView animateWithDuration:1.0 animations:^{
arrow1.alpha = 0.0;
arrow2.alpha = 0.0;
arrow3.alpha = 0.0;
NSLog(#"alpha 0");
} completion:^(BOOL finished){;}];
}
later on I call it like this:
for (int i = 0;i < 10; i++){
[self arrowsAnimate]; }
This gives me 10x alpha 1, and then 10x alpha 0. In the middle we see only one animation.
Any suggestions?
Thanks.
There is a simpler way to achieve a flashing animation using only 1 animation block:
aview.alpha = 1.0f;
[UIView animateWithDuration:0.5f
delay:0.0f
options:UIViewAnimationOptionAutoreverse
animations:^ {
[UIView setAnimationRepeatCount:10.0f/2.0f];
aview.alpha = 0.0f;
} completion:^(BOOL finished) {
[aview removeFromSuperview];
}];
The trick is to use [UIView setAnimationRepeatCount:NTIMES/2]; *_inside_* your animation block.
No need for extra functions or extra loops.
Use
+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion
and pass the UIViewAnimationOptionRepeat and probably UIViewAnimationOptionAutoreverse in your options. You shouldn't need to provide a completion block and only perform the first animation.
Edit: here is some sample code for an image that fades in and out indefinitely.
[UIView animateWithDuration:1.0
delay:0.0
options:(UIViewAnimationOptionRepeat | UIViewAnimationOptionAutoreverse)
animations:^{
self.myImageView.alpha = 1.0;
}
completion:NULL];
Edit 2: I see you actually need to flash it 10 times only. I wasn't able to do that with blocks actually. When the completion block executed, the animation seemed to complete instantly the remaining 9 times. I was however able to do this with just the old-style animations quite easily.
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:1.0];
[UIView setAnimationRepeatCount:10.0];
[UIView setAnimationRepeatAutoreverses:YES];
self.myImageView.alpha = 1.0;
[UIView commitAnimations];
Edit 3: I found a way to do this with blocks.
- (void)animate
{
if (self.animationCount < 10)
{
[UIView animateWithDuration:1.0
animations:^{
self.myImageView.alpha = 1.0;
}
completion:^(BOOL finished){
[self animateBack];
}];
}
}
- (void)animateBack
{
[UIView animateWithDuration:1.0
animations:^{
self.myImageView.alpha = 0.0;
}
completion:^(BOOL finished){
self.animationCount++;
[self animate];
}];
}
Above blinking may not work when your app go background and foreground at time of blinking.
Instead of these you can take a transparent image and your actual image and animate your ImageView
UIImageView *imageview=[UIImageView new];
imageview.animationDuration=1;
imageview.animationImages = your array of images;
[imageview startAnimating];
You need to wait for an animation to complete before launching a new one. You could chain your completion block in animate2 to go back to animate, and stop based on a counter property, implementing your loop in the animate/completion blocks instead of a separate loop.