Animation push sans storyboard

Bonjour,


 


je connais bien la programmation osx, mais je débute encore en ios.


 


J'ai un controller mainVC qui a une propriété mainView.


J'ai deux controllers firstVC et secondVC.


 


mainView a pour fonction d'afficher le contenu de firstVC ou de secondVC.


 


Je n'utilise ni les storyboards ni les childViewControllers. Pour l'instant, tout marche mais je n'arrive pas à  animer le passage firstVC -> secondVC. Je souhaiterais avoir une animation de type push.


 


Voici ce que je fais:



CATransition *animation = [CATransition animation];
[animation setDuration:0.25];
[animation setType:kCATransitionPush];
[animation setSubtype:kCATransitionFromRight];
[animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];


[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.25];

[self.mainView removeAllSubviews_cbd_] ;
[self.mainView fillWithSubview_cbd_:self.secondVC.view] ;
[UIView commitAnimations];

(code copié-collé du web)


ça ne marche pas.


 


J'ai aussi essayé (code que j'ai écrit)



[UIView animateWithDuration:1
delay:0
options:0
animations:^{
[self.mainView removeAllSubviews_cbd_] ;
[self.mainView fillWithSubview_cbd_:self.secondVC.view] ;
}
completion:nil] ;

ça ne donne aucune animation.


 


 


Si des programmeurs plus expérimentés pouvaient m'éclairer de leur lanterne, merci !


 


Colas


