I'm using [UIView animateWithDuration:...] for animate sequence of UIImageView views. Like this:
[UIView animateWithDuration:1.0 animations:^{
imageView.frame = newImageRectPosition;
}completion:^(BOOL finished){
//animate next UIImageView
}];
I need animate 'next UIImageView' not on completion. I need animate 'next UIImageView' on the middle of previous animation, not on completion. Is it possible to do so?
You can setup two UIView animation blocks, one which has a delay of half the duration of the first animation:
[UIView animateWithDuration:1.0
animations:^{ ... }
completion:^(BOOL finished){ ... }
];
[UIView animateWithDuration:1.0
delay:0.5
options:UIViewAnimationCurveLinear
animations:^{ ... }
completion:^(BOOL finished) { ... }
];
There are many options you can use to achieve the effect you're after. One which comes to mind is the use of timers.
Use an NSTimer with a firing interval half of that of your animation, and get the timer to fire off another animation. As long as the two animations don't interfere with each other, you should be ok.
An example would be as so:
NSTimer* timer;
// Modify to your uses if so required (i.e. repeating, more than 2 animations etc...)
timer = [NSTimer scheduledTimerWithTimeInterval:animationTime/2 target:self selector:#selector(runAnimation) userInfo:nil repeats:NO];
[UIView animateWithDuration:animationTime animations:^{
imageView.frame = newImageRectPosition;
} completion:nil];
- (void)runAnimation
{
// 2nd animation required
[UIView animateWithDuration:animationTime animations:^{
imageView.frame = newImageRectPosition;
} completion:nil];
}
With a timer, this could scale up if you needed to do more than two animations, and it all holds together if you need to change the animation time later on.
Related
I have a horizontal table view and want to change images each 5 secs. I want to change images with fade animation so the old image fades out and the new one fades in. So i call this method:
self.slideTimer = [NSTimer scheduledTimerWithTimeInterval:5
target:self
selector:#selector(slideToNextImage)
userInfo:nil
repeats:YES];
And here is my slideToNextImage:
self.lastIndexPath = indexPath;
[UIView beginAnimations:#"FadeAnimations" context:nil];
[UIView setAnimationDuration:2];
self.horizontalView.alpha = 0.1f;
[self.horizontalView.tableView scrollToRowAtIndexPath:self.lastIndexPath
atScrollPosition:UITableViewScrollPositionMiddle
animated:NO];
[UIView commitAnimations];
[UIView beginAnimations:#"FadeAnimations" context:nil];
[UIView setAnimationDuration:2];
self.horizontalView.alpha = 1.0f;
[UIView commitAnimations];
With my realisation the image fades too fast and i see the second image scrolling with no fade animation
the second animation starts without waiting for the first to end.
Try something like this instead:
[UIView animateWithDuration:2.0 animations:^{
self.horizontalView.alpha = 0.1f;
[self.horizontalView.tableView scrollToRowAtIndexPath:self.lastIndexPath
atScrollPosition:UITableViewScrollPositionMiddle
animated:NO];
} completion:^(BOOL finished) {
[UIView animateWithDuration:2 animations:^{
self.horizontalView.alpha = 1.0f;
}];
}];
I am attempting to have a UIImageView (a picture of a finger) animate from right to left, using CGAffineTransformMakeTranslation(). This animation will repeat until the user performs a swipe, moving the user along in a tutorial. All of this is already working swimmingly as follows:
[UIView animateWithDuration:1.0 animations:^ {
self.finger.alpha = 1.0;
}completion:^(BOOL finished) {
CGAffineTransform originalTransform = self.finger.transform;
[UIView animateWithDuration:1.0 delay:0.0 options:UIViewAnimationCurveEaseInOut animations:^ {
self.finger.transform = CGAffineTransformMakeTranslation(-200, 0);
self.finger.alpha = 0.0;
}completion:^(BOOL finished) {
self.finger.transform = originalTransform;
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:#selector(repeatSwipeAnimation1) userInfo:nil repeats:YES];
self.swipeTimerForFinger1 = timer;
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addTimer:self.swipeTimerForFinger1 forMode:NSDefaultRunLoopMode];
[self.swipeTimerForFinger1 fire];
}];
}];
And the selector:
-(void)repeatSwipeAnimation1 {
[UIView animateWithDuration:1.0 delay:0.0 options:UIViewAnimationCurveEaseIn animations:^ {
self.finger.alpha = 1.0;
}completion:^(BOOL finished) {
CGAffineTransform originalTransform = self.finger.transform;
[UIView animateWithDuration:1.0 delay:0.0 options:UIViewAnimationCurveEaseInOut animations:^ {
self.finger.transform = CGAffineTransformMakeTranslation(-200, 0);
self.finger.alpha = 0.0;
}completion:^(BOOL finished) {
self.finger.transform = originalTransform;
}];
}];
}
The finger animates and translates beautifully.
The issue comes when I want to do this with a different finger with a different timer. I have the same exact code, however it is a different finger and different selector for the timer.
What happens is that the timer's selector will not translate the UIImageView, and (more scary) the timer will not invalidate when I call the method to invalidate. Upon debugging, I see that the the 2nd timer is calling the 2nd selector, but just not behaving (e.g. not translating, and fading in the 2nd finger too rapidly).
What I am assuming is that I need to somehow turn off the NSRunLoop when I first call it? This is the first time working with NSRunLoop so I apologize for my ignorance. Any help is very much appricated.
Well, you certainly have a block retain cycle going on. You need to use either the __block or __weak specifier. See Blocks and Variables. Your problem may be related to a memory issue.
Make sure you invalidate the first timer when it is done.
For safety, you may also want to reset the transform on your UIImageView before attempting to transform it. You can do that like so:
finger.transform = CGAffineTransformIdentity;
I've searched loads of SO stuff and in Apple's references, but still unable to manage my problem.
What I have:
A screen with 2 UIImageViews and 2 UIButtons connected to them
2 kinds of animation:
Scaling up and then down of each image, one after another, only once in viewDidLoad
When a button pressed (a custom button hidden 'inside' of each UIImageView) it triggers animation of appropriate UIImageView–only one, not both–(also scale up, then down).
As I am writing for iOS4+ I'm told to use block based animations!
What I need:
How do I cancel a running animation? I've managed to cancel after all but the last one... :/
Here is my code snippet:
[UIImageView animateWithDuration:2.0
delay:0.1
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
isAnimating = YES;
self.bigLetter.transform = CGAffineTransformScale(self.bigLetter.transform, 2.0, 2.0);
} completion:^(BOOL finished){
if(! finished) return;
[UIImageView animateWithDuration:2.0
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
self.bigLetter.transform = CGAffineTransformScale(self.bigLetter.transform, 0.5, 0.5);
} completion:^(BOOL finished){
if(! finished) return;
[UIImageView animateWithDuration:2.0
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
self.smallLetter.transform = CGAffineTransformScale(self.smallLetter.transform, 2.0, 2.0);
} completion:^(BOOL finished){
if(! finished) return;
[UIImageView animateWithDuration:2.0
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
self.smallLetter.transform = CGAffineTransformScale(self.smallLetter.transform, 0.5, 0.5);
}
completion:^(BOOL finished){
if (!finished) return;
//block letter buttons
[self.bigLetterButton setUserInteractionEnabled:YES];
[self.smallLetterButton setUserInteractionEnabled:YES];
//NSLog(#"vieDidLoad animations finished");
}];
}];
}];
}];
Somehow the smallLetter UIImageView is not working properly, because when pressed (through button) bigLetter is canceling animations properly...
EDIT:
I've used this solution, but still having problem with scaling down smallLetter UIImageView - not cancelling at all...
solution
EDIT2: I've added this at the beginning of next/prev methods:
- (void)stopAnimation:(UIImageView*)source {
[UIView animateWithDuration:0.01
delay:0.0
options:(UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionAllowUserInteraction)
animations:^ {
source.transform = CGAffineTransformIdentity;
}
completion:NULL
];
}
problem stays... :/ no idea how to interrupt last animation for letters in animation chain
You can stop all animations on a view by calling:
[view.layer removeAllAnimations];
(You'll need to import the QuartzCore framework to call methods on view.layer).
If you want to stop a specific animation, not all animations, your best best bet is to use CAAnimations explicitly rather than the UIView animation helper methods, then you will have more granular control and can stop animations explicitly by name.
The Apple Core Animation documentation can be found here:
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CoreAnimation_guide/CreatingBasicAnimations/CreatingBasicAnimations.html
For iOS 10 use UIViewPropertyAnimator to animate.
It provides methods to start, stop and pause UIView animations.
let animator = UIViewPropertyAnimator(duration: 2.0, curve: .easeOut){
self.view.alpha = 0.0
}
// Call this to start animation.
animator.startAnimation()
// Call this to stop animation.
animator.stopAnimation(true)
I'd add to Nick's answer that to make removeAllAnimations smooth next idea be very handy.
[view.layer removeAllAnimations];
[UIView transitionWithView:self.redView
duration:1.0f options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
[view.layer displayIfNeeded];
} completion:nil];
You can try this (in Swift):
UIView.setAnimationsEnabled(false)
UIView.setAnimationsEnabled(true)
Note: you can put code between those two calls if necessary, for example:
UIView.setAnimationsEnabled(false)
aview.layer.removeAllAnimations() // remove layer based animations e.g. aview.layer.opacity
UIView.setAnimationsEnabled(true)
Here is my problem.
I use blocks to display two labels on screen one by one (one READY label appears, then disappears, and GO! label appears, then disappears).
I also have a gesture recognizer to detect if the user is dragging a view.
When my app is displaying the labels, gesture recognizers stop calling their callback.
Here is my code:
[UIView animateWithDuration:1 animations:^{
readyLabel.alpha = 0;
}completion:^(BOOL finished){
[readyLabel removeFromSuperview];
[self.view addSubview:goLabel];
[UIView animateWithDuration:1 animations:^{
goLabel.alpha = 0;
}completion:^(BOOL finished){
self.ball = [[Ball alloc] init];
[self.view addSubview:self.ball];
_timer = [NSTimer scheduledTimerWithTimeInterval:0.02 target:self selector:#selector(moveBall:) userInfo:nil repeats:YES];
}];
}];
What I tried so far is using NSThread to execute my blocks outside of the main thread, but without result.
I could use performSelector:withObject:afterDelay to avoid the problem for my labels (display one label after the first animation has finished) but I think it's a bit dirty.
Why does my gesture recognizer stop calling his callback? Are blocks responsible for this?
This is because block animations disable user interaction. You should use animateWithDuration:delay:options:animations:completion: and specify UIViewAnimationOptionAllowUserInteraction in options.
This is because, when UIView is animating using blocks it prevents UI interaction. To avoid this behaviour you must use the UIViewAnimationOptionAllowUserInteraction option:
[UIView animateWithDuration:1
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
readyLabel.alpha = 0;
}
completion:^(BOOL finished){
[UIView animateWithDuration:1
delay:0.0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{
goLabel.alpha = 0;
}
completion:^(BOOL finished){
self.ball = [[Ball alloc] init];
[self.view addSubview:self.ball];
_timer = [NSTimer scheduledTimerWithTimeInterval:0.02 target:self selector:#selector(moveBall:) userInfo:nil repeats:YES];
}
];
}
];
I have the following code:
[UIView animateWithDuration:2.0 delay:0.0 options:UIViewAnimationOptionAllowUserInteraction
animations:^{
imageView.bounds = endBounds;
}
completion:^(BOOL finished) {
[UIView animateWithDuration:2.0 delay:0.5 options:UIViewAnimationOptionAllowUserInteraction
animations:^{
imageView.bounds = startBounds;
}
completion:^(BOOL finished) {
[imageView removeFromSuperview];
}];
}];
Additionally I have:
[imageView setUserInteractionEnabled:YES];
and a tap gesture recognizer set that will handle the user tapping on imageView. While the first animation is happening, the gesture recognizer fires as I would expect. But if I try and tap imageView during the chained animation from the completion block, nothing happens even though I have set the appropriate option.
Anyone have any thoughts? I've googled and can't find an answer.
When using the new animation blocks, if you want user interaction to be enabled during the animation, you have to set it in the options mask. For example:
[UIView animateWithDuration:1.0
delay:0
options:UIViewAnimationOptionAllowUserInteraction
animations:^{ myView.alpha = 0.5; }
completion:NULL];
I came up with a solution:
I wrap the UIImageView in a UIView (I subclass UIView) with the same bounds/center point as the image. Then I attach the gesture recognizer to the wrapper, instead of the image. Because the wrapper's bounds rectangle/center point never change for the duration of the animation, it's always available as the target of a gesture.
This works quite well.
-j
Do you see the same behaviour if you use:
+ [UIView setAnimationDidStopSelector:]
instead of using blocks?