J'ai suivi des tutoriels pour créer une animation personnalisée tout en passant d'une vue à une autre.
Mon projet de test utilisant la séquence personnalisée de ici fonctionne bien, mais quelqu'un m'a dit que ce n'était plus encouragé de créer une animation personnalisée dans une séquence personnalisée et que je devrais utiliser UIViewControllerAnimatedTransitioning
.
J'ai suivi plusieurs tutoriels utilisant ce protocole, mais ils concernent tous une présentation modale (par exemple ce tutoriel ).
Ce que j'essaie de faire, c'est une séquence Push dans un arbre de contrôleur de navigation, mais lorsque j'essaie de faire la même chose avec une séquence Show (Push), cela ne fonctionne plus.
Dites-moi s'il vous plaît la bonne façon de faire une animation de transition personnalisée d'une vue à une autre dans un contrôleur de navigation.
Et puis-je quand même utiliser une méthode pour toutes les animations de transition? Ce serait gênant si un jour je veux faire la même animation mais que je finisse par devoir dupliquer le code deux fois pour travailler sur la transition modale par rapport au contrôleur.
Pour effectuer une transition personnalisée avec le contrôleur de navigation (UINavigationController
), vous devez:
Définissez votre contrôleur de vue pour qu'il soit conforme au protocole UINavigationControllerDelegate
. Par exemple, vous pouvez avoir une extension de classe privée dans le fichier .m
De votre contrôleur de vue spécifiant la conformité à ce protocole:
@interface ViewController () <UINavigationControllerDelegate>
@end
Assurez-vous de bien spécifier votre contrôleur de vue en tant que délégué de votre contrôleur de navigation:
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationController.delegate = self;
}
Implémentez animationControllerForOperation
dans votre contrôleur de vue:
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController*)fromVC
toViewController:(UIViewController*)toVC
{
if (operation == UINavigationControllerOperationPush)
return [[PushAnimator alloc] init];
if (operation == UINavigationControllerOperationPop)
return [[PopAnimator alloc] init];
return nil;
}
Implémenter des animateurs pour les animations Push et Pop, par exemple:
@interface PushAnimator : NSObject <UIViewControllerAnimatedTransitioning>
@end
@interface PopAnimator : NSObject <UIViewControllerAnimatedTransitioning>
@end
@implementation PushAnimator
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
return 0.5;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
[[transitionContext containerView] addSubview:toViewController.view];
toViewController.view.alpha = 0.0;
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
toViewController.view.alpha = 1.0;
} completion:^(BOOL finished) {
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
@end
@implementation PopAnimator
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
return 0.5;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
[[transitionContext containerView] insertSubview:toViewController.view belowSubview:fromViewController.view];
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
fromViewController.view.alpha = 0.0;
} completion:^(BOOL finished) {
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
@end
Cela fait disparaître la transition, mais vous devriez vous sentir libre de personnaliser l'animation comme bon vous semble.
Si vous souhaitez gérer les gestes interactifs (par exemple, un glissement natif de gauche à droite), vous devez implémenter un contrôleur d'interaction:
Définissez une propriété pour un contrôleur d'interaction (un objet conforme à UIViewControllerInteractiveTransitioning
):
@property (nonatomic, strong) UIPercentDrivenInteractiveTransition *interactionController;
This UIPercentDrivenInteractiveTransition
est un objet Nice qui effectue la mise à jour de votre animation personnalisée en fonction de la complétude du geste.
Ajoutez un identificateur de geste à votre vue. Ici, je suis juste en train d'implémenter le logiciel de reconnaissance de geste gauche pour simuler un pop:
UIScreenEdgePanGestureRecognizer *Edge = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:@selector(handleSwipeFromLeftEdge:)];
Edge.edges = UIRectEdgeLeft;
[view addGestureRecognizer:Edge];
Implémentez le gestionnaire de reconnaissance de gestes:
/** Handle swipe from left Edge
*
* This is the "action" selector that is called when a left screen Edge gesture recognizer starts.
*
* This will instantiate a UIPercentDrivenInteractiveTransition when the gesture starts,
* update it as the gesture is "changed", and will finish and release it when the gesture
* ends.
*
* @param gesture The screen Edge pan gesture recognizer.
*/
- (void)handleSwipeFromLeftEdge:(UIScreenEdgePanGestureRecognizer *)gesture {
CGPoint translate = [gesture translationInView:gesture.view];
CGFloat percent = translate.x / gesture.view.bounds.size.width;
if (gesture.state == UIGestureRecognizerStateBegan) {
self.interactionController = [[UIPercentDrivenInteractiveTransition alloc] init];
[self popViewControllerAnimated:TRUE];
} else if (gesture.state == UIGestureRecognizerStateChanged) {
[self.interactionController updateInteractiveTransition:percent];
} else if (gesture.state == UIGestureRecognizerStateEnded) {
CGPoint velocity = [gesture velocityInView:gesture.view];
if (percent > 0.5 || velocity.x > 0) {
[self.interactionController finishInteractiveTransition];
} else {
[self.interactionController cancelInteractiveTransition];
}
self.interactionController = nil;
}
}
Dans votre délégué de contrôleur de navigation, vous devez également implémenter la méthode interactionControllerForAnimationController
delegate
- (id<UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController
interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController {
return self.interactionController;
}
Si vous google "tutoriel de transition personnalisé UINavigationController" et vous obtiendrez de nombreux hits. Ou voir vidéo WWDC 2013 Custom Transitions .
Vous voudrez peut-être ajouter le code suivant avant addSubview
toViewController.view.frame = [transitionContext finalFrameForViewController:toViewController];
D'une autre question custom-transition-for-Push-animation-with-navigationcontroller-on-ios-9
Extrait de la documentation Apple pour finalFrameForViewController:
Renvoie le rectangle du cadre de fin pour la vue du contrôleur de vue spécifié.
Le rectangle renvoyé par cette méthode représente la taille de la vue correspondante à la fin de la transition. Pour la vue couverte lors de la présentation, la valeur renvoyée par cette méthode peut être CGRectZero, mais il peut également s'agir d'un rectangle de cadre valide.
En utilisant les réponses parfaites de Rob's & Qi, voici le code simplifié Swift, utilisant la même animation de fondu pour .Push et .pop:
extension YourViewController: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController,
animationControllerFor operation: UINavigationControllerOperation,
from fromVC: UIViewController,
to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
//INFO: use UINavigationControllerOperation.Push or UINavigationControllerOperation.pop to detect the 'direction' of the navigation
class FadeAnimation: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.5
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)
if let vc = toViewController {
transitionContext.finalFrame(for: vc)
transitionContext.containerView.addSubview(vc.view)
vc.view.alpha = 0.0
UIView.animate(withDuration: self.transitionDuration(using: transitionContext),
animations: {
vc.view.alpha = 1.0
},
completion: { finished in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
})
} else {
NSLog("Oops! Something went wrong! 'ToView' controller is nill")
}
}
}
return FadeAnimation()
}
}
N'oubliez pas de définir le délégué dans la méthode viewDidLoad () de YourViewController:
override func viewDidLoad() {
//...
self.navigationController?.delegate = self
//...
}
Cela fonctionne à la fois Swift 3 et 4
@IBAction func NextView(_ sender: UIButton) {
let newVC = self.storyboard?.instantiateViewControllerWithIdentifier(withIdentifier: "NewVC") as! NewViewController
let transition = CATransition()
transition.duration = 0.5
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.type = kCATransitionPush
transition.subtype = kCAGravityLeft
//instead "kCAGravityLeft" try with different transition subtypes
self.navigationController?.view.layer.add(transition, forKey: kCATransition)
self.navigationController?.pushViewController(newVC, animated: false)
}