Can I stop UIImageView Animation at last frame? - iphone

I have an animation using a UIImageView:
myAnimatedView.animationImages = myImages;
myAnimatedView.animationDuration = 1;
myAnimatedView.animationRepeatCount = 1;
[myAnimatedView startAnimating];
How can I tell to animation to stop at the last frame (keeping the last image in the series visible)?

the image property on the UIImageView class has the following docs:
"If the animationImages property contains a value other than nil, the contents of this property are not used."
So the trick to hold on the last frame of an animation in iOS4 is to first set the image property to that last frame (while animationImages is still nil), then set the animationImages property and call startAnimating. When the animation completes, the image property is then displayed. No callback/delegate needed.

You can set image of last frame to your UIImageView instance and then when animation will finish last frame image remains visible. Here is the code:
// create your array of images
NSArray *images = #[[UIImage imageNamed:#"video1.png"], [UIImage imageNamed:#"video2.png"]];
UIImageView *imageView = [UIImageView new];
imageView.image = images.lastObject;
imageView.animationImages = images;
imageView.animationDuration = 1.0; // seconds
imageView.animationRepeatCount = 1;
[imageView startAnimating];

After the animation finishes you can set your UIImageView image to the last frame of your animation.
myAnimatedView.image = [myAnimatedImages objectAtIndex:myAnimatedImages.count - 1]
I don't think there is a delegate method that would notify you on animationFinish event so you would need to start a NSTimer with same duration as the animation to get the notification.
UIImageViewAnimation documentation

Swift version will be as given below but in objective-c logic will be same
you can hack it just setting the image of the UIImageView to the last image of the images array. just before the code of animation, like imageView.image = arrayOfImage.last
the whole logic is given below
imageView.image = arrayOfImage.last //(this line create magic)
imageView.animationImages = arrayOfImage
imageView.animationDuration = 1.0
imageView.animationRepeatCount = 1
imageView.startAnimating()

I just toss a UIView holding the first frame on top of the animation UIView...
make the animation view hidden and set it's image as the last frame...
and then have them switch hidden properties during the process.
so... just before your last line...
// quickly hide the view that only holds the first frame
myOtherView.hidden = YES;
// start the animation
[myAnimatedView startAnimating];
// show the animation view
myAnimatedView.hidden = NO;
// BAM, done. It will use the last image when it ends.
Less code, more IB, whenever possible.

I've achieved this by creating a subclass of UIImageView, overriding startAnimating() and setting the image property to be the last image in your image array before you call super.startAnimating().
This in addition to setting the animationRepeatCount to 1 achieves the desired effect.
Here is my Swift class:
class SuccessAnimationImageView: UIImageView {
var duration: NSTimeInterval = 2
override func didMoveToSuperview() {
super.didMoveToSuperview()
setupImages()
}
override func startAnimating() {
setLastFrame()
super.startAnimating()
}
func setupImages() {
animationImages = ImageHelper.successAnimationImages
image = animationImages?.first
animationRepeatCount = 1
}
func setLastFrame() {
image = animationImages?.last
}
}

Was having issues in start/stop animation & managing animation count So used this pod
imageViewObject.animate(withGIFNamed: "gif_name", loopCount: 1) {
print("animating image")
}
To start/stop animation :
imageViewObject.isAnimatingGIF ? imageViewObject.stopAnimatingGIF() : imageViewObject.startAnimatingGIF()

Have you set the removedOnCompletion property of the rotation animation to NO,
e.g.,
rota.removedOnCompletion = NO;
That should leave the presentation layer where it was when the animation finished. The default is YES, which will snap back to the model value, i.e., the behavior you describe.
The fillMode should also be set,
i.e.,
rota.fillMode = kCAFillModeForwards;

You can Stop the animation by setting the UIImageView property
animateImg.animationDuration = 1

Related

On iOS, why changing the frame of a CALayer didn't cause an animation?

I have some iOS sample code that if I tap on the main view and the tap handler changes the CALayer object's frame property, the layer object will animate to its position and size.
But if I put that animation inside ViewController's viewDidLoad:
UIImage *shipImage = [UIImage imageNamed:#"SpaceShip1.png"];
ship1 = [[CALayer alloc] init];
ship1.contents = (id) [shipImage CGImage];
ship1.frame = CGRectMake(50, 50, 100, 100);
[self.view.layer addSublayer:ship1];
ship1.frame = CGRectMake(0, 0, 300, 200);
then the animation won't happen. It will just use the (0, 0, 300, 200) with no animation. Why is that and how to make it work?
Several things.
First of all, the frame property is not animatable. Instead, you should be animating bounds (to change the size) and position (to move the layer).
To quote the docs:
Note: The frame property is not directly animatable. Instead you
should animate the appropriate combination of the bounds, anchorPoint
and position properties to achieve the desired result.
Second, you have to return to the event loop after adding a layer before changing an animatable property.
When you return and the system visits the event loop, the next time you change an animatable property, it creates an animation transaction to animate that change.
Since you created a layer, set it's frame, and then changed it, all before returning, your code simply adds the layer at it's final frame.
What you should do instead is to set up the layer, install it into it's parent layer, and return. You can use performSelector:withObject:afterDelay: to invoke the animation after the layer has been added:
- (void)viewDidLoad
{
UIImage *shipImage = [UIImage imageNamed:#"SpaceShip1.png"];
ship1 = [[CALayer alloc] init];
ship1.contents = (id) [shipImage CGImage];
ship1.frame = CGRectMake(50, 50, 100, 100);
[self.view.layer addSublayer:ship1];
[self performSelector: #selector(changeLayer)
withObject: nil
afterDelay: 0.0];
}
- (void) changeLayer
{
//Animate changes to bounds and position, not frame.
ship1.bounds = CGRectMake(0, 0, 300, 200);
//Note that position is based on the center of the layer by default, so you'll
//need to adjust your desired position.
ship1.position = CGPointMake(0,0);
}
You have specified no time delay or animation time. So the object will be moved before it is displayed, and you won't see it in the earlier position. You also need to return to the run loop for the display to show any animation.
There are 3 ways to trigger animations on iOS:
1. UIView Animation
You use a UIView.animateWithDuration method or other variant to directly specify that you want an animation. You can specify parameters such as duration, delay, a completion block and others.
2. Core Animation - Implicit Animation (DESIRED APPROACH)
As the name implies, this animation is triggered without an explicit command. In order to work though, you need to set the CALayer delegate, like this:
ship1.delegate = self;
All the parameters have default values until you say otherwise. For modifying these parameters, you need to call CATransaction:
[CATransition setAnimationDuration:0.2];
IMPORTANT: These CATransaction calls must be placed BEFORE the modifications of the layer's properties, because all animations happen asynchronous (in different runtime threads) and it will be too late for you if you put these calls after.
3. Core Animation - Explicit Animation
You create an CAAnimation (there are different CAAnimation subclass to choose from) and attach it to the layer:
CABasicAnimation *a = [CABasicAnimation alloc] init....
[ship1 addAnimation:a];
I hope this clarifies some doubts about the subject.

When animation finishes, keep the ending image of the animation

i have an animation that starts out as a plain back ground. when the animation begins, it plays and when it finishes, the screen goes back to the original background. How can i keep the ending image of my animation and set it as my background? Thanks for the help!!
By the way, what i am asking is similar to a childrens book. When the animation finishes, i want the background to stay the same as the very last part of the animation. So it looks like the animation just stops!
Assuming your NSArray for the images is called imageArray and your UIImageView is called imageView, try this:
imageView.image = [imageArray lastObject];
imageView.animationImages = imageArray;
imageView.animationDuration = 1.0f;
imageView.animationRepeatCount = 1;
[imageView startAnimating];
That should loop through the images once, and then once it ends, leave the UIImageView on the last image. Hope that Helps!

How do I present a UIViewController by zooming?

In my iPad app I have a view controller with a small table view. When you tap on the table view it opens a modal view controller that is a larger and more refined version of the small table view. I would like to create an animation from a pre-rendered image of the large view controller by scaling the image down to be the size of the small table view and zoom it to full screen size and then replace the image with the "real" view controller.
Something like:
LargeViewController* lvc = [[LargeViewController alloc] init];
[self presentModalViewController:lvc byZoomingFromRect:CGRectMake(50,50,200,300)];
I know you can produce an image from a view:
- (UIImage *) imageWithView:(UIView *)view
{
UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.opaque, [[UIScreen mainScreen] scale]);
[view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage * img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return img;
}
But how do I make the view controller draw itself (offscreen) so I can take it's view and scale the image in an animation to fill screen?
Thanks in advance.
I suppose you want to create your very own animation. Last month I played around with something like that. My solution was adding a custom view (maybe taken from a view controller) to the current view as an overlay. This works with layers, too.
First you fetch the Image from your "future" or "present" view controller, like you did in your code example above. Normally the view controllers content should be available while rendering to the context.
Now you have the image. The manipulation of the image must be done by you.
Add the image to a UIImageView. This ImageView can be added as subview or layer. Now you have a layer where you can freely draw above your actual user interface. Sometimes you have to move the layer or view around, so that it perfectly overlays your view. This depends on your view setup. If you are dealing with Tableviews, adding a subview is not that easy. So better use the layer.
After all the work was done, present the new view controller without animation, so that it appears immediately.
Remove the layer or view from your parent view after the work was done, and clean up.
This sounds complicated, but once you've done that you have a template for that. In "WWDC 2011, Session 309 Introducing Interface Builder Storyboarding" apple introduced 'custom segues', where you'll find a mechanism for exactly what you want to do. The code below is a cut out of an older project and is somehow messy and must be cleaned up. But for showing the principle this should work:
-(void) animate {
static LargeViewController* lvc = [[LargeViewController alloc] init];
UIGraphicsBeginImageContextWithOptions(self.bounds.size, view.opaque, [[UIScreen mainScreen] scale]);
[lvc.view.layer renderInContext:UIGraphicsGetCurrentContext()];
// Create a ImageView to display your "zoomed" image
static UIImageView* displayView = [[UIImageView alloc] initWithFrame:self.view.frame];
static UIImage * img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// Add your image to the view
displayView.image = img;
// insert the view above your actual view, adjust coordinates in the
// frame property of displayView if overlay is misaligned
[[self.view] addSubview:displayView];
// alternatively you can use the layer
// [self.view.layer addSublayer:displayView.layer];
// draw the imageView
[displayView setNeedsDisplay];
// do something in background. You may create your own
// construction, i.e. using a timer
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSDate *now = [NSDate date];
NSTimeInterval animationDuration = 3.;
NSTimeInterval t = -[now timeIntervalSinceNow];
while (t < animationDuration) {
t = -[now timeIntervalSinceNow];
// Do some animation here, by manipulation the image
// or the displayView
// <calculate animation>, do something with img
// you have exact timing information in t,
// so you can set the scalefactor derived from t
// You must not use an UIImage view. You can create your own view
// and do sth. in draw rect. Do whatever you want,
// the results will appear
// in the view if you added a subview
// or in a layer if you are using the layer
dispatch_sync(dispatch_get_main_queue(), ^{
// display the result
displayView.image = img;
[displayView setNeedsDisplay];
});
}
});
// now the animation is done, present the real view controller
[self presentModalViewController:lvc animated:NO];
// and clean up here
}
Perhaps you could use something like
CGAffineTransform tr = CGAffineTransformScale(lvc.view.transform, 0.5, 0.5);
to embed a scaled down version of the view in your parent view controller, then present lvc modally and restore scale when the user taps the view.
UIKit takes care of most of this for you. While jbat100's solution could be made to work too, you should be able to do this simply by setting lvc's initial frame to the smaller rect you want to start out at and then when you set the frame too its full size, the implicit animation for changing the frame will handle the zooming animation for you. Each UIView has a CALayer that its content is drawn in and that layer has several implicit animtions setup to animated changes to certain properties such as the frame or position properties. Here is my untested stab at it:
.
.
lvc.view.frame = CGRectMake(50,50,200,300);
[self performSelector:#selector(setFrameToFullScreen) withObject:nil afterDelay:0];
}
- (void)setFrameToFullScreen {
lcv.view.frame = [UIScreen mainScreen].bounds;
}
The performSelector:withObject:afterDelay call will cause setFrameToFullScreen to be called on the next run loop cycle. If you don't do something like that, then only the final frame will be used and the system won't recognize the change in the frame and apply its implicit animation to the views layer.

problem with uiimageView animation

here is my code :
-(void) createNewImage {
UIImage * image = [UIImage imageNamed:#"boutonplay_03.png"];
imageView = [[UIImageView alloc] initWithImage:image];
[imageView setCenter:[self randomPointSquare]];
[[self view] addSubview:imageView];
ix=imageView.center.x;
iy=imageView.center.y;
X=(240-ix)/230;
Y=(160-iy)/230;
}
-(void)Animation{
imageView.center = CGPointMake(imageView.center.x + X, imageView.center.y + Y);
}
"CreateNewImage" is alled every 2 seconds, and there is a CADisplayLink on "Animation".
My problem is that the animation of "imageView" stops every 2 seconds because a new "imageView" is created and this last start moving and it stops again after 2 seconds, and again and again and again ... What I would like is to make "imageView" continue his animation in spite of the creation of new "imageView".How can I do this please ? sorry for my english I'm french:/
Check to see if imageView == nil or not.
if (imageView == nil) {
imageView = [[UIImageView alloc] initWithImage:image];
}
//rest of code
You are creating a new UIImageView every time you call CreateNewImage
instead, check to see if it has already been created.
-(void) createNewImage {
if(imageView == nil) {
UIImage * image = [UIImage imageNamed:#"boutonplay_03.png"];
imageView = [[UIImageView alloc] initWithImage:image];
}
[imageView setCenter:[self randomPointSquare]];
[[self view] addSubview:imageView];
ix=imageView.center.x;
iy=imageView.center.y;
X=(240-ix)/230;
Y=(160-iy)/230;
}
If I understand correctly your case, what happens is this:
you create a new image in createNewImage;
this image is animated through the method:
-(void)Animation{
imageView.center = CGPointMake(imageView.center.x + X, imageView.center.y + Y);
}
which animates your imageView ivar;
when you create a new image, after 2 seconds, you reassign it to the imageView ivar, so that the Animation method can animate it when it is called;
at the same time, you have lost the reference to your previous image that you were animating before, so Animation will not animate it anymore.
Now what I suggest is trying to change Animation in this way:
-(void)Animation{
for (UIView* imgView in self.view.subviews) {
imgView.center = CGPointMake(imgView.center.x + X, imgView.center.y + Y);
}
}
So, this will simply animate any subview of your main view. If your only subviews are the images you want to animate, it should be fine.
EDIT:
From your description in the comments, it seems to me that what happens is that addSubview has the effect of stopping shortly all animations. I don't know if this is related to loading the images from disk, but I remember I also experienced a similar problem time ago. The only approach I see is pre-creating and pre-adding all the UIImageViews you need (or a maximum number of allowed images), make them all hidden and then making them visible again one by one. This would make the logic of your program more complex, but it would work.
Another approach could be using CALayers instead of UIVIews. Best thing would be using a CABasicAnimation to animate your images.
If you want to use UIImageView, you could get [imageView layer] and add this one instead of the view, like this:
[[self.view layer] addSublayer:[imageView layer]];
but I am not sure that this approach would work.
Hope that his helps.

Animating UIButtons

I have a few buttons I would like to animate.
I have looked at and tried the animateWithDuration method but that seems to be for Views and I couldnt get my implementation to work.
Can this method work for animating buttons too? if not how could it be done?
EDIT
I have the following code
-(void) animate:(UIButton*) b withMonster: (int) monster withState: (int) state andLastState:(int) last_state {
if (state < last_state) {
float duration = 1.0 / 10;
__block int stateTemp = state;
[b animateWithDuration: duration
animations: ^{ [UIImage imageNamed:[NSString stringWithFormat:#"m%d.a000%d.png", monster, state]]; }
completion: ^{ [self animate: A1 withMonster: monster withState: stateTemp++ andLastState: last_state];; }];
}
and I call it like so
[self animate: A1 withMonster: monsterDecider withState: 1 andLastState: 5];
When this code executes the app crashes.
Is it correct to call is with self in both calls(self being ViewController).
Thanks
You can animate buttons, yes. In the animations block you need to change some of the button parameters like this:
[UIView animateWithDuration:animTime
animations:^{button.frame = newFrame;}];
Looking at your code you probably want to set the button.imageView.image property to a new image (your code currently loads a new image but doesn't set it onto the button).
However, UIImageView objects actually support changing between multiple images without needing to use the UIView animateWithduration:... methods.
You probably want to set the following properties:
button.imageView.animationImages
button.imageView.animationDuration
button.imageView.animationRepeatCount
Then call
[button.imageView startAnimating];
and the UIImageView will do all the animation for you.
See the docs on UIImageView:
http://developer.apple.com/library/ios/#documentation/uikit/reference/UIImageView_Class/Reference/Reference.html