I want to animate my UIProgressView progression from 0 to 1 during 10 seconds.
Code:
[UIView animateWithDuration:10.0 animations:^{
[_myProgressView setProgress:1 animated:YES];
} completion:(BOOL finished)^{
if (finished) NSLog(#"animation finished);
}];
Animation is working fine except that completion and NSLog are always called instantly.
I tried animateWithDuration: withDelay: but the delay is not respected and executed immediately.
Has anyone encountered the same problem?
Thanks for you help.
Its an old problem but I am still posting the solution here. The solution turned out to be really simple.
All you need to do is:
[_myProgressView setProgress:1];
[UIView animateWithDuration:10.0 animations:^{
[_myProgressView layoutIfNeeded];
} completion:^(BOOL finished){
if (finished) NSLog(#"animation finished");
}];
Here is a good explanation for why things were working incorrectly for you:
http://blog.spacemanlabs.com/2012/08/premature-completion-an-embarrassing-problem/
So you can not use setProgress:animated: method to animate the progress view and get your desired behaviour of completion block as well. However, just setting progress inside the block will not animate it, since progress is not an animatable property.
Refer to apple documentation which says its necessary for the property to be animatable:
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/#//apple_ref/occ/clm/UIView/animateWithDuration:animations:
So you just update the value of progress property outside the animate block and do the layout of the view inside the animate block.
None of these answers really worked for me. I was able to get the progress to animate, but for some reason it had a delay in the beginning (especially for longer durations).
The key for me, was to call layoutIfNeeded inside an animation block and wait on the completion before doing the 2nd animation. This code is called from viewDidLoad. With the following code, I was able to achieve a perfectly smooth timer animation.
// For some reason, the first layoutIfNeeded() needs to be wrapped in an animation block, even though it is supposedly a synchronous call. Otherwise, the progress just jumps right to 100%
UIView.animate(withDuration: 0.0, animations: {
self.progressView.layoutIfNeeded()
}, completion: { finished in
self.progressView.progress = 1.0
UIView.animate(withDuration: self.timerDuration, delay: 0.0, options: [.curveLinear], animations: {
self.progressView.layoutIfNeeded()
}, completion: { finished in
print("animation completed")
})
})
Make it simple instead using NSTimer like,
[progressview setProgress:0 animated:NO];
NSTimer *t=[NSTimer scheduledTimerWithTimeInterval: 1.0f
target: self
selector: #selector(progressupdate)
userInfo: nil
repeats: YES];
Below is the function which updates progress,
-(void)progressupdate{
if(progressview.progress<1){
[progressview setProgress:(progressview.progress+=0.2) animated:YES];
}
else{
[t invalidate];
}
}
Hope it helps....
What i found out is a set of both UIview animations and progress view animatons that work much nicer and animate the progressview smoothly. If you just use the combination of animation and progressview animation it is fired directly without regard to the UIview animation timer.
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
timer = [NSTimer scheduledTimerWithTimeInterval: 1.0f
target: self
selector: #selector(updateTimer)
userInfo: nil
repeats: YES];
}
- (void)updateTimer
{
if (progressView.progress >= 1.0) {
[timer invalidate];
}
[UIView animateWithDuration:1 animations:^{
float newProgress = [self.progressView progress] + 0.125;
[self.progressView setProgress:newProgress animated:YES];
}];
}
Feel free to adjust the animation times to even better and smooth transitions
It's an old question, but I had a similar problem and here is my solution (sorry it's in Swift). The completion part is called when the animation finishes. It's inside a custom UIProgressView class.
override func setProgress(progress: Float, animated: Bool) {
self.progress = progress
if animated {
let duration: NSTimeInterval = 10.0
UIView.animateWithDuration(duration, delay: 0.0, options: UIViewAnimationOptions.CurveLinear, animations: {
self.layoutIfNeeded()
}, completion: { (completed) in
self.animationDuration = nil
})
} else {
self.layoutIfNeeded()
}
}
Related
I have a problem with the setting UIViewAnimationOptionAutoReverse.
Here is my code.
CALayer *aniLayer = act.viewToChange.layer;
[UIView animateWithDuration:2.0 delay:1.0 options:(UIViewAnimationCurveLinear | UIViewAnimationOptionAutoreverse) animations:^{
viewToAnimate.frame = GCRectMake(1,1,100,100);
[aniLayer setValue:[NSNumber numberWithFloat:degreesToRadians(34)] forKeyPath:#"transform.rotation"];
} completion:nil ];
The problem is that, after the animation has reversed, the view jumps back to the frame set in the animation block. I want the view to grow and "ungrow" and stop at its original position.
Is there a solution without programming two consecutive animations?
You have three options.
When you use the -[UIView animateWithDuration:…] methods, the changes you make in the animations block are applied immediately to the views in question. However, there is also an implicit CAAnimation applied to the view that animates from the old value to the new value. When a CAAnimation is active on a view, it changes the displayed view, but does not change the actual properties of the view.
For example, if you do this:
NSLog(#"old center: %#", NSStringFromCGPoint(someView.center));
[UIView animateWithDuration:2.0 animations: ^{ someView.center = newPoint; }];
NSLog(#"new center: %#", NSStringFromCGPoint(someView.center));
you will see that 'old center' and 'new center' are different; new center will immediately reflect the values of newPoint. However, the CAAnimation that was implicitly created will cause the view to still be displayed at the old center and smoothly move its way to the new center. When the animation finishes, it is removed from the view and you switch back to just seeing the actual model values.
When you pass UIViewAnimationOptionAutoreverse, it affects the implicitly created CAAnimation, but does NOT affect the actual change you're making to the values. That is, if our example above had the UIViewAnimationOptionAutoreverse defined, then the implicitly created CAAnimation would animate from oldCenter to newCenter and back. The animation would then be removed, and we'd switch back to seeing the values we set… which is still at the new position.
As I said, there are three ways to deal with this. The first is to add a completion block on the animation to reverse it, like so:
First Option
CGPoint oldCenter = someView.center;
[UIView animateWithDuration:2.0
animations: ^{ someView.center = newPoint; }
completion:
^(BOOL finished) {
[UIView animateWithDuration:2.0
animations:^{ someView.center = oldCenter; }];
}];
Second Option
The second option is to autoreverse the animation like you're doing, and set the view back to its original position in a completion block:
CGPoint oldCenter = someView.center;
[UIView animateWithDuration:2.0
delay:0
options: UIViewAnimationOptionAutoreverse
animations: ^{ someView.center = newPoint; }
completion: ^(BOOL finished) { someView.center = oldCenter; }];
However, this may cause flickering between the time that the animation autoreverse completes and when the completion block runs, so it's probably not your best choice.
Third Option
The last option is to simply create a CAAnimation directly. When you don't actually want to change the final value of the property you're changing, this is often simpler.
#import <QuartzCore/QuartzCore.h>
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position"];
animation.autoreverses = YES;
animation.repeatCount = 1; // Play it just once, and then reverse it
animation.toValue = [NSValue valueWithCGPoint:newPoint];
[someView.layer addAnimation:animation forKey:nil];
Note that the CAAnimation way of doing things never changes the actual values of the view; it just masks the actual values with an animation. The view still thinks it's in the original location. (This means, for example, that if your view responds to touch events, it will still be watching for those touch events to happen at the original location. The animation only changes the way the view draws; nothing else.
The CAAnimation way also requires that you add it to the view's underlying CALayer. If this scares you, feel free to use the -[UIView animateWithDuration:…] methods instead. There's additional functionality available by using CAAnimation, but if it's something you're not familiar with, chaining UIView animations or resetting it in the completion block is perfectly acceptable. In fact, that's one of the main purposes of the completion block.
So, there you go. Three different ways to reverse an animation and keep the original value. Enjoy!
Here's my solution. For 2x repeat, animate 1.5x and do the last 0.5x part by yourself:
[UIView animateWithDuration:.3
delay:.0f
options:(UIViewAnimationOptionRepeat|
UIViewAnimationOptionAutoreverse)
animations:^{
[UIView setAnimationRepeatCount:1.5f];
... animate here ...
} completion:^(BOOL finished) {
[UIView animateWithDuration:.3 animations:^{
... finish the animation here ....
}];
}];
No flashing, works nice.
There is a repeatCount property on CAMediaTiming. I think you have to create an explicit CAAnimation object, and configure it properly. The repeatCount for grow and ungrow would be 2, but you can test this.
Something a long the lines of this. You would need two CAAnimation objects though, one for the frame and one for the rotation.
CABasicAnimation *theAnimation;
theAnimation=[CABasicAnimation animationWithKeyPath:#"transform.rotation"];
theAnimation.duration=3.0;
theAnimation.repeatCount=1;
theAnimation.autoreverses=YES;
theAnimation.fromValue=[NSNumber numberWithFloat:0.0];
theAnimation.toValue=[NSNumber numberWithFloat:degreesToRadians(34)];
[theLayer addAnimation:theAnimation forKey:#"animateRotation"];
AFAIK there is no way to avoid programming two animation objects.
Here is a Swift version of #Rudolf Adamkovič's answer, where you set the animation to run 1.5 times and in the completion block run a 'reverse' animation one final time.
In the below snippet I am translating my view by -10.0 pts on the y-axis 1.5 times. Then in the completion block I animate my view one final time back to its original y-axis position of 0.0.
UIView.animate(withDuration: 0.5, delay: 0.5, options: [.repeat, .autoreverse], animations: {
UIView.setAnimationRepeatCount(1.5)
self.myView.transform = CGAffineTransform(translationX: 0.0, y: -10.0)
}, completion: { finished in
UIView.animate(withDuration: 0.5, delay: 0, options: [], animations: {
self.myView.transform = CGAffineTransform(translationX: 0.0, y: 0.0)
}, completion: nil)
})
I have used the follwing code to rotate a wheel with 26 segments.The problem is that it is not showing the effect of easeIn and ease Out.
- (void) spinWithOptions: (UIViewAnimationOptions) options {
// this spin completes 360 degrees every 2 seconds
[UIView animateWithDuration: 0.5f
delay: 0.0f
options: options
animations: ^{
self.imageToMove.transform = CGAffineTransformRotate(imageToMove.transform, M_PI_2/6.49);
}
completion: ^(BOOL finished) {
if (finished) {
if (animating) {
// if flag still set, keep spinning with constant speed
[self spinWithOptions: UIViewAnimationOptionCurveLinear];
} else if (options != UIViewAnimationOptionCurveEaseOut) {
// one last spin, with deceleration
[self spinWithOptions: UIViewAnimationOptionCurveEaseOut];
}
}
}];
}
- (void) startSpin {
if (!animating) {
animating = YES;
[self spinWithOptions: UIViewAnimationOptionCurveEaseIn];
}
}
- (void) stopSpin {
// set the flag to stop spinning after one last 90 degree increment
animating = NO;
}
An EaseIn causes the animation to begin slowly and then speed up, this should happen only on first animation which is 0.5 sec, afterwards you use UIViewAnimationOptionCurveLinear and only for last animation you use EastOut.
As far as I can see code looks ok, are you sure you can see EaseIn effect in such a short time? Try to set longer duration and see does effect occur.
I am trying to translate this code in my solution to enable a view to gracefully fade when clicked:
[UIView transitionWithView:button
duration:0.4
options:UIViewAnimationOptionTransitionCrossDissolve
animations:NULL
completion:NULL];
button.hidden = YES;
But I can't find the equivalent of transitionWithView under UIView in Xamarin, can someone advise?
I tried this for a different animation but it does not give the option of dissolving:
UIView.SetAnimationTransition(UIViewAnimationTransition.CurlDown, button, true);
Setting the hidden property will just hide the UIView. You can use the following code to animate out your view, and use the callback to execute logic when the animation is done.
UIView.Animate (2, 0, UIViewAnimationOptions.CurveLinear,
() => {
button.Alpha = 0.0f;
},
() => {
// animation complete logic
}
);
The code above will fade out the button view over 2 second, with a delay of 0 seconds using a linear curve.
UIView.Transition(button, 0.4, UIViewAnimationOptions.TransitionCrossDissolve, null,
() => {
button.Hidden = true;
});
UIKit.UIView.Transition
Ref: https://developer.xamarin.com/api/member/UIKit.UIView.Transition/p/UIKit.UIView/System.Double/UIKit.UIViewAnimationOptions/System.Action/System.Action/
I am using transitionWithView animation block to animate view. This block is called when user swipe the view in left or right direction. It works fine. But I want to create my own custom animation like view gets changed from left to right direction. I don't know how to achieve this. I am using UIViewAnimationOptionTransitionCurlUp for right to left swipe
and UIViewAnimationOptionTransitionCurlDown for left to right swipe in UIViewAnimationOptions.
[UIView transitionWithView:self.view duration:0.6 options:UIViewAnimationOptionTransitionCurlUp animations:^{
setDataInLayout();
} completion:^(BOOL finished) {
[scrollViewContent setContentOffset:CGPointZero animated:YES];
[self.delegate updatePaginationCounter:_currentStoneIndex];
}];
You have to implement this animation by yourself. I would use UIView animation API like:
UIView.animateWithDuration(0.6, animations: {
// apply 2D transformation to the view, or use any other animatable property
self.view.transform = CGAffineTransformMakeRotation(fullRotation)
} completion: {finished in
// you completion block
})
Here is the list of UIView's animatable properties. For more complex animations you can use the keyframe-based animation:
UIView.animateKeyframesWithDuration(duration, delay: delay, options: options, animations: {
// add your keyframe animations here
UIView.addKeyframeWithRelativeStartTime(0, relativeDuration: 1/2, animations: {
self.view.transform = ...
})
UIView.addKeyframeWithRelativeStartTime(1/3, relativeDuration: 1/2, animations: {
self.view.transform = ...
})
}, completion: {finished in
// completion block
})
}
I am using [UIView animateWithDuration... ] in order to display a text for each page of my app. Each page got its own text. I'm swiping to navigate between pages. I am using a 1 second dissolve effect to have the text fading in after the page is displayed.
Here's the problem: If I'm swiping during that 1 second (during which the text is fading in) the animation will complete when the next page appears and 2 texts will overlap (the previous and the current).
The solution I'd like to implement is to interrupt the animation if I happen to swipe during its occurrence. I just cannot make it happen. [self.view.layer removeAllAnimations]; does not work for me.
Here's my animation code:
- (void) replaceContent: (UITextView *) theCurrentContent withContent: (UITextView *) theReplacementContent {
theReplacementContent.alpha = 0.0;
[self.view addSubview: theReplacementContent];
theReplacementContent.alpha = 0.0;
[UITextView animateWithDuration: 1.0
delay: 0.0
options: UIViewAnimationOptionTransitionCrossDissolve
animations: ^{
theCurrentContent.alpha = 0.0;
theReplacementContent.alpha = 1.0;
}
completion: ^(BOOL finished){
[theCurrentContent removeFromSuperview];
self.currentContent = theReplacementContent;
[self.view bringSubviewToFront:theReplacementContent];
}];
}
Do you guys know how to make this work? Do you know any other way to address this issue?
You can not directly cancel animations created via +animateWithDuration.... What you want to do is replace the running animation with an instant new one.
You could write the following method, that get's called when you want to show the next page:
- (void)showNextPage
{
//skip the running animation, if the animation is already finished, it does nothing
[UIView animateWithDuration: 0.0
delay: 0.0
options: UIViewAnimationOptionTransitionCrossDissolve | UIViewAnimationOptionBeginFromCurrentState
animations: ^{
theCurrentContent.alpha = 1.0;
theReplacementContent.alpha = 0.0;
}
completion: ^(BOOL finished){
theReplacementContent = ... // set the view for you next page
[self replaceContent:theCurrentContent withContent:theReplacementContent];
}];
}
Notice the additional UIViewAnimationOptionBeginFromCurrentState passed to options:. What this does is, it basically tells the framework to intercept any running animations for the affected properties and replace them with this one.
By setting the duration: to 0.0 the new values are set instantly.
In the completion: block you can then create and set your new content and call your replaceContent:withContent: method.
So, another possible solution would be to just disable interaction during the animation.
[[UIApplication sharedApplication] beginIgnoringInteractionEvents];
[[UIApplication sharedApplication] endIgnoringInteractionEvents];
I would declare a flag like shouldAllowContentToBeReplaced. Set it to false when the animation starts and back true when it's complete. Then say if (shouldAllowContentToBeReplaced) { before you start the animation.