Fondu / Dissoudre lors du changement d'image d'UIImageView

160

Plutôt que d'en créer deux UIImageViews, il semble logique de simplement changer la imagevue d'une vue. Si je fais cela, y a-t-il de toute façon un fondu / fondu croisé entre les deux images plutôt qu'un commutateur instantané?

Josh Kahane
la source
1
@ kumar-kl Votre modification a supprimé une balise importante. Ne fais pas ça.
Eric Aya
1
@KumarKL Mais ici, "objective-c" n'est pas une balise de faible priorité! Ne le supprimez pas juste pour ajouter "swift", cela n'a aucun sens, c'est en fait du vandalisme limite ...
Eric Aya
1
@EricD, d'accord, je ne vais pas répéter la même chose. Merci pour votre inquiétude.
Kumar KL

Réponses:

45

Edit: il y a une meilleure solution de @algal ci-dessous .

Une autre façon de procéder consiste à utiliser des transitions CAAnimation prédéfinies:

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;

Consultez l'exemple de projet View Transitions d'Apple: https://developer.apple.com/library/content/samplecode/ViewTransitions/Introduction/Intro.html#//apple_ref/doc/uid/DTS40007411

Mirkules
la source
1
Parfait! J'ajoute une partie de ce code dans la catégorie UIImageView (WebCache) de SDWebImage.
Autobots
@OutMan, vous feriez peut-être mieux d'utiliser la nouvelle méthode ci-dessous par user577888.
Mirkules le
Je configure également mon image après un rappel SDWebImages de cette façon, mais pour une raison quelconque dans ma CollectionViewCell, l'image de la première cellule, est initialement celle de la dernière cellule visible si toutes mes cellules sont mises à jour. Je suppose que c'est une sorte de mise en cache du presentationLayer ou quelque chose?!? Des idées de ce que cela pourrait être?
Georg
Upvote pour l'édition "il y a une meilleure solution de @algal ci-dessous."
Rob
581

Cela peut être beaucoup plus simple en utilisant les nouvelles UIKit animationméthodes basées sur des blocs .

Supposons que le code suivant se trouve dans le contrôleur de vue et que l'UIImageView que vous souhaitez dissoudre de manière croisée soit une sous-vue de self.view adressable via la propriété.Tout self.imageViewce dont vous avez besoin est:

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

Terminé.

Et pour le faire dans Swift, c'est comme ça:

    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 et 5

     let toImage = UIImage(named:"myname.png")
     UIView.transition(with: self.imageView,
                       duration: 0.3,
                       options: .transitionCrossDissolve,
                       animations: {
                           self.imageView.image = toImage
                       },
                       completion: nil)
algue
la source
3
Les autres réponses sont correctes mais dépassées (et / ou trop verbeuses).
Steven Kramer le
3
Il n'est pas nécessaire de faire la transition sur la super vue. J'ai réussi à faire ce travail en spécifiant UIImageView lui-même comme cible.
Desmond
7
@ user577888 juste une petite chose. Vous devez passer nil au bloc de complétion vide. Les blocs ne sont pas des pointeurs de fonction (comme indiqué par le ^) et fonctionnent comme un objet objective-c. Si vous passez NULL, le compilateur l'interprétera comme 0 ou (void *) 0. Si, pour une raison quelconque, le code sous-jacent de transitionWithView: ... envoie accidentellement un message au bloc d'achèvement (par exemple, [copie d'achèvement]) sans vérifier sa validité, cela conduira à une erreur. Vous devez donc toujours utiliser nil d'objectif-c lorsque vous définissez un bloc pour qu'il soit vide.
M. T
3
@ Mr.T NULL et nil ont une valeur identique. Les deux sont définis à 0 et aucun n'est susceptible de changer. Ainsi, il est sûr d'utiliser NULL partout où vous utiliseriez ou pourriez utiliser nil.
ashevin
1
@ Mr.T Mon point est que l'utilisation de l'un sur l'autre ne causera jamais de plantage car au niveau du code compilé, ils sont identiques et, pour des raisons pratiques, le seront toujours. La suppression des avertissements du compilateur est une bonne idée, en général, mais dire aux gens que c'est une erreur d'utiliser NULL au lieu de nil est tout simplement faux.
ashevin
6

Oui, ce que vous dites est absolument correct et c'est la façon de le faire. J'ai écrit cette méthode et je l'utilise toujours pour fondre mon image. Je m'occupe CALayerde ça. Vous devez importer Core Animation pour cela.

+ (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;
}

Vous pouvez faire le contraire pour Fade out une image. Après qu'il s'estompe. Vous venez de le supprimer de superview (ce qui est UIImageView). [imageView removeFromSuperview].

Srikar Appalaraju
la source
J'ajoute ce code après avoir défini une image dans UIImageView, il semble y avoir un clignotement lors du démarrage de l'animation.
Autobots
4

Vous pouvez également empaqueter la fonctionnalité de fondu d'entrée dans une sous-classe, de sorte que vous puissiez ensuite l'utiliser comme une UIImageView commune, comme dans l'exemple suivant:

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

Une implémentation possible suit.

Remarque: la façon dont vous implémentez réellement le fondu d'entrée dans la setImage:fonction peut changer et pourrait être l'un des autres excellents exemples décrits dans les autres réponses à cette question - créer un autre à la volée UIImageViewcomme je le fais ici pourrait être une surcharge inacceptable dans votre situation spécifique .

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];
        }];
    }
}