Réponses

  • CéroceCéroce Membre, Modérateur
    Le push se fait traditionnellement en utilisant un UINavigationController.
    Est-ce que ça ne convient pas à  ton besoin ?
  • colas_colas_ Membre
    septembre 2014 modifié #3

    Merci Céroce !


    Je suis plus dans le cas où les "sous-vues" sont toutes à  un même niveau (cf. mon message avec le dessin de l'interface) que dans le cas où il y a un VC parent qui a des sous-vues, etc. Mais je ne doute pas que je puisse faire entrer mon cas d'utilisation dans celui prévu par Apple.


     


    Je suis aussi preneur d'une solution qui expliquerait pourquoi l'animation bête et méchante ne marche pas. J'ai parcouru SO et certains messages expliquent qu'il faut mettre en cache la vue courante dans une image bitmat, etc. 


  • CéroceCéroce Membre, Modérateur
    septembre 2014 modifié #4
    Pour CATransition, j'en sais rien.

    Dans les méthodes +[UIView animate...], il faut changer dans le bloc des propriétés animables de UIView: frame, bounds, center, transform, alpha, backgroundColor ou contentStretch.
    Faire un push consiste à  déplacer la frame. Je ne sais pas trop ce que tu fais dans tes méthodes.

    Ensuite, tu ne dois pas retirer la vue d'origine avant que l'autre soit affichée, il faut donc le faire dans le bloc de complétion.

    Cela dit, il me semble qu'on peut tout à  fait utiliser un navigation controller qui ne remplit pas tout l'écran (il hérite de UIViewController, après tout). En fait, je l'ai déjà  fait. Donc, tu pourrais le disposer sur la droite, masquer sa barre de navigation. C'est une solution plus élégante et bien plus facile à  programmer.
  • AliGatorAliGator Membre, Modérateur

    Je ne vois pas trop en lisant ton code ce que tu attends comme animation.


    Tu sembles supprimer le firstVC et tu ajoutes le secondVC, façon removeFromSuperview: et addSubview:, directement.


    En quoi cela, si tu l'animes, est sensé faire une animation de "push" de la droite vers la gauche ?


     


    Si tu veux reproduire l'animation push du UINavigationController, il faut que tu ajoutes le secondViewController mais avec une frame à  droite de l'écran, que tu animes ensuite le changement de la frame de sa position à  droite de l'écran jusqu'à  sa position finale (frame de l'écran), et qu'une fois l'animation finie tu supprimes le firstVC. Comme ça tu vas bien animer le déplacement de ton secondVC de la droite vers le centre, et ton firstVC sera bien encore visible pendant ton animation, et supprimé seulement à  la fin.


  • AliGatorAliGator Membre, Modérateur


    Cela dit, il me semble qu'on peut tout à  fait utiliser un navigation controller qui ne remplit pas tout l'écran (il hérite de UIViewController, après tout). En fait, je l'ai déjà  fait. Donc, tu pourrais le disposer sur la droite, masquer sa barre de navigation. C'est une solution plus élégante et bien plus facile à  programmer.




    Je confirme, cela consiste à  utiliser un UINavigationController (avec la barre de navigation affichée ou masquée selon ce que tu veux) dans un Container View Controller.


     


    Honnêtement colas2 tu dis ne pas utiliser les childViewController, mais je pense que c'est un tort, car ils collent justement parfaitement à  ce genre de cas d'usage. D'ailleurs les UINavigationController, UITabBarController & co sont des Container View Controllers ayant des childViewControllers, donc si tu veux reproduire leur fonctionnement, c'est bête de ne pas profiter de cette API faite pour.

  • colas_colas_ Membre
    septembre 2014 modifié #7

    Merci de vos réponses.


     


    Je vais essayer d'utiliser navigationController.


     


    @Ali : je vais me renseigner sur les childViewControllers (je n'ai jamais regardé cette API, qui n'existe pas sous osx)


  • Joanna CarterJoanna Carter Membre, Modérateur
    septembre 2014 modifié #8


    Bonjour,


     


    je connais bien la programmation osx, mais je débute encore en ios.


     


    J'ai un controller mainVC qui a une propriété mainView.


    J'ai deux controllers firstVC et secondVC.


     


    mainView a pour fonction d'afficher le contenu de firstVC ou de secondVC.


     


    Je n'utilise ni les storyboards ni les childViewControllers. Pour l'instant, tout marche mais je n'arrive pas à  animer le passage firstVC -> secondVC. Je souhaiterais avoir une animation de type push.


     


    Voici ce que je fais:



    CATransition *animation = [CATransition animation];
    [animation setDuration:0.25];
    [animation setType:kCATransitionPush];
    [animation setSubtype:kCATransitionFromRight];
    [animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];


    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration:0.25];

    [self.mainView removeAllSubviews_cbd_] ;
    [self.mainView fillWithSubview_cbd_:self.secondVC.view] ;
    [UIView commitAnimations];

    (code copié-collé du web)


    ça ne marche pas.


     


    J'ai aussi essayé (code que j'ai écrit)



    [UIView animateWithDuration:1
    delay:0
    options:0
    animations:^{
    [self.mainView removeAllSubviews_cbd_] ;
    [self.mainView fillWithSubview_cbd_:self.secondVC.view] ;
    }
    completion:nil] ;

    ça ne donne aucune animation.


     


     


    Si des programmeurs plus expérimentés pouvaient m'éclairer de leur lanterne, merci !


     


    Colas




     


    Tu as oublié de gérer les ViewControllers. Si tu insistes à  éviter les moyens déjà  fourni en Cocoa, il te faut t'informer sur le sujets des ViewControllers et leurs hiérarchies en plus de vues et ses sous-vues.


     


    il vaut l'effort de regarder la vidéo 218 du WWDC 2013.


     


    Sinon, utilises les storyboards et ce sera beaucoup plus facile


  • AliGatorAliGator Membre, Modérateur
    septembre 2014 modifié #9

    Creating Custom Container View Controllers (View Controller Programming Guide for iOS)


     


    T'as tout d'expliqué là  dedans, avec exemple, schémas et bout de code à  l'appui.


  • Finalement, je crois que je ne vais pas utiliser les parent/child view controllers.


     


    Je suis confronté au problème suivant : la méthode transitionFromViewController:toViewController:duration:options:animations:completion: ne permet pas d'ajouter des contraintes lors de l'ajout du nouveau child view controller (cf. SO)

  • colas_colas_ Membre
    septembre 2014 modifié #11


    Si tu veux reproduire l'animation push du UINavigationController, il faut que tu ajoutes le secondViewController mais avec une frame à  droite de l'écran, que tu animes ensuite le changement de la frame de sa position à  droite de l'écran jusqu'à  sa position finale (frame de l'écran), et qu'une fois l'animation finie tu supprimes le firstVC. Comme ça tu vas bien animer le déplacement de ton secondVC de la droite vers le centre, et ton firstVC sera bien encore visible pendant ton animation, et supprimé seulement à  la fin.




     


    C'est finalement ce que j'ai fait (cf. plus bas)


     


    J'ai une question à  propos de cette solution.


    Imaginons que je souhaite faire un effet push entre deux vues, mais que ces deux vues ne vont pas occuper tout l'écran, mais seulement une partie :


    • viewForTransition n'est pas tout l'écran, mais seulement un rectangle dans l'écran


    • firstView et secondView remplissent alternativement viewForTransition, avec effet push entre les deux.


     


    Avec la solution donnée par @Aligator, en imaginant que secondView va pusher firstView, on va voir secondView traverser tout l'écran, en venant de la gauche (par exemple), pour à  la fin de mettre "dans" viewForTransition.


     


    Ce n'est pas exactement l'effet que je souhaiterais. Je voudrais que le push ait lieu à  l'intérieur de viewForTransition. Autrement dit, je voudrais qu'on voie secondView traverser viewForTransition, en venant de la gauche (par exemple), pour à  la fin être entièrement dans viewForTransition.


     


    Pour l'instant, je m'en suis sorti en mettant des views blanches opaques autour de viewForTransition, mais :


    • il y a un petit écart temporel entre le moment où l'on clique pour avoir la transition et le moment entre secondView commence à  traverser viewForTransition. 


    • cette solution est crade... et pas générique


     


    Si vous avez lu jusque là  ;-) et que vous avez des idées, je suis preneur.


     


    Voilà  mon code pour l'instant :



    //
    // CBDTransitionVC.h


    #import <UIKit/UIKit.h>

    typedef NS_ENUM(NSInteger, CBDViewControllerTransition) {
    CBDViewControllerTransitionSlideFromTop = 0,
    CBDViewControllerTransitionSlideFromLeft,
    CBDViewControllerTransitionSlideFromBottom,
    CBDViewControllerTransitionSlideFromRight
    };


    @interface CBDTransitionVC : UIViewController


    /*
    This property should be set (for example, via IB)
    */
    @property (strong, nonatomic, readwrite) IBOutlet UIView * viewForTransition ;


    @property (nonatomic, weak, readonly) UIView * currentMainView ;

    @property (assign, nonatomic, readwrite) CBDViewControllerTransition transition ;
    @property (assign, nonatomic, readwrite) NSTimeInterval durationTransition ;
    @property (assign, nonatomic, readwrite) CGFloat scale ;
    @property (assign, nonatomic, readwrite) CGFloat velocity ;




    - (void)setMainViewTo:(UIView *)view
    withFillingByConstraints:(BOOL)withFillingByConstraining ;


    - (void)setMainViewTo:(UIView *)view
    withTransition:(BOOL)withTransition
    withFillingByConstraints:(BOOL)withFillingByConstraining ;


    - (void)setMainViewTo:(UIView *)view
    withCustomTransition:(CBDViewControllerTransition)transition
    withFillingByConstraints:(BOOL)withFillingByConstraining ;

    et



    //
    // CBDTransitionVC.m
    //
    //

    #import <QuartzCore/QuartzCore.h>
    #import "CBDTransitionVC.h"

    #import "OSView+CBDRemoveAllSubviews.h"
    #import "UIView+CBDHelperMethods.h"



    //
    //
    /**************************************/
    #pragma mark - Constantes
    /**************************************/

    static CBDViewControllerTransition const kDefaultTransition = CBDViewControllerTransitionSlideFromRight ;
    static NSTimeInterval const kDefaultDurationTransition = 0.5 ;
    static NSTimeInterval const kDefaultScale = 1 ;
    static NSTimeInterval const kDefaultVelocity = 1 ;







    @interface CBDTransitionVC ()

    @property (nonatomic, weak) UIViewController *currentChildViewController;
    @property (nonatomic, weak, readwrite) UIView * currentMainView ;

    @end


    @implementation CBDTransitionVC




    //
    //
    /**************************************/
    #pragma mark - Init
    /**************************************/


    - (id)initWithNibName:(NSString *)nibNameOrNil
    bundle:(NSBundle *)nibBundleOrNil
    {
    self = [super initWithNibName:nibNameOrNil
    bundle:nibBundleOrNil] ;

    if (self)
    {
    [self initWithDefaultValues] ;
    }

    return self ;
    }



    - (id)initWithCoder:(NSCoder *)aDecoder
    {
    self = [super initWithCoder:aDecoder] ;

    if (self)
    {
    [self initWithDefaultValues] ;
    }

    return self ;
    }


    - (void)initWithDefaultValues
    {
    _transition = kDefaultTransition ;
    _durationTransition = kDefaultDurationTransition ;
    _scale = kDefaultScale ;
    _velocity = kDefaultVelocity ;
    }







    //
    //
    /**************************************/
    #pragma mark - Managing the transition
    /**************************************/








    - (void)setMainViewTo:(UIView *)view
    withFillingByConstraints:(BOOL)withFillingByConstraining
    {
    return [self setMainViewTo:view
    withCustomTransition:self.transition
    withTransition:YES
    withFillingByConstraints:withFillingByConstraining] ;
    }


    - (void)setMainViewTo:(UIView *)view
    withTransition:(BOOL)withTransition
    withFillingByConstraints:(BOOL)withFillingByConstraining
    {
    [self setMainViewTo:view
    withCustomTransition:self.transition
    withTransition:withTransition
    withFillingByConstraints:withFillingByConstraining] ;
    }


    - (void)setMainViewTo:(UIView *)view
    withCustomTransition:(CBDViewControllerTransition)transition
    withFillingByConstraints:(BOOL)withFillingByConstraining
    {
    [self setMainViewTo:view
    withCustomTransition:transition
    withTransition:YES
    withFillingByConstraints:withFillingByConstraining] ;

    }






    /*
    cf.
    https://github.com/NSCookbook/Recipe28-View_Controller_Containment_and_Transitioning
    */
    - (void)setMainViewTo:(UIView *)view
    withCustomTransition:(CBDViewControllerTransition)transition
    withTransition:(BOOL)withTransition
    withFillingByConstraints:(BOOL)withFillingByConstraining


    {
    UIView * oldSubview = self.currentMainView ;
    //oldSubview.transform = CGAffineTransformIdentity;


    /*
    Case with no animation
    */
    if (!withTransition)
    {
    if (!withFillingByConstraining)
    {
    [self.viewForTransition addSubview:view] ;
    [oldSubview removeFromSuperview] ;
    }
    else
    {
    [self.viewForTransition fillWithSubview_cbd_:view] ;
    [oldSubview removeFromSuperview] ;
    [self.viewForTransition updateConstraints] ;
    }

    self.currentMainView = view ;
    }
    else
    {

    /*
    Case with animation
    */

    view.transform = [self startingTransformForViewControllerTransition:transition];

    [UIView animateWithDuration:self.durationTransition
    delay:0
    options:0
    animations:^{
    if (!withFillingByConstraining)
    {
    [self.viewForTransition addSubview:view] ;
    }
    else
    {
    [self.viewForTransition fillWithSubview_cbd_:view] ;
    [self.viewForTransition updateConstraints] ;
    }

    self.currentMainView.alpha = 0 ;
    CGAffineTransform transform = CGAffineTransformMakeTranslation(view.transform.tx * self.velocity,
    view.transform.ty * self.velocity);
    transform = CGAffineTransformRotate(transform, acosf(view.transform.a));
    self.currentChildViewController.view.transform = CGAffineTransformScale(transform,
    self.scale,
    self.scale);

    view.transform = CGAffineTransformIdentity;
    }
    completion:^(BOOL finished) {
    [oldSubview removeFromSuperview] ;

    //[self.currentChildViewController.view removeFromSuperview] ;
    self.currentMainView.alpha = 1 ;
    self.currentMainView = view ;

    }];
    }
    }






    - (CGAffineTransform)startingTransformForViewControllerTransition:(CBDViewControllerTransition)transition
    {
    CGFloat width = CGRectGetWidth(self.viewForTransition.bounds);
    CGFloat height = CGRectGetHeight(self.viewForTransition.bounds);
    CGAffineTransform transform = CGAffineTransformIdentity;

    switch (transition)
    {
    case CBDViewControllerTransitionSlideFromTop:
    transform = CGAffineTransformMakeTranslation(0, -height);
    break;
    case CBDViewControllerTransitionSlideFromLeft:
    transform = CGAffineTransformMakeTranslation(-width, 0);
    break;
    case CBDViewControllerTransitionSlideFromRight:
    transform = CGAffineTransformMakeTranslation(width, 0);
    break;
    case CBDViewControllerTransitionSlideFromBottom:
    transform = CGAffineTransformMakeTranslation(0, height);
    break;
    default:
    break;
    }

    return transform;
    }


    @end


  • AliGatorAliGator Membre, Modérateur
    1) prévoit une vue conteneur "viewTransitionContainer" avec son clipToBounds à  YES de la taille de firstView=secondView, et met firstView dedans (directement dans ton XIB par exemple). C'est ton état initial.


    2) quand tu veux faire ton push, ajoute secondView en subview de viewTransitionContainer, anime tes frames, puis à  la fin de l'anim supprime firstView



    Si tu préfères utiliser la méthode de [UIView transitionFromView:toView:etc...] c'est exactement le même principe : le secret est de prévoir une vue parente (avec clipToBounds=YES) dans laquelle tu vas encapsuler tes firstView/secondView pour clipper l'animation.
  • Merci ! Je vais essayer ça.

  • ça marche. 


    Merci de votre aide et de vos retours !


Connectez-vous ou Inscrivez-vous pour répondre.