Transiciones personalizadas en iOS7

1
5527

Transiciones personalizadas en iOS7

Índice de contenidos

1. Introducción
2. Entorno
3. Creando proyecto.
4. Conclusiones.

1. Introducción

IOS7 introduce nuevas clases para facilitar a los desarrolladores la posibilidad de hacer transiciones a nuestro gusto, tanto en la presentación de pantallas de forma modal como en los UINavigationsControllers y los UITabBarControllers, por lo que el límite es nuestra imaginación, empezamos a ver un poco de teoría, las clases que juegan en este papel son las siguientes;

  • UIViewControllerAnimatedTransitioning: Este protocolo tiene tres métodos, uno de ellos opcional, los principales son (transitionDuration: ) y (animateTransition: ) en los cuales se determina la duración y la animación que se va a ejecutar.
  • UIViewControllerTransitioningDelegate: Este es el protocolo al que tiene que acogerse el controlador que implemente las transiciones modales personalizadas, tiene 4 métodos opcionales, dos para las transiciones por defecto (animationControllerForPresentedController: presentingController:sourceController:) y (animationControllerForDismissedController:) para presentar y ocultar respectivamente y otros dos para las transiciones interactivas, en las cuales se puede interactuar con la animación con un gesto por ejemplo (interactionControllerForPresentation:) y (interactionControllerForDismissal:).
  • UIViewControllerContextTransitioning: Este protocolo nos proporcionara toda la información acerca del contexto de la transición, no tiene implementación , solo nos provee de información.
  • UINavigationControllerDelegate: Este ya le conoceréis unos cuantos, pero ahora en iOS7 tiene un par de métodos más para la implementación de las transiciones personalizadas, el primero es (navigationController:interactionControllerForAnimationController:) para las transiciones interactivas y (navigationController: animationControllerForOperation:fromViewController:toViewController:) para las transiciones por defecto.
  • UITabBarControllerDelegate: Al igual que el protocolo del navigationController en iOS7 añade unos metodos para las transiciones (tabBarController:interactionControllerForAnimationController:) para las transiciones interactivas y (tabBarController:animationControllerForTransitionFromViewController:toViewController:) para las de por defecto.

Bien una vez vistos los protocolos y métodos vamos a ver un poco de código, vamos a crear un proyecto que haga dos tipos de transiciones, una de tipo modal y otra de tipo de navegación.

2. Entorno

  • Macbook pro core i7 con 8gb RAM
  • SO: Mavericks
  • IDE: Xcode 5.0.2.

3. Creando proyecto.

Vamos a crear un nuevo proyecto en Xcode de tipo single View aplication, y vamos a borrar el controlador del storyboard que viene por defecto y añadir unUINavigarionController, recordar poner un identifirer a la celda que viene por defecto en el UITableViewController. Vamos a crear una clase que herede UITableViewController llamada MasterViewController y la vamos a linkar al controlador de tipo tabla que nos ha arrastrado el UINavigationController.

Bien ahora vamos a crear un fichero .h al que llamaremos AnimationDelegate.h y escribimos lo siguiente;


#import 

@protocol IACAnimationControllerDelegate <UIViewControllerAnimatedTransitioning>

@property (nonatomic, assign) NSTimeInterval presentationDuration;

@property (nonatomic, assign) NSTimeInterval dismissalDuration;

@property (nonatomic, assign) BOOL isPresenting;

@end

Ahora creamos una clase que herede NSObject a la que llamaremos ModalTransition e importamos el fichero que acabamos de crear y adoptamos su protocolo, también vamos a crear las propertys necesarias y los métodos del protocolo UIViewControllerAnimatedTransitioning, nos quedaría de esta forma;


#import 
#import "IACAnimationController.h"

@interface IACModalTransition : NSObject<IACAnimationControllerDelegate>

@property (nonatomic        ) BOOL           isPresenting;
@property (nonatomic, assign) NSTimeInterval presentationDuration;
@property (nonatomic, assign) NSTimeInterval dismissalDuration;
@property (nonatomic, strong) id<UIViewControllerContextTransitioning> transitionContext;

