Je dois effectuer certaines actions lorsque le bouton Précédent (revenir à l'écran précédent, revenir à la vue parent) est activé sur une barre de navigation.
Existe-t-il une méthode que je peux mettre en œuvre pour intercepter l'événement et déclencher certaines actions pour mettre en pause et enregistrer les données avant la disparition de l'écran?
UPDATE: Selon certains commentaires, la solution de la réponse d'origine ne semble pas fonctionner sous certains scénarios sous iOS 8+. Je ne peux pas vérifier que c'est bien le cas sans plus de détails.
Pour ceux d'entre vous cependant dans cette situation, il existe une alternative. Il est possible de détecter le déclenchement d'un contrôleur de vue en surchargeant willMove(toParentViewController:)
. L'idée de base est qu'un contrôleur de vue est affiché lorsque parent
est nil
.
Départ "Implémentation d'un contrôleur de vue de conteneur" pour plus de détails.
Depuis iOS 5, le moyen le plus simple de gérer cette situation consiste à utiliser la nouvelle méthode - (BOOL)isMovingFromParentViewController
:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (self.isMovingFromParentViewController) {
// Do your stuff here
}
}
- (BOOL)isMovingFromParentViewController
est logique lorsque vous poussez et éjectez des contrôleurs dans une pile de navigation.
Toutefois, si vous présentez des contrôleurs de vue modale, utilisez plutôt - (BOOL)isBeingDismissed
:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (self.isBeingDismissed) {
// Do your stuff here
}
}
Comme indiqué dans cette question , vous pouvez combiner les deux propriétés:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (self.isMovingFromParentViewController || self.isBeingDismissed) {
// Do your stuff here
}
}
D'autres solutions reposent sur l'existence d'un UINavigationBar
. C’est plutôt mon approche qui différencie les tâches à exécuter de l’action qui a déclenché l’événement, c’est-à-dire qui appuie sur un bouton Précédent.
Bien que viewWillAppear()
et viewDidDisappear()
soient appelés lorsque vous appuyez sur le bouton Précédent, ils sont également appelés à d'autres moments. . Voir la fin de la réponse pour plus d'informations à ce sujet.
La détection du bouton Précédent est plus efficace lorsque le VC est supprimé de son parent (le NavigationController) à l’aide de willMoveToParentViewController(_:)
_ OR didMoveToParentViewController()
Si parent est nil, le contrôleur de vue est extrait de la pile de navigation et supprimé. Si parent n'est pas nil, il est ajouté à la pile et présenté.
// Objective-C
-(void)willMoveToParentViewController:(UIViewController *)parent {
[super willMoveToParentViewController:parent];
if (!parent){
// The back button was pressed or interactive gesture used
}
}
// Swift
override func willMove(toParentViewController parent: UIViewController?) {
super.willMove(toParentViewController:parent)
if parent == nil {
// The back button was pressed or interactive gesture used
}
}
Remplacez willMove
par didMove
et vérifiez que self.parent fonctionne après le contrôleur de vue est ignoré.
Notez que vérifier le parent ne vous permet pas de "suspendre" la transition si vous devez effectuer une sorte de sauvegarde asynchrone. Pour ce faire, vous pouvez implémenter ce qui suit. Le seul inconvénient est que vous perdez le bouton arrière stylé/animé pour iOS. Faites également attention ici avec le geste de balayage interactif. Utilisez ce qui suit pour gérer ce cas.
var backButton : UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
// Disable the swipe to make sure you get your chance to save
self.navigationController?.interactivePopGestureRecognizer.enabled = false
// Replace the default back button
self.navigationItem.setHidesBackButton(true, animated: false)
self.backButton = UIBarButtonItem(title: "Back", style: UIBarButtonItemStyle.Plain, target: self, action: "goBack")
self.navigationItem.leftBarButtonItem = backButton
}
// Then handle the button selection
func goBack() {
// Here we just remove the back button, you could also disabled it or better yet show an activityIndicator
self.navigationItem.leftBarButtonItem = nil
someData.saveInBackground { (success, error) -> Void in
if success {
self.navigationController?.popViewControllerAnimated(true)
// Don't forget to re-enable the interactive gesture
self.navigationController?.interactivePopGestureRecognizer.enabled = true
}
else {
self.navigationItem.leftBarButtonItem = self.backButton
// Handle the error
}
}
}
Si vous n'avez pas eu le problème viewWillAppear
viewDidDisappear
, voyons un exemple. Disons que vous avez trois contrôleurs de vue:
Suivons les appels sur le detailVC
à mesure que vous passez de listVC
à settingsVC
et revenez à listVC
Liste> Détails (Push detailVC) Detail.viewDidAppear
<- apparaît
Détail> Paramètres (Paramètres Push VC) Detail.viewDidDisappear
<- disparaître
Et comme nous revenons en arrière ...
Paramètres> Détails (paramètres contextuels VC) Detail.viewDidAppear
<- apparaît
Détail> Liste (pop detailVC) Detail.viewDidDisappear
<- disparaître
Notez que viewDidDisappear
est appelé plusieurs fois, non seulement lors des retours, mais aussi lors des prochaines étapes. Ce n'est peut-être pas le cas pour une opération rapide, mais pas pour une opération plus complexe, telle qu'un appel réseau à enregistrer.
Première méthode
- (void)didMoveToParentViewController:(UIViewController *)parent
{
if (![parent isEqual:self.parentViewController]) {
NSLog(@"Back pressed");
}
}
Deuxième méthode
-(void) viewWillDisappear:(BOOL)animated {
if ([self.navigationController.viewControllers indexOfObject:self]==NSNotFound) {
// back button was pressed. We know this is true because self is no longer
// in the navigation stack.
}
[super viewWillDisappear:animated];
}
J'ai joué (ou combattu) avec ce problème pendant deux jours. La meilleure approche à l’OMI consiste simplement à créer une classe d’extension et un protocole, comme ceci:
@protocol UINavigationControllerBackButtonDelegate <NSObject>
/**
* Indicates that the back button was pressed.
* If this message is implemented the pop logic must be manually handled.
*/
- (void)backButtonPressed;
@end
@interface UINavigationController(BackButtonHandler)
@end
@implementation UINavigationController(BackButtonHandler)
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
UIViewController *topViewController = self.topViewController;
BOOL wasBackButtonClicked = topViewController.navigationItem == item;
SEL backButtonPressedSel = @selector(backButtonPressed);
if (wasBackButtonClicked && [topViewController respondsToSelector:backButtonPressedSel]) {
[topViewController performSelector:backButtonPressedSel];
return NO;
}
else {
[self popViewControllerAnimated:YES];
return YES;
}
}
@end
Cela fonctionne car UINavigationController
recevra un appel à navigationBar:shouldPopItem:
chaque fois qu'un contrôleur de vue est affiché. Là nous détectons si le dos a été appuyé ou pas (n'importe quel autre bouton). La seule chose à faire est d'implémenter le protocole dans le contrôleur de vue sur lequel vous appuyez sur la touche Retour.
N'oubliez pas de faire apparaître manuellement le contrôleur de vue à l'intérieur de backButtonPressedSel
, si tout va bien.
Si vous avez déjà sous-classé UINavigationViewController
et implémenté navigationBar:shouldPopItem:
ne vous inquiétez pas, cela n’interférera pas.
Vous pouvez également être intéressé par désactiver le geste du dos.
if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}
Cela fonctionne pour moi dans iOS 9.3.x avec Swift:
override func didMoveToParentViewController(parent: UIViewController?) {
super.didMoveToParentViewController(parent)
if parent == self.navigationController?.parentViewController {
print("Back tapped")
}
}
Contrairement aux autres solutions proposées ici, cela ne semble pas se déclencher de manière inattendue.
Ceux qui prétendent que cela ne fonctionne pas se trompent:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if self.isMovingFromParent {
print("we are being popped")
}
}
Cela fonctionne bien. Alors, qu'est-ce qui cause le mythe répandu que ce n'est pas le cas?
Le problème semble être dû à une implémentation incorrecte d'une méthode différente, à savoir que l'implémentation de willMove(toParent:)
a oublié d'appeler super
.
Si vous implémentez willMove(toParent:)
sans appeler super
, alors self.isMovingFromParent
Sera false
et l'utilisation de viewWillDisappear
semblera échouer. Cela n'a pas échoué tu l'as cassé.
Pour mémoire, je pense que c'est plus ce qu'il cherchait…
UIBarButtonItem *l_backButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRewind target:self action:@selector(backToRootView:)];
self.navigationItem.leftBarButtonItem = l_backButton;
- (void) backToRootView:(id)sender {
// Perform some custom code
[self.navigationController popToRootViewControllerAnimated:YES];
}
Comme purrrminator
dit, la réponse de elitalon
n’est pas tout à fait juste, puisque your stuff
serait exécuté même lors du démarrage programmé du contrôleur.
La solution que j'ai trouvée jusqu'à présent n'est pas très gentille, mais ça marche pour moi. Outre ce que elitalon
a dit, je vérifie également si j'utilise le programme par programmation ou non:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if ((self.isMovingFromParentViewController || self.isBeingDismissed)
&& !self.isPoppingProgrammatically) {
// Do your stuff here
}
}
Vous devez ajouter cette propriété à votre contrôleur et lui attribuer la valeur YES avant d'afficher le programme par programmation:
self.isPoppingProgrammatically = YES;
[self.navigationController popViewControllerAnimated:YES];
Merci de votre aide!
Le meilleur moyen consiste à utiliser les méthodes de délégation UINavigationController
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
En utilisant cela, vous pouvez savoir quel contrôleur affiche le UINavigationController.
if ([viewController isKindOfClass:[HomeController class]]) {
NSLog(@"Show home controller");
}
Pour Swift avec un UINavigationController:
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
if self.navigationController?.topViewController != self {
print("back button tapped")
}
}
Comme l'a dit Coli88, vous devriez vérifier le protocole UINavigationBarDelegate.
De manière plus générale, vous pouvez également utiliser la fonction - (void)viewWillDisapear:(BOOL)animated
pour effectuer un travail personnalisé lorsque la vue conservée par le contrôleur de vue actuellement visible est sur le point de disparaître. Malheureusement, cela couvrirait les affaires Push et Pop.
J'ai résolu ce problème en ajoutant un UIControl à la barre de navigation située à gauche.
UIControl *leftBarItemControl = [[UIControl alloc] initWithFrame:CGRectMake(0, 0, 90, 44)];
[leftBarItemControl addTarget:self action:@selector(onLeftItemClick:) forControlEvents:UIControlEventTouchUpInside];
self.leftItemControl = leftBarItemControl;
[self.navigationController.navigationBar addSubview:leftBarItemControl];
[self.navigationController.navigationBar bringSubviewToFront:leftBarItemControl];
Et vous devez vous rappeler de le supprimer lorsque la vue disparaîtra:
- (void) viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
if (self.leftItemControl) {
[self.leftItemControl removeFromSuperview];
}
}
C'est tout!
Vous devriez vérifier INavigationBarDelegate Protocol . Dans ce cas, vous pouvez utiliser la méthode navigationBar: shouldPopItem:.
La réponse de 7ynk3r était très proche de ce que j'avais utilisé à la fin, mais il fallait quelques ajustements:
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {
UIViewController *topViewController = self.topViewController;
BOOL wasBackButtonClicked = topViewController.navigationItem == item;
if (wasBackButtonClicked) {
if ([topViewController respondsToSelector:@selector(navBackButtonPressed)]) {
// if user did press back on the view controller where you handle the navBackButtonPressed
[topViewController performSelector:@selector(navBackButtonPressed)];
return NO;
} else {
// if user did press back but you are not on the view controller that can handle the navBackButtonPressed
[self popViewControllerAnimated:YES];
return YES;
}
} else {
// when you call popViewController programmatically you do not want to pop it twice
return YES;
}
}
Vous pouvez utiliser le rappel du bouton de retour, comme ceci:
- (BOOL) navigationShouldPopOnBackButton
{
[self backAction];
return NO;
}
- (void) backAction {
// your code goes here
// show confirmation alert, for example
// ...
}
pour Swift vous pouvez faire quelque chose comme dans la portée globale
extension UIViewController {
@objc func navigationShouldPopOnBackButton() -> Bool {
return true
}
}
extension UINavigationController: UINavigationBarDelegate {
public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
return self.topViewController?.navigationShouldPopOnBackButton() ?? true
}
}
En dessous de celui que vous placez dans le contrôleur de vue où vous souhaitez contrôler l'action du bouton Précédent:
override func navigationShouldPopOnBackButton() -> Bool {
self.backAction()//Your action you want to perform.
return true
}
self.navigationController.isMovingFromParentViewController ne fonctionne plus sous iOS8 et 9 J'utilise:
-(void) viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
if (self.navigationController.topViewController != self)
{
// Is Popping
}
}