Le code ci-dessus repose sur quelques hypothèses (y compris l'activation d'ARC dans votre projet XCode), est uniquement destiné à être une preuve de concept, et dans un souci de clarté et de concentration, il reste pertinent en omettant un code non lié important. Veuillez ne pas simplement le copier-coller aveuglément.

magma
la source
3

J'avais besoin de la transition pour répéter indéfiniment. Il a fallu BEAUCOUP d'essais et d'erreurs pour celui-ci, mais j'ai finalement obtenu le résultat final que je recherchais. Il s'agit d'extraits de code permettant d'ajouter une animation d'image à un UIImageView dans un UITableViewCell.

Voici le code pertinent:

@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;
}
Christophe
la source
Salut, je me demandais si vous pouviez ajouter plus d'informations sur votre [self setVarietyImageView: cell.imageView]; méthode car je peux obtenir la dernière cellule à animer ou tout animer mais très vite, merci
gav
Salut Gav, "variétéImageView" est une référence faible dans le contrôleur de vue afin que je puisse attribuer à cell.imageView et le référencer plus tard dans la méthode 'animateImages'. @ propriété (non atomique, faible) UIImageView * variétéImageView; Vous pouvez accélérer la rotation des images en définissant une «durée» plus courte dans «transitionWithView». J'espère que c'est ce que vous demandez.
Christopher
Salut Christopher, merci pour la réponse, je m'interrogeais sur le "setVarietyImageView:" Je suppose que c'est une sorte de méthode void qui permet la réutilisation car je n'ai pu animer que la dernière cellule. Encore merci pour votre réponse, tout le meilleur Gav
gav
Cette méthode est générée à l'aide de la déclaration standard "propriété". Par exemple, @property (non atomique, faible) UIImageView * variétéImageView. C'est une référence «faible» car elle fait référence à une autre variable locale (l'imageView de la cellule du tableau). J'aurais pu plutôt faire une référence à la cellule du tableau et accéder à cell.imageView dans la méthode animateImages. J'espère que ça aide.
Christopher
2

Après avoir joué avec UIView.transition()et avoir eu des problèmes avec l' .transitionCrossDissolveoption (j'essayais d'animer des images changeant à l'intérieur d'une UIImageView et la transition s'est produite instantanément sans animation), j'ai découvert qu'il vous suffit d'ajouter une option supplémentaire qui vous permet d'animer les propriétés changeant à l'intérieur de la vue ( Swift 4.2 ):

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

De plus: si vous avez des sous-vues sur votre imageView, elle sera également redessinée et cela pourrait empêcher l'animation. Par exemple, j'avais une sous-vue avec un flou sur mon imageView et dans ce cas, l'animation ne fonctionne pas. Je viens donc de changer ma hiérarchie de vue et de déplacer le flou vers sa propre vue et de le placer sur imageView.

Pavel
la source
0

C'est, je pense, le moyen le plus court de le faire. Créez une animation UIView et validez-la sur votre imageView.

[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.5];
[myImageView setAlpha:0.0];
[UIView commitAnimations];
divyenduz
la source
0

En utilisant la highlightedImagepropriété, cela peut être rendu un peu plus simple. Voici un exemple dans Swift 3. Commencez par définir à la fois l'image normale et surlignée:

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

Et quand vous voulez changer entre ceux animés:

UIView.transition(with: imageView, duration: 0.3, options: .transitionCrossDissolve, animations: { self.imageView.isHighlighted = !self.imageView.isHighlighted}, completion: .none)
Jens Schwarzer
la source