-(NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext;
-(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext;

@end

Bien, ahora vamos a implementar los métodos en el .m;


#import "IACModalTransition.h"

@implementation IACModalTransition

-(id)init{
    self = [super init];
    
    if(self){
        
        self.presentationDuration = 1.0;
        self.dismissalDuration = 0.5;
    }
    
    return self;
}

-(NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext{
    
    return self.isPresenting ? self.presentationDuration : self.dismissalDuration;
}

-(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext{
    
}

@end

Antes de ponernos con la magia de CoreAnimation vamos a preparar la tabla, en la clase controladora que hemos llamado MasterViewController, vamos al .h y vamos a crear un array para poblar la tabla, una propiedad para nuestro AnimationController y nos vamos a acoger a los protocolos que vamos a necesitar para hacer nuestras transiciones, nos quedaría de esta forma;


#import 
#import "IACAnimationController.h"

@interface IACMasterViewController : UITableViewController<UINavigationControllerDelegate,UIViewControllerTransitioningDelegate>

@property(nonatomic,strong) NSArray *transitionsTypes;
@property(nonatomic, strong) id<IACAnimationControllerDelegate>animationController;

@end

Bien en el .m vamos a iniciar el array y decirle a la tabla que tiene 1 sección y dos celdas y poblamos las celdas con el array, también vamos implementar el método didSelectRowAtIndexPath: para la acción de pulsar la celda y decirle al controlador quién es el delegado del UINavigationController, nos quedaría de esta forma;


- (void)viewDidLoad
{
    [super viewDidLoad];
    self.navigationController.delegate = self;
    self.transitionsTypes = @[@"Transición modal",@"Transición de navegación"];
}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    // Return the number of sections.
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // Return the number of rows in the section.
    return 2;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
    
    cell.textLabel.text = [self.transitionsTypes objectAtIndex: indexPath.row];
    
    return cell;
}

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    
}

Bien, ahora vamos a crear otro controlador en el storyboard que sea un UIViewController y colocamos una imagen de fondo, y un botón en la parte inferior.

Vamos a crear su clase y la Llamaremos ImageViewController, después vamos a crear los Outlets para crear la acción del botón y la propiedad de la imageView, también vamos a indicarle al storyboard el ID de nuestro controlador;

Vamos a indicar en el IBAction del botón que queremos hacer un DissMiss;


- (IBAction)actionBack:(id)sender {
    [self dismissViewControllerAnimated:YES completion:NULL];
}

Ahora vamos a importar nuestro nuevo controlador y la clase ModalTransition en la clase MasterViewController y en el método de selección de la celda haremos lo siguiente;


-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    
    IACImageViewController *detailVC = [self.storyboard instantiateViewControllerWithIdentifier:@"IACImageViewController"];
    detailVC.transitioningDelegate = self;
    
    
    switch (indexPath.row) {
        case 0:
            self.animationController = [IACModalTransition new];
            [self presentViewController:detailVC animated:YES completion:nil];
            break;
    }
    
}

Ahora vamos a implementar en este mismo controlador los métodos para las transiciones modales;


#pragma mark UIViewControllerAnimatedTransitioning

-(id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source{
    
    self.animationController.isPresenting = YES;
    return self.animationController;
}

-(id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed{
    
    self.animationController.isPresenting = NO;
    return self.animationController;
}

Y ya por fin volvemos a la clase ModalTransition para hacer la animación, voy a comentar el código para que quede claro lo que vamos a ir haciendo;



-(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext{
    
    
    self.transitionContext = transitionContext;
    
    if(self.isPresenting){
        [self animatePresenting:transitionContext];
    }
    else{
        
        [self animateDissMiss:transitionContext];
    }
    
}

-(void)animatePresenting:(id<UIViewControllerContextTransitioning>)transitionContext
{
    //Obtenemos los controladores del contexto
    UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    
    //obtenemos la camptura del controlador del que venimos para animar una captura en vez de una vista
    UIView *capture1 = [[fromViewController.view snapshotViewAfterScreenUpdates:YES] resizableSnapshotViewFromRect:CGRectMake(0, 0, 160, 640) afterScreenUpdates:YES withCapInsets:UIEdgeInsetsMake(0, 0, 0, 0)];
    UIView*capture2 = [[fromViewController.view snapshotViewAfterScreenUpdates:YES] resizableSnapshotViewFromRect:CGRectMake(160, 0, 160, 640) afterScreenUpdates:YES withCapInsets:UIEdgeInsetsMake(0, 0, 0, 0)];
    capture2.center = CGPointMake(capture1.center.x *3, capture1.center.y);
    
    //creamos la vista contenedora de la animación y borro la vista del controlador del que venimos
    UIView *containerView = [transitionContext containerView];
    [fromViewController.view removeFromSuperview];
    
    //añando la vista
    [containerView addSubview:toViewController.view];
    //preparo la captura
    [containerView addSubview:capture1];
    [containerView addSubview:capture2];
    
    //seteo el estado inicial del controlador al que vamos
    CATransform3D scale = CATransform3DIdentity;
    toViewController.view.layer.transform = CATransform3DScale(scale, 0.0, 0.0, 1);
    toViewController.view.alpha = 0.6;
    
    // obtengo las transformaciones
    CATransform3D t1 = [self firstTransform];
    CATransform3D t2 = [self secondTransform];
    
    [UIView animateKeyframesWithDuration:self.presentationDuration
                                   delay:0.0
                                 options:UIViewKeyframeAnimationOptionCalculationModeLinear
                              animations:^{
                                  
                                  [UIView addKeyframeWithRelativeStartTime:0.0f
                                                          relativeDuration:0.5f
                                                                animations:^{
                                                                    //animación del fromVieController dividiendose en dos
                                                                    capture1.layer.transform = t1;
                                                                    capture1.center = CGPointMake(capture1.center.x - 120, capture1.center.y
                                                                                                  );
                                                                    capture2.layer.transform = t2;
                                                                    capture2.center = CGPointMake(capture2.center.x + 120, capture1.center.y
                                                                                                  );
                                                                }];
                                  
                                  [UIView addKeyframeWithRelativeStartTime:0.5f
                                                          relativeDuration:0.5f
                                                                animations:^{
                                                                    //animación del toVieController acercandose
                                                                    toViewController.view.layer.transform = CATransform3DIdentity;
                                                                    toViewController.view.alpha = 1.0f;
                                                                }];
                                  
                              } completion:^(BOOL finished) {
                                  
                                  [self.transitionContext completeTransition:YES];
                                  
                              }];
    
    
}

-(void)animateDissMiss:(id<UIViewControllerContextTransitioning>)transitionContext
{
    
    //Obtenemos los controladores del contexto
    UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    
    //obtenemos la captura del controlador al que vamos, la dividimos en dos Y LA DEJAMOS YA ROTADA.
    UIView *capture1 = [[toViewController.view snapshotViewAfterScreenUpdates:YES] resizableSnapshotViewFromRect:CGRectMake(0, 0, 160, 640) afterScreenUpdates:YES withCapInsets:UIEdgeInsetsMake(0, 0, 0, 0)];
    capture1.layer.transform = [self firstTransform];
    capture1.center = CGPointMake(capture1.center.x - 120, capture1.center.y);
    UIView*capture2 = [[toViewController.view snapshotViewAfterScreenUpdates:YES] resizableSnapshotViewFromRect:CGRectMake(160, 0, 160, 640) afterScreenUpdates:YES withCapInsets:UIEdgeInsetsMake(0, 0, 0, 0)];
    capture2.center = CGPointMake(capture2.center.x *3  + 120, capture2.center.y);
    capture2.layer.transform = [self secondTransform];
    
    // obtengo la captura del controlador del que venimos
    UIView *intermediateView = [fromViewController.view snapshotViewAfterScreenUpdates:YES];
    
    //creamos la vista contenedora de la animación y borro la vista del controlador del que venimos
    UIView *containerView = [transitionContext containerView];
    [fromViewController.view removeFromSuperview];
    
    //añando la vista las campuras
    [containerView addSubview:intermediateView];
    [containerView addSubview:capture1];
    [containerView addSubview:capture2];
    
    CATransform3D scale = CATransform3DIdentity;
    
    [UIView animateKeyframesWithDuration:self.presentationDuration
                                   delay:0.0
                                 options:UIViewKeyframeAnimationOptionCalculationModeLinear
                              animations:^{
                                  
                                  [UIView addKeyframeWithRelativeStartTime:0.0f
                                                          relativeDuration:0.5f
                                                                animations:^{
                                                                    
                                                                    //animación del fromViewController alejandose
                                                                    intermediateView.layer.transform = CATransform3DScale(scale, 0.0, 0.0, 1);
                                                                    intermediateView.alpha = 0.6;
                                                                    
                                                                }];
                                  
                                  [UIView addKeyframeWithRelativeStartTime:0.5f
                                                          relativeDuration:0.5f
                                                                animations:^{
                                                                    
                                                                    //animación del toVieController uniendose de las dos partes
                                                                    capture1.layer.transform = CATransform3DIdentity;
                                                                    capture1.center = CGPointMake(capture1.center.x + 120, capture1.center.y
                                                                                                  );
                                                                    capture2.layer.transform = CATransform3DIdentity;
                                                                    capture2.center = CGPointMake(capture2.center.x - 120, capture1.center.y);
                                                                    
                                                                }];
                                  
                              } completion:^(BOOL finished) {
                                  
                                  [self.transitionContext completeTransition:YES];
                                  
                              }];
    
    
}

-(CATransform3D)firstTransform{
    
    CATransform3D transform = CATransform3DIdentity;
    transform.m34           = 1.0/-900;
    transform               = CATransform3DRotate(transform, 90.0f, 0, 1, 0);
    return transform;
    
}

-(CATransform3D)secondTransform{
    
    CATransform3D transform = CATransform3DIdentity;
    transform.m34           = 1.0/-900;
    transform               = CATransform3DRotate(transform, -90.0f, 0, 1, 0);
    return transform;
    
}

Si compilamos ahora veréis que hace una animación que divide en dos el controlador actual y acerca el siguiente.

Ahora vamos a hacer una transición para el UINavigationController, vamos a crear una clase que herede NSObject llamada NavigationTransition y vamos a dejar nuestro .h de la misma forma que el ModalTransition;


#import <Foundation/Foundation.h>
#import "IACAnimationController.h"

@interface IACNavigationTransition : NSObject<IACAnimationControllerDelegate>

@property (nonatomic        ) BOOL           isPresenting;
@property (nonatomic, assign) NSTimeInterval presentationDuration;
@property (nonatomic, assign) NSTimeInterval dismissalDuration;
@property (nonatomic, strong) id<UIViewControllerContextTransitioning> transitionContext;

-(NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext;
-(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext;


@end

El .m vamos a dejarlo de esta forma;



#import "IACNavigationTransition.h"

@implementation IACNavigationTransition



-(id)init{
    if (self = [super init]) {
        self.presentationDuration = 1.0;
        self.dismissalDuration = 1.0;
    }
    return self;
}

-(NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
    //determinamos el tiempo que durará la animación
    return self.isPresenting ? self.presentationDuration:self.dismissalDuration;
}

-(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
    
    self.transitionContext = transitionContext;
    if (self.isPresenting) {
        [self animatePresenting:self.transitionContext];
    }else{
        [self animateDissMiss:self.transitionContext];
    }
}

-(void)animatePresenting:(id<UIViewControllerContextTransitioning>)transitionContext
{
    
    
    UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    
    UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    
    UIView *container = [transitionContext containerView];
    
    
    fromViewController.view.frame = container.frame;
    toViewController.view.frame = container.frame;
    
    // coloco el toViewController en la aprte superior y boca abajo
    toViewController.view.transform = CGAffineTransformMakeRotation(-M_PI);
    toViewController.view.layer.anchorPoint = CGPointMake(0.5, 0.0);
    toViewController.view.layer.position = CGPointMake(160.0, 0);
    
    //inserto los controladores en el contenedor
    [container insertSubview:toViewController.view belowSubview:fromViewController.view];
    
    [UIView animateWithDuration:1.0
                          delay:0.0
         usingSpringWithDamping:.8
          initialSpringVelocity:6.0
                        options:UIViewAnimationOptionCurveEaseIn
     
                     animations:^{
                         
                         toViewController.view.layer.transform = CATransform3DIdentity;
                         
                         fromViewController.view.center = CGPointMake(-container.frame.size.width, container.frame.size.height/2);
                         fromViewController.view.transform = CGAffineTransformMakeRotation(M_PI/2);
                         
                     } completion:^(BOOL finished) {
                         
                         [transitionContext completeTransition:YES];
                         
                     }];
}

-(void)animateDissMiss:(id<UIViewControllerContextTransitioning>)transitionContext
{
    
    UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    
    UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    
    CGRect initialRect = [transitionContext initialFrameForViewController:fromViewController];
    
    //coloco el formViewController en el centro y le indico su punto de anclaje y la posición desde la que rota
    CGAffineTransform rotation;
    rotation = CGAffineTransformMakeRotation(M_PI);
    fromViewController.view.frame = initialRect;
    fromViewController.view.layer.anchorPoint = CGPointMake(0.5, 0.0);
    fromViewController.view.layer.position = CGPointMake(160.0, 0);
    
    //inserto los controladores en el contenedor
    UIView *container = [transitionContext containerView];
    [container insertSubview:toViewController.view belowSubview:fromViewController.view];
    
    //coloco el toViewController del revés y en la parte izquierda
    toViewController.view.center    = CGPointMake(-initialRect.size.width, initialRect.size.height);
    toViewController.view.transform = rotation;
    
    
    [UIView animateWithDuration:1.0
                          delay:0.0
         usingSpringWithDamping:.8
          initialSpringVelocity:6.0
                        options:UIViewAnimationOptionCurveEaseIn
     
                     animations:^{
                         //rotamos el controlador del que venimos hacia arriba y centramos al que vamos
                         fromViewController.view.transform = rotation;
                         toViewController.view.frame       = initialRect;
                         toViewController.view.transform   = CGAffineTransformMakeRotation(0);
                     } completion:^(BOOL finished) {
                         
                         [transitionContext completeTransition:YES];
                         
                     }];
}


Esta animación rota las vistas con un efecto de rebote muy chulo ;-).

Ahora vamos a implementar los métodos para que nuestro UINavigationController ejecute nuestra transición;


#pragma mark - navigationController Delegate

-(id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC{
    
    
    switch (operation) {
        case UINavigationControllerOperationPush:
            self.animationController.isPresenting = YES;
            return self.animationController;
        case UINavigationControllerOperationPop:
            self.animationController.isPresenting = NO;
            return self.animationController;
        default: return nil;
    }
    
}

4. Conclusiones.

Pues como veis se pueden hacer cosas muy chulas con las transiciones personalizadas, podéis bajaros el proyecto aquí;

1 COMENTARIO

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad