NSViewAnimation : euh, comment ça marche ??

Bonjour à  tous,



J'ai un soucis avec NSViewAnimation.

J'ai une NSBox qui sert de conteneur. J'ai 2 vues qui doivent remplir cette box, selon la sélection d'un NSArrayController. J'ai d'abord commencé par ajouter en brut à  la contentView de la box la vue intéressante après avoir supprimé celle qui devait disparaà®tre de la contentView. Cela fonctionne .



Après, je cherchais à  animer un peu le changement de vue. Je me suis donc penché sur la doc. Et là , c'est le drame. Je ne comprends pas comment utiliser les NSViewAnimation : Les 2 vues doivent être déjà  des subViews de la contentView de la box ?? J'ai essayé de faire comme dans l'ex de la doc, mais j'ai un soucis :



La première fois que l'animation a lieu, cela fonctionne. Les 2 animations (fadeOut d'une vue et FadeIn de l'autre) ont bien lieu nikel.



Après, le fadeOut se déroule bien, mais je ne vois que la fin du fadeIn.



Bref, je ne comprends pas comment cela fonctionne. Si quelqu'un peut m'expliquer plus clairment que la doc ce qui se passe réellement durant ces animations, je suis preneur.

Réponses

  • CéroceCéroce Membre, Modérateur
    Par rapport à  NSViewAnimation, personnellement, j'ai toujours trouvé la doc horrible. De plus, les performances sont assez mauvaises, si bien qu'on se dit que faire appel à  Core Animation n'est pas si mal (même s'il faut deux semaines pour maà®triser ce monstre-là ).



    Toutefois, j'utilise NSViewAnimation pour passer d'un onglet à  l'autre dans les Préférences. Ceci vaut de l'or:


    <br />
    - (void) _setCurrentViewController:(NSViewController*)viewController<br />
    {<br />
    if(viewController == currentViewController)<br />
      return;<br />
    <br />
    // Insert the view and remove the old one<br />
    [self _insertSubview:[viewController view]];<br />
    currentViewController = viewController; <br />
    }<br />
    /* Change the subview displayed in the window.<br />
    An animation is shown:<br />
      - The new view fades in<br />
      - The old view fades out<br />
      - The window is resized.<br />
    */<br />
    - (void) _insertSubview:(NSView*)newView<br />
    {<br />
    NSAssert(destView &#33;= nil, @&quot;destView must be set.&quot;);<br />
    <br />
    oldView = [currentViewController view];<br />
    <br />
    // Animation<br />
    NSDictionary* newViewAnimDic = [NSDictionary dictionaryWithObjectsAndKeys:<br />
       newView, NSViewAnimationTargetKey,<br />
       NSViewAnimationFadeInEffect, NSViewAnimationEffectKey,<br />
       nil];<br />
    <br />
    NSDictionary* oldViewAnimDic = [NSDictionary dictionaryWithObjectsAndKeys:<br />
      oldView, NSViewAnimationTargetKey,<br />
      NSViewAnimationFadeOutEffect, NSViewAnimationEffectKey,<br />
      nil];<br />
    <br />
    NSDictionary* windowAnimDic = [NSDictionary dictionaryWithObjectsAndKeys:<br />
      [self window], NSViewAnimationTargetKey,<br />
      [NSValue valueWithRect:self.window.frame], NSViewAnimationStartFrameKey,<br />
      [NSValue valueWithRect:[self _windowFrameToFitView:newView]], NSViewAnimationEndFrameKey,<br />
      nil];<br />
    <br />
    NSViewAnimation* anim = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:windowAnimDic, newViewAnimDic, oldViewAnimDic, nil]];<br />
    [anim setDelegate:self];<br />
    [anim setDuration:0.3];<br />
    [anim setAnimationBlockingMode:NSAnimationBlocking];<br />
    [anim startAnimation];<br />
    [anim release];<br />
    <br />
    // Add the new view<br />
    [destView addSubview:newView];<br />
     <br />
    }<br />
    // Remove the old view when the animation has finished<br />
    - (void)animationDidEnd:(NSAnimation *)animation<br />
    {<br />
    [oldView removeFromSuperview];<br />
    }<br />
    // Return the new frame the window needs to take to fit &#39;view&#39; in destView.<br />
    - (NSRect) _windowFrameToFitView:(NSView*)view<br />
    {<br />
    // Get the minimum height of the window<br />
    float minHeight = [[self window] frame].size.height - [destView frame].size.height;<br />
    float newHeight = minHeight + [view frame].size.height;<br />
    <br />
    NSRect newFrame = [[self window] frame];<br />
    newFrame.origin.y += newFrame.size.height-newHeight;<br />
    newFrame.size.height = newHeight;<br />
    <br />
    return newFrame;<br />
    }<br />
    




    Ceci devrait répondre à  tes questions.
  • MickMick Membre
    Merci Céroce. Bien vu pour le coup du delegate qui permet de supprimer le vue après l'animation.



    Mais ce que j'ai du mal à  comprendre : la vue n'est pas introduite dans la view hierarchy avant l'anim, donc comment le système "sait" que la vue est une subview de telle ou telle vue de la hierarchie ??



    j'essaye de toute façon et je repost
  • CéroceCéroce Membre, Modérateur
    août 2012 modifié #4
    'Mick' a écrit:


    Mais ce que j'ai du mal à  comprendre : la vue n'est pas introduite dans la view hierarchy avant l'anim, donc comment le système "sait" que la vue est une subview de telle ou telle vue de la hierarchie ??




    D'après mon code, il ne sait pas, il se contente de faire l'animation, c'est à  dire changer l'opacité de la nouvelle vue, mais ceci est fait de façon asynchrone. C'est toujours le addSubview: qui fait d'elle une sous-vue.
  • MickMick Membre
    Ah yes Ok !! ASYNCHRONE, là  est la clef !



    Je vais refaire un truc propre pour maintenir la vue courante comme tu l'as fait. (ma méthode est un peu brouillon là  !).

    Merci Céroce. j'ai enfin compris. (franchement, la doc là , zéro)
  • MickMick Membre
    août 2012 modifié #6
    Mouais,



    Après avoir rendu plus propre la méthode, le résultat n'est pas super convaincant. En effet, l'objectif est de fondre les 2 vues. Lorsque je mets un temps important, je m'aperçois que le fade In n'est jamais fait. la vue est tout bonnement ajoutée !



    Voici le bout de code
    <br />
    - (NSDictionary *)animWithVue:(NSView *)uneVue fadeOut:(BOOL)fo { //Méthode pour creer le dictionnary de l&#39;anim<br />
    	NSRect bounds=[[laBox contentView] frame];<br />
    	NSMutableDictionary *dico=[NSMutableDictionary dictionary];<br />
    	NSRect startFrame;<br />
    	NSRect endFrame;<br />
    	if (fo) { // Si c&#39;est un fadeOut, on part de la taille de la box, et on réduit à  zéro, la vue etant centree.<br />
    		startFrame=bounds;<br />
    		endFrame=NSMakeRect(bounds.size.width/2.0, bounds.size.height/2.0, 0.0, 0.0);<br />
    		[dico setObject:NSViewAnimationFadeOutEffect forKey:NSViewAnimationEffectKey];<br />
    	}<br />
    	else { //Sinon, le contraire : c&#39;est un fadeIn<br />
    		startFrame=NSMakeRect(bounds.size.width/2.0, bounds.size.height/2.0, 0.0, 0.0);<br />
    		endFrame=bounds;<br />
    		[dico setObject:NSViewAnimationFadeInEffect forKey:NSViewAnimationEffectKey];<br />
    	}<br />
    	<br />
    	[dico setObject:[NSValue valueWithRect:startFrame] forKey:NSViewAnimationStartFrameKey];<br />
    	[dico setObject:[NSValue valueWithRect:endFrame] forKey:NSViewAnimationEndFrameKey];<br />
    	[dico setObject:uneVue forKey:NSViewAnimationTargetKey];<br />
    	<br />
    	return [NSDictionary dictionaryWithDictionary:dico];<br />
    }<br />
    <br />
    - (void)animationDidEnd:(NSViewAnimation *)anim {<br />
    	[oldView removeFromSuperview]; //Après l&#39;anim, on supprime l&#39;ancienne vue.<br />
    }<br />
    - (void)setCurrentView:(NSView *)newView {<br />
    	oldView=currentView;<br />
    	if (newView==currentView) return; //Si la nouvelle vue est aussi l&#39;ancienne, on laisse tomber.<br />
    	currentView=newView; //La nouvelle vue devient la vue courante officiellement.<br />
    	NSDictionary *animFadeOut=[self animWithVue:oldView fadeOut:YES];<br />
    	NSDictionary *animFadeIn=[self animWithVue:currentView fadeOut:NO];<br />
    	NSViewAnimation *anim=[[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:animFadeIn,animFadeOut,nil]];<br />
    	[anim setDelegate:self];<br />
    	[anim setDuration:5.0];<br />
    	[anim setAnimationBlockingMode:NSAnimationBlocking];<br />
    	//[anim setAnimationCurve:NSAnimationEaseInOut];<br />
    	[anim startAnimation];<br />
    	[anim release];<br />
    	[[laBox contentView] addSubview:currentView];<br />
    }<br />
    <br />
    




    Mon dieu, mais qu'ai-je fait de mal ?
  • CéroceCéroce Membre, Modérateur
    Cette ligne me paraà®t bizarre:


    <br />
    [dico setObject:NSViewAnimationFadeOutEffect forKey:NSViewAnimationEffectKey];<br />
    
  • MickMick Membre
    août 2012 modifié #8
    Grillé. J'ai modifié un peu le code en le commentant.

    Mais .. toujours pas. J'ai l'impression que l'ajout de la vue ne se fait qu'après la fin du fadeOut de la oldView. c'est bizarre.



    D'autant plus que la PREMIERE animation fonctionne bien, et pas les autres !
  • MickMick Membre
    Bon, en ne faisant qu'un fadeIn de la nouvelle vue, sans fadeOut de l'ancienne, on voit bien le fadeIn.

    Quand il y a un fadeOut de l'ancienne et un fadeIn de la nouvelle, alors la première fois ça se passe bien, puis ça merdoie.



    Mystérieux image/huh.gif' class='bbc_emoticon' alt='???' />
  • Je vais pas me prendre la tête plus que ça. Je laisse le fadeIn seul sans fadeOut de l'ancienne vue. Ca le fait quand même visuellement. Toujours est-il que le problème est toujours mystérieux.



    Autre question : comment zapper les warnings qui me disent que mon controller n'implémente pas les méthodes delegate de NSViewAnimationDelegate protocol ? (je n'ai besoin que de animationDidEnd:)
  • CéroceCéroce Membre, Modérateur
    Rends ton contrôleur conforme au protocole.


    @interface MonControleur : NSViewController &lt;[color=#282828][font=helvetica, arial, sans-serif][size=3]NSViewAnimationDelegate&gt;[/size][/font][/color]
    
  • Ok. Effectivement, c'est beaucoup mieux !

    Dernière chose : si je veux utiliser coreAnimation, comment puis-je récupérer une bitMap de la NSView dessinée afin de remplir un Layer avec et ensuite animer la propriété opacity ?



    Du coup, au démarrage de l'animation, la vue afficheraient les 2 layers superposés, leur opacité serait animée, puis à  la fin, les layers seraient supprimés et la vue retracée. C'est l'idée ou je suis à  coté ?
  • CéroceCéroce Membre, Modérateur
    Le sujet est trop complexe pour être expliqué sur un bout de forum.

    En appelant setWantsLayer:YES sur une NSView, le dessin se fait dans une CALayer au lieu de l'être directement à  l'écran. Note qu'on perd alors le lissage des sous-pixels, le rendu est donc moins bon, c'est flagrant pour les textes.



    La doc d'Apple quant à  l'interaction NSView/CALayer est pitoyable, j'ai eu toutes les infos sur des blogs ou dans un livre. L'implémentation elle-même n'est pas une réussite, on est très loin d'iOS.



    En résumé, si NSViewAnimation te convient, reste-y. Core Animation est plus performante, permet des animations plus poussées, mais c'est au prix d'une grande complexité, et d'heures d'essais. Je dirais que seul un professionnel peut se permettre de faire cet investissement intellectuel.
  • Merci de ces infos. En fait, j'ai déjà  utilisé les layers pour des besoins particuliers (MovieLayer et une couche CALayer par dessus ...) le seul "problème" que je vois quand on utilise ces fameux layers, c'est qu'on doit tout faire "à  la mano" : IB n'est d'aucune utilité ? A moins qu'il y ait des astuces ?



    Je vais essayer de créer une View, d'utiliser ensuite la méthode dataWithEPS.. pour créer au final une CGImage qui pourra "peupler" un Layer. Un fois ça fait, je peux animer les layers, (explicit animation de opacity) et à  la fin de l'animation dire à  la vue qu'elle n'a plus besoin de layer afin qu'elle trace son contenu par drawRect: . Ne Céroce pas image/grin.gif' class='bbc_emoticon' alt=';D' /> ce que fait coreAniation avec ses NSViewAnimation ?



    Cette technique pourrait me permettre de concevoir les vues dans IB avant de les animer. et retrouver des vues "conventionnelles" après. En effet, j'ai cru comprendre que les layers ne supportaient pas les actions ! c'est la vue "support" qui les intercepte ! c'est quand même un peu tordu.
  • CéroceCéroce Membre, Modérateur
    août 2012 modifié #15
    Dans IB, dans les propriétés en haut à  droite, il y a un onglet Layers. Cocher la case est équivalent à  appeler setWantsLayer:YES.

    À partir de ce moment, tu peux accéder à  la propriété .layer de la NSView et faire les anims que tu veux avec. En comprenant bien que c'est la layer qui est animée, pas la NSView. Par exemple, si tu fais une animation qui déplace de 100 points vers la droite, la layer.anchorPoint est déplacé, mais pas la frame de la NSView.



    Cependant, NSView a un proxy animator, qui modifie bien la layer et la NSView. Cherche un peu de doc sur les blogs.



    Même si je n'en suis pas certain, je pense que NSViewAnimation n'est pas basée sur CoreAnimation. Trois raisons à  cela:

    1) NSViewAnimation a été introduite dans 10.4, Core Animation dans 10.5.

    2) D'après mes tests sous 10.5, les performances ne sont pas les mêmes.

    3) Les textes restent bien lisses avec NSViewAnimation.
Connectez-vous ou Inscrivez-vous pour répondre.