Fade/dissolve when changing UIImageView's image
Asked Answered
S

10

167

Rather than creating two UIImageViews, it seems logical to simply change the image of one view. If I do that, is there anyway of having a fade/cross dissolve between the two images rather than an instant switch?

Stephanestephani answered 3/10, 2011 at 18:3 Comment(0)
C
46

Edit: there is a better solution from @algal below.

Another way to do this is by using predefined CAAnimation transitions:

CATransition *transition = [CATransition animation];
transition.duration = 0.25;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = kCATransitionFade;
transition.delegate = self;
[self.view.layer addAnimation:transition forKey:nil];
view1.hidden = YES;
view2.hidden = NO;

See the View Transitions example project from Apple: https://developer.apple.com/library/content/samplecode/ViewTransitions/Introduction/Intro.html#//apple_ref/doc/uid/DTS40007411

Cervical answered 3/10, 2011 at 18:51 Comment(4)
Perfect! I add part of this code into SDWebImage's UIImageView (WebCache) category.Medievalism
@OutMan you might be better off using the newer method below by user577888.Cervical
I also am setting my image after a SDWebImages callback that way, but for some reason in my CollectionViewCell the image of the first cell, is initially the one from the last visible cell if all my cells update. I guess it's some kind of caching the presentationLayer or something?!? Any ideas what it could be?Na
Upvote for the edit "there is a better solution from @Indigene below."Impertinence
I
598

It can be much simpler using the new block-based, UIKit animation methods.

Suppose the following code is in the view controller, and the UIImageView you want to cross-dissolve is a subview of self.view addressable via the property self.imageView Then all you need is:

UIImage * toImage = [UIImage imageNamed:@"myname.png"];
[UIView transitionWithView:self.imageView
        duration:5.0f
        options:UIViewAnimationOptionTransitionCrossDissolve
        animations:^{
  self.imageView.image = toImage;
} completion:nil]

Done.

And to do it in Swift, it's like so:

let toImage = UIImage(named:"myname.png")
UIView.transitionWithView(self.imageView,
                          duration:5,
                          options: UIViewAnimationOptions.TransitionCrossDissolve, animations: { self.imageView.image = toImage }, completion: nil)

Swift 3, 4 & 5

let toImage = UIImage(named:"myname.png")
UIView.transition(with: self.imageView,
                  duration: 0.3,
                  options: .transitionCrossDissolve,
                  animations: { self.imageView.image = toImage },
                  completion: nil)
Indigene answered 19/3, 2012 at 16:10 Comment(18)
The other answers are correct but outdated (and/or overly verbose).Tericaterina
It's not necessary to do the transition on the super view. I managed to make this work by specifying the UIImageView itself as the target.Abhorrent
This does not work with iOS 6.0+ ?? Any one knows an alternative for iOS6?Giliana
@VanDuTran This works fine on iOS6. Just try it: github.com/algal/TrivialImageDissolveDemo .Indigene
@Abhorrent Thanks for the correction. In fact calling it on self.view will also impose the specified animation duration on other subviews of self.view, such as a button. I will edit this later once I'm 100% sure I'm not missing some other factor.Indigene
@user577888 just a small thing. You should pass nil to the empty completion block. Blocks aren't function pointers (as denoted by the ^) and operate like an objective-c object. If you pass NULL, the compiler will interpret that as either 0 or (void *)0. If for some reason the underlying code for transitionWithView:... accidentally sends a message to the completion block (eg. [completion copy]) without checking its validity, this will lead to an error. So you should always use objective-c's nil when setting a block to be empty.Serena
@Mr.T I think what you see is totally convincing on its merits, especially the point that blocks behave like objective-c objects so the absence of a block should be conveyed with nil not NULL. But: as near as I can tell, all of Apple's code examples pass NULL not nil when there's no block. Or did they change?Indigene
@Mr.T NULL and nil are identical in value. Both are defined to 0 and neither is ever likely to change. Thus, it is safe to use NULL anywhere you would or could use nil.Kisner
@Kisner yea their values are both 0 but they're cast differently. NULL is either cast to 0 or (void *)0, and nil is cast as (id)0. Depending on your compiler, sending a message to NULL will cause a warning to occur, whereas sending a message to (id)0 will suppress that warning. So I tend to use nils whenever it's an objective-C object involved and use NULL whenever it's a c/c++ object involved.Serena
@Mr.T My point is that using one over the other will never cause a crash because at the compiled code level they are identical, and for practical reasons, always will be. Suppressing compiler warnings is a good idea, in general, but telling people it's an error to use NULL instead of nil is simply wrong.Kisner
@Mr. T Passing NULL for a block is legal, has always been legal, and always will be legal.Natividad
No, it does not work on iOS 6 if you transition from a stretchable image to a non-stretchable image. ( @VanDuTran )Externality
+1 for the Swift bit. Looks like this is how we're going to have to write Cocoa-Touch answers from now on..Trinee
I just lost an hour trying to figure out why my images never showed. Turns out I needed to call [imageView sizeToFit] after changing the image. FYI.Erythrite
I had to wrap the whole thing in a dispatch_after... to make it work. Presumably to make sure, layout is completed before animating.Hypercatalectic
It's not working for me in in Swift 3 either. It animates the first time, however, when I call it 2nd time and on, it's just a change without the animation.Kira
I think we can also say the animation like.. animations:{self.imageView.image = self.imageview.image}..it is the same thing i thinkClaviform
I think we can also say the animation like.. animations:{self.imageView.image = self.imageview.image}..it is the same thing i thinkClaviform
C
46

