Dato che questo è il miglior risultato su Google, ho pensato di condividere quello che penso sia il modo più sano; che consiste nell'utilizzare l'API di transizione iOS 7+. Ho implementato questo per iOS 10 con Swift 3.
È abbastanza semplice combinare questo con il modo in cui si UINavigationController
anima tra due controller di visualizzazione se si crea una sottoclasse di UINavigationController
e si restituisce un'istanza di una classe conforme al UIViewControllerAnimatedTransitioning
protocollo.
Ad esempio, ecco la mia UINavigationController
sottoclasse:
class NavigationController: UINavigationController {
init() {
super.init(nibName: nil, bundle: nil)
delegate = self
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension NavigationController: UINavigationControllerDelegate {
public func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return NavigationControllerAnimation(operation: operation)
}
}
Puoi vedere che ho impostato il valore UINavigationControllerDelegate
su se stesso e in un'estensione nella mia sottoclasse implemento il metodo UINavigationControllerDelegate
che ti consente di restituire un controller di animazione personalizzato (ovvero, NavigationControllerAnimation
). Questo controller di animazione personalizzato sostituirà l'animazione stock per te.
Probabilmente ti starai chiedendo perché passo l'operazione NavigationControllerAnimation
all'istanza tramite il suo inizializzatore. Lo faccio in modo tale che NavigationControllerAnimation
, nell'implementazione del UIViewControllerAnimatedTransitioning
protocollo, sappia quale sia l'operazione (ovvero, "push" o "pop"). Questo aiuta a sapere che tipo di animazione dovrei fare. Il più delle volte, si desidera eseguire un'animazione diversa a seconda dell'operazione.
Il resto è piuttosto standard. Implementa le due funzioni richieste nel UIViewControllerAnimatedTransitioning
protocollo e anima come preferisci:
class NavigationControllerAnimation: NSObject, UIViewControllerAnimatedTransitioning {
let operation: UINavigationControllerOperation
init(operation: UINavigationControllerOperation) {
self.operation = operation
super.init()
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.3
}
public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from),
let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) else { return }
let containerView = transitionContext.containerView
if operation == .push {
// do your animation for push
} else if operation == .pop {
// do your animation for pop
}
}
}
È importante ricordare che per ogni diverso tipo di operazione (ad esempio, "push" o "pop"), i controller da e verso la vista saranno diversi. Quando si esegue un'operazione push, il controller per visualizzare sarà quello che viene premuto. Quando ci si trova in un'operazione pop, il controller di visualizzazione sarà quello a cui si sta eseguendo la transizione e il controller di visualizzazione sarà quello che viene visualizzato.
Inoltre, il to
controller di visualizzazione deve essere aggiunto come sottoview containerView
nel contesto di transizione.
Al termine dell'animazione, è necessario chiamare transitionContext.completeTransition(true)
. Se stai eseguendo una transizione interattiva, dovrai restituire dinamicamente un Bool
a completeTransition(didComplete: Bool)
, a seconda che la transizione sia completa alla fine dell'animazione.
Infine ( lettura facoltativa ), potresti voler vedere come ho fatto la transizione su cui stavo lavorando. Questo codice è un po 'più confuso e l'ho scritto abbastanza rapidamente, quindi non direi che è un ottimo codice di animazione ma mostra comunque come eseguire la parte di animazione.
La mia è stata una transizione davvero semplice; Volevo imitare la stessa animazione di UINavigationController in genere, ma invece dell'animazione "Pagina successiva sopra", volevo implementare un'animazione 1: 1 del vecchio controller di vista contemporaneamente alla nuova vista appare il controller. Ciò ha l'effetto di far sembrare i due controller di vista come se fossero bloccati l'uno all'altro.
Per l'operazione push, ciò richiede prima di impostare l' toViewController
origine della vista sullo schermo dell'asse x, aggiungendola come sottoview della containerView
, animandola sullo schermo impostandola origin.x
su zero. Allo stesso tempo, anima la fromViewController
vista di distanza impostandola origin.x
fuori dallo schermo:
toViewController.view.frame = containerView.bounds.offsetBy(dx: containerView.frame.size.width, dy: 0.0)
containerView.addSubview(toViewController.view)
UIView.animate(withDuration: transitionDuration(using: transitionContext),
delay: 0,
options: [ UIViewAnimationOptions.curveEaseOut ],
animations: {
toViewController.view.frame = containerView.bounds
fromViewController.view.frame = containerView.bounds.offsetBy(dx: -containerView.frame.size.width, dy: 0)
},
completion: { (finished) in
transitionContext.completeTransition(true)
})
L'operazione pop è sostanzialmente l'inverso. Aggiungi toViewController
come sottoview di containerView
, e anima via fromViewController
a destra mentre anima in toViewController
da sinistra:
containerView.addSubview(toViewController.view)
UIView.animate(withDuration: transitionDuration(using: transitionContext),
delay: 0,
options: [ UIViewAnimationOptions.curveEaseOut ],
animations: {
fromViewController.view.frame = containerView.bounds.offsetBy(dx: containerView.frame.width, dy: 0)
toViewController.view.frame = containerView.bounds
},
completion: { (finished) in
transitionContext.completeTransition(true)
})
Ecco un riassunto con l'intero file rapido:
https://gist.github.com/alanzeino/603293f9da5cd0b7f6b60dc20bc766be