Edit: there is a better solution from @algal below.

Another way to do this is by using predefined CAAnimation transitions:

CATransition *transition = [CATransition animation];
transition.duration = 0.25;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = kCATransitionFade;
transition.delegate = self;
[self.view.layer addAnimation:transition forKey:nil];
view1.hidden = YES;
view2.hidden = NO;

See the View Transitions example project from Apple: https://developer.apple.com/library/content/samplecode/ViewTransitions/Introduction/Intro.html#//apple_ref/doc/uid/DTS40007411

Cervical answered 3/10, 2011 at 18:51 Comment(4)
Perfect! I add part of this code into SDWebImage's UIImageView (WebCache) category.Medievalism
@OutMan you might be better off using the newer method below by user577888.Cervical
I also am setting my image after a SDWebImages callback that way, but for some reason in my CollectionViewCell the image of the first cell, is initially the one from the last visible cell if all my cells update. I guess it's some kind of caching the presentationLayer or something?!? Any ideas what it could be?Na
Upvote for the edit "there is a better solution from @Indigene below."Impertinence
S
9

For Swift 3.0.1 :

UIView.transition(with: self.imageView,
              duration:0.5,
              options: .transitionCrossDissolve,
              animations: { self.imageView.image = newImage },
              completion: nil)

Reference: https://gist.github.com/licvido/bc22343cacfa3a8ccf88

Seeress answered 10/3, 2017 at 4:3 Comment(0)
L
6

Yes what you say is absolutely correct and thats the way to do it. I wrote this method & always use this to Fade in my image. I deal with CALayer for this. You need to import Core Animation for this.

+ (void)fadeInLayer:(CALayer *)l
{
    CABasicAnimation *fadeInAnimate   = [CABasicAnimation animationWithKeyPath:@"opacity"];
    fadeInAnimate.duration            = 0.5;
    fadeInAnimate.repeatCount         = 1;
    fadeInAnimate.autoreverses        = NO;
    fadeInAnimate.fromValue           = [NSNumber numberWithFloat:0.0];
    fadeInAnimate.toValue             = [NSNumber numberWithFloat:1.0];
    fadeInAnimate.removedOnCompletion = YES;
    [l addAnimation:fadeInAnimate forKey:@"animateOpacity"];
    return;
}

You could do the opposite for Fade out an image. After it fades out. You just remove it from superview (which is UIImageView). [imageView removeFromSuperview].

Layamon answered 3/10, 2011 at 18:12 Comment(1)
I add this code after set a image into UIImageView, it seem's there is a blink when starting animation.Medievalism
B
4

You could also package the fade-in feature in a subclass, so that you can then use it as a common UIImageView, as in the following example:

IMMFadeImageView *fiv=[[IMMFadeImageView alloc] initWithFrame:CGRectMake(10, 10, 50, 50)];
[self.view addSubview:fiv];
fiv.image=[UIImage imageNamed:@"initialImage.png"];
fiv.image=[UIImage imageNamed:@"fadeinImage.png"];  // fades in

A possible implementation follows.

Note: the way you actually implement the fade-in in the setImage: function can change, and could be one of the other excellent examples described in the other answers to this question — creating an additional on-the-fly UIImageView as I'm doing here might be an unacceptable overhead in your specific situation.

IMMFadeImageView.h :

#import <UIKit/UIKit.h>

@interface IMMFadeImageView : UIImageView
@property (nonatomic,assign) float fadeDuration;
@end

IMMFadeImageView.m :

#import "IMMFadeImageView.h"

@implementation IMMFadeImageView
@synthesize fadeDuration;

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        self.fadeDuration=1;
    }
    return self;
}
-(void)setImage:(UIImage *)newImage{

    if(!self.image||self.fadeDuration<=0){
        super.image=newImage;
    } else {
        UIImageView *iv=[[UIImageView alloc] initWithFrame:self.bounds];
        iv.contentMode=self.contentMode;
        iv.image=super.image;
        iv.alpha=1;
        [self addSubview:iv];
        super.image=newImage;
        [UIView animateWithDuration:self.fadeDuration delay:0 options:UIViewAnimationCurveEaseInOut animations:^{
            iv.alpha=0;
        } completion:^(BOOL finished) {
            [iv removeFromSuperview];
        }];
    }
}

The above code relies on a few assumptions (including ARC being enabled in your XCode project), is only intended as a proof of concept, and in the interest of clarity and focus, it stays relevant by omitting important unrelated code. Please don't just copy-paste it blindly.

Boling answered 14/7, 2012 at 20:44 Comment(0)
E
3

I needed the transition to repeat indefinitely. It took a LOT of trial and error for this one but I finally got the end-result I was looking for. These are code snippets for adding image animation to a UIImageView in a UITableViewCell.

Here is the relevant code:

@interface SomeViewController ()
@property(nonatomic, strong) NSMutableArray *imagesArray;
@property(nonatomic, assign) NSInteger varietyImageAnimationIndex;
@property(nonatomic, assign) BOOL varietyImagesAnimated;
@end

@implementation SomeViewController
@synthesize imagesArray;
@synthesize varietyImageAnimationIndex;
@synthesize varietyImagesAnimated;
...

// NOTE: Initialize the array of images in perhaps viewDidLoad method.

-(void)animateImages
{
    varietyImageAnimationIndex++;

    [UIView transitionWithView:varietyImageView
                      duration:2.0f
                       options:UIViewAnimationOptionTransitionCrossDissolve
                    animations:^{
                        varietyImageView.image = [imagesArray objectAtIndex:varietyImageAnimationIndex % [imagesArray count]];
                    } completion:^(BOOL finished) {
                        [self animateImages];
                    }];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
   ...
        [cell.imageView setImage:[imagesArray objectAtIndex:0]];


        [self setVarietyImageView:cell.imageView];

        if (! varietyImagesAnimated)
        {
            varietyImagesAnimated = YES;
            [self animateImages];
        }

    ...
    return cell;
}
Escapement answered 10/3, 2013 at 20:44 Comment(4)
Hi I was wondering if you could add some more info on your [self setVarietyImageView:cell.imageView]; method as I can get the last cell to animate or all to animate but really fast, ThanksCannoneer
Hi Gav, "varietyImageView" is a weak reference in the view controller so that I can assign to cell.imageView and reference it later in method 'animateImages'. @property(nonatomic, weak) UIImageView *varietyImageView; You can make the images rotate faster by setting a shorter "duration" in "transitionWithView". Hopefully this is what you are asking.Escapement
Hi Christopher thank you for the reply, I was wondering about the "setVarietyImageView:" I assume its some kind of void method that allows for reuse as I have only been able to animate the last cell. Again Thank you for replying, all the best GavCannoneer
This method is generated using the standard "property" declaration. For example, @property(nonatomic, weak) UIImageView *varietyImageView. It's a "weak" reference as it refers to another local variable (the imageView of the table cell). I could have instead made a reference to the table cell and access the cell.imageView in method animateImages. I hope this helps.Escapement
M
2

After playing around with UIView.transition() and getting problems with .transitionCrossDissolve option (I was trying to animate images changing inside one UIImageView and transition occurred instantly without animation) I found out that you just need to add one more option which is letting you animate properties changing inside the view (Swift 4.2):

UIView.transition(with: self.imageView,
                   duration: 1,
                   options: [.allowAnimatedContent, .transitionCrossDissolve],
                   animations: { self.imageView.image = newImage },
                   completion: nil)

In addition: if your have any subviews on your imageView, it will be redrawn as well and it could prevent animation. For example, I had subview with blur on my imageView and in that case animation doesn't work. So I just changed my view hierarchy and move blur to its own view and put it over imageView.

Morgue answered 24/1, 2019 at 22:22 Comment(0)
B
0

This is I think the shortest way of doing it. Create a UIView animation and commit it on your imageView.

[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.5];
[myImageView setAlpha:0.0];
[UIView commitAnimations];
Balata answered 14/9, 2014 at 20:4 Comment(0)
S
0

By using the highlightedImage property this can be made a bit more simple. Here's an example in Swift 3. First set both normal and highlighted image:

let imageView = UIImageView(image: UIImage(named: "image"), highlightedImage: UIImage(named: "highlightedImage"))

And when you want to change between those animated:

UIView.transition(with: imageView, duration: 0.3, options: .transitionCrossDissolve, animations: { self.imageView.isHighlighted = !self.imageView.isHighlighted}, completion: .none)
Subeditor answered 28/10, 2016 at 10:3 Comment(0)
B
0

If you're using SDWebImage, you're in luck. SDWebImage ver 4.3.0 onwards has inbuilt fade & fade(duration:) methods. DOC

imageView.sd_imageTransition = .fade  //.fade(duration: 0.7)
let url = URL(string: "https://foo/bar.jpg")
imageView.sd_setImage(with: url)
Brainbrainard answered 10/3, 2022 at 21:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.