web-dev-qa-db-fra.com

Changer le bouton Précédent dans iOS 7 désactive le balayage pour revenir en arrière

J'ai une application iOS 7 sur laquelle je configure un bouton de retour personnalisé comme ceci:

    UIImage *backButtonImage = [UIImage imageNamed:@"back-button"];
    UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom];

    [backButton setImage:backButtonImage forState:UIControlStateNormal];
    backButton.frame = CGRectMake(0, 0, 20, 20);

    [backButton addTarget:self
                   action:@selector(popViewController)
         forControlEvents:UIControlEventTouchUpInside];

    UIBarButtonItem *backBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:backButton];
    viewController.navigationItem.leftBarButtonItem = backBarButtonItem;

Mais cela désactive le geste "glisser de gauche à droite" iOS 7 pour naviguer vers le contrôleur précédent. Est-ce que quelqu'un sait comment je peux définir un bouton personnalisé tout en maintenant ce geste activé?

EDIT: J'ai essayé de définir plutôt viewController.navigationItem.backBarButtonItem, mais cela ne semble pas afficher mon image personnalisée.

77
lehn0058

IMPORTANT: C'est un hack. Je recommanderais de jeter un oeil à cette réponse .

Appeler la ligne suivante après avoir affecté la leftBarButtonItem a fonctionné pour moi:

self.navigationController.interactivePopGestureRecognizer.delegate = self;

Edit: Cela ne fonctionne pas s'il est appelé dans les méthodes init. Il devrait être appelé dans viewDidLoad ou par des méthodes similaires.

76
Paul Hunter

Utilisez si possible les propriétés backIndicatorImage et backIndicatorTransitionMaskImage de UINavigationBar. Le fait de les définir sur un UIAppearanceProxy peut facilement modifier le comportement de votre application. Le problème, c’est que vous ne pouvez les définir que sur ios 7, mais cela fonctionne, car vous ne pouvez utiliser le geste contextuel que sur ios 7 de toute façon. Votre style normal d'ios 6 peut rester intact.

UINavigationBar* appearanceNavigationBar = [UINavigationBar appearance];
//the appearanceProxy returns NO, so ask the class directly
if ([[UINavigationBar class] instancesRespondToSelector:@selector(setBackIndicatorImage:)])
{
    appearanceNavigationBar.backIndicatorImage = [UIImage imageNamed:@"back"];
    appearanceNavigationBar.backIndicatorTransitionMaskImage = [UIImage imageNamed:@"back"];
    //sets back button color
    appearanceNavigationBar.tintColor = [UIColor whiteColor];
}else{
    //do ios 6 customization
}

Essayer de manipuler le délégué de interactivePopGestureRecognizer soulèvera beaucoup de problèmes.

56
Saltymule

J'ai vu cette solution http://keighl.com/post/ios7-interactive-pop-gesture-custom-back-button/ / qui classe UINavigationController. C'est une meilleure solution car elle gère le cas où vous faites glisser votre doigt avant que le contrôleur ne soit en place, ce qui provoque un crash.

En plus de cela, j'ai remarqué que si vous faites un balayage sur le contrôleur de vue racine (après avoir appuyé dessus, puis à nouveau), l'interface utilisateur cesse de répondre (même problème dans la réponse ci-dessus). 

Donc, le code dans le UINavigationController de la sous-classe devrait ressembler à ceci:

@implementation NavigationController

- (void)viewDidLoad {
    [super viewDidLoad];
    __weak NavigationController *weakSelf = self;

    if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
        self.interactivePopGestureRecognizer.delegate = weakSelf;
        self.delegate = weakSelf;
    }
}

- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
    // Hijack the Push method to disable the gesture
    if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
        self.interactivePopGestureRecognizer.enabled = NO;
    }
    [super pushViewController:viewController animated:animated];
}

#pragma mark - UINavigationControllerDelegate

- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animate {
    // Enable the gesture again once the new controller is shown
    self.interactivePopGestureRecognizer.enabled = ([self respondsToSelector:@selector(interactivePopGestureRecognizer)] && [self.viewControllers count] > 1);
}

@end
28
Nick H247

J'utilise 

[[UINavigationBar appearance] setBackIndicatorImage:[UIImage imageNamed:@"nav_back.png"]];
[[UINavigationBar appearance] setBackIndicatorTransitionMaskImage:[UIImage imageNamed:@"nav_back.png"]];

[UIBarButtonItem.appearance setBackButtonTitlePositionAdjustment:UIOffsetMake(0, -64) forBarMetrics:UIBarMetricsDefault];
19
Albert Chu

Voici la version Swift3 de La réponse de Nick H247

class NavigationController: UINavigationController {
  override func viewDidLoad() {
    super.viewDidLoad()
    if responds(to: #selector(getter: interactivePopGestureRecognizer)) {
      interactivePopGestureRecognizer?.delegate = self
      delegate = self
    }
  }

  override func pushViewController(_ viewController: UIViewController, animated: Bool) {
    if responds(to: #selector(getter: interactivePopGestureRecognizer)) {
      interactivePopGestureRecognizer?.isEnabled = false
    }
    super.pushViewController(viewController, animated: animated)
  }
}

extension NavigationController: UINavigationControllerDelegate {
  func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
    interactivePopGestureRecognizer?.isEnabled = (responds(to: #selector(getter: interactivePopGestureRecognizer)) && viewControllers.count > 1)
  }
}

extension NavigationController: UIGestureRecognizerDelegate {}
6
duan
navigationController.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)self;

C'est de http://stuartkhall.com/posts/ios-7-development-tips-tricks-hacks , mais cela cause plusieurs bugs:

  1. Insérez un autre viewController dans le navigationController lorsque vous faites glisser votre doigt depuis le bord gauche de l'écran.
  2. Ou faites glisser votre doigt depuis le bord gauche de l'écran lorsque topViewController apparaît à partir du navigateurController;

par exemple. Lorsque le contrôleur rootView de navigationController est affiché, faites glisser votre doigt depuis le bord gauche de l'écran, puis tapez quelque chose (RAPIDEMENT) pour envoyer un autre contrôleur de navigation dans le contrôleur de navigation,

  • RootViewController ne répond à aucun événement tactile.
  • Le anotherViewController ne sera pas affiché;
  • Balayez de nouveau à partir du bord de l’écran pour afficher le anotherViewController.
  • Appuyez sur le bouton retour personnalisé pour faire apparaître le anotherViewController, crash!

Donc vous devez implémenter la méthode UIGestureRecognizerDelegate dans self.navigationController.interactivePopGestureRecognizer.delegate comme ceci:

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
    if (gestureRecognizer == navigationController.interactivePopGestureRecognizer) {
        return !navigationController.<#TODO: isPushAnimating#> && [navigationController.viewControllers count] > 1;
    }
    return YES;
}
6
iwill

Je cache également le bouton de retour en le remplaçant par un leftBarItem personnalisé. 
Supprimer le délégué interactivePopGestureRecognizer après que l'action Push a fonctionné pour moi:

[self.navigationController pushViewController:vcToPush animated:YES];

// Enabling iOS 7 screen-Edge-pan-gesture for pop action
if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
    self.navigationController.interactivePopGestureRecognizer.delegate = nil;
}
6
avishic

Essayez self.navigationController.interactivePopGestureRecognizer.enabled = YES;

3
ilya n.

RootView

override func viewDidAppear(_ animated: Bool) {
    self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false
}

ChildView

override func viewDidLoad() {
    self.navigationController?.interactivePopGestureRecognizer?.isEnabled = true
    self.navigationController?.interactivePopGestureRecognizer?.delegate = self
} 

extension ChildViewController: UIGestureRecognizerDelegate {}
1
PW486

Utilisez cette logique pour continuer d'activer ou de désactiver le geste de balayage.

- (void)navigationController:(UINavigationController *)navigationController
       didShowViewController:(UIViewController *)viewController
                    animated:(BOOL)animate
{
    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)])
    {
        if (self.navigationController.viewControllers.count > 1)
        {
            self.navigationController.interactivePopGestureRecognizer.enabled = YES;
        }
        else
        {
            self.navigationController.interactivePopGestureRecognizer.enabled = NO;
        }
    }
}
1

Je n'ai pas écrit cela, mais le blog suivant a beaucoup aidé et a résolu mes problèmes avec le bouton de navigation personnalisé:

http://keighl.com/post/ios7-interactive-pop-gesture-custom-back-button/

En résumé, il implémente un UINavigationController personnalisé qui utilise le délégué Geste Pop. Très propre et portable!

Code:

@interface CBNavigationController : UINavigationController <UINavigationControllerDelegate, UIGestureRecognizerDelegate>
@end

@implementation CBNavigationController

- (void)viewDidLoad
{
  __weak CBNavigationController *weakSelf = self;

  if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)])
  {
    self.interactivePopGestureRecognizer.delegate = weakSelf;
    self.delegate = weakSelf;
  }
}

// Hijack the Push method to disable the gesture

- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
  if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)])
    self.interactivePopGestureRecognizer.enabled = NO;

  [super pushViewController:viewController animated:animated];
}

#pragma mark UINavigationControllerDelegate

- (void)navigationController:(UINavigationController *)navigationController
       didShowViewController:(UIViewController *)viewController
                    animated:(BOOL)animate
{
  // Enable the gesture again once the new controller is shown

  if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)])
    self.interactivePopGestureRecognizer.enabled = YES;
}

Modifier. Ajout d'un correctif pour les problèmes lorsqu'un utilisateur tente de glisser à gauche sur un contrôleur de vue racine:

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {

    if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)] &&
        self.topViewController == [self.viewControllers firstObject] &&
        gestureRecognizer == self.interactivePopGestureRecognizer) {

        return NO;
    }

    return YES;
}
1
kgaidis

J'avais un problème similaire dans lequel j'affectais le contrôleur de vue actuel en tant que délégué du geste pop interactif, mais interrompais le geste sur les vues poussées ou sous la vue dans la pile de navigation. La façon dont j'ai résolu ce problème était de définir le délégué dans -viewDidAppear, puis de le définir sur nil dans -viewWillDisappear. Cela a permis à mes autres vues de fonctionner correctement.

0
Bill Burgess

Imaginez que nous utilisions le modèle de projet maître/détail par défaut d'Apple, où maître est un contrôleur de vue de tableau et sur lequel vous appuyez pour afficher le contrôleur de vue de détail.

Nous souhaitons personnaliser le bouton Précédent qui apparaît dans le contrôleur de vue de détail. Voici comment personnaliser le image , couleur de l'image , texte , couleur du texte et police du bouton de retour.


Pour modifier l’image, la couleur de l’image, la couleur du texte ou la police de manière globale, placez les éléments suivants dans un emplacement appelé avant la création de tout contrôleur de vue (par exemple, application:didFinishLaunchingWithOptions: est un bon emplacement).

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    UINavigationBar* navigationBarAppearance = [UINavigationBar appearance];

    // change the back button, using default tint color
    navigationBarAppearance.backIndicatorImage = [UIImage imageNamed:@"back"];
    navigationBarAppearance.backIndicatorTransitionMaskImage = [UIImage imageNamed:@"back"];

    // change the back button, using the color inside the original image
    navigationBarAppearance.backIndicatorImage = [[UIImage imageNamed:@"back"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
    navigationBarAppearance.backIndicatorTransitionMaskImage = [UIImage imageNamed:@"back"];

    // change the tint color of everything in a navigation bar
    navigationBarAppearance.tintColor = [UIColor greenColor];

    // change the font in all toolbar buttons
    NSDictionary *barButtonTitleTextAttributes =
    @{
      NSFontAttributeName: [UIFont fontWithName:@"HelveticaNeue-Light" size:12.0],
      NSForegroundColorAttributeName: [UIColor purpleColor]
      };

    [[UIBarButtonItem appearance] setTitleTextAttributes:barButtonTitleTextAttributes forState:UIControlStateNormal];

    return YES;
}

Notez que vous pouvez utiliser appearanceWhenContainedIn: pour contrôler davantage les contrôleurs de vue affectés par ces modifications, mais gardez à l'esprit que vous ne pouvez pas transmettre [DetailViewController class], car il est contenu dans un UINavigationController, pas votre DetailViewController. Cela signifie que vous devrez sous-classer UINavigationController si vous souhaitez davantage de contrôle sur ce qui est affecté.

Pour personnaliser le texte ou la police/couleur d'un élément de bouton Précédent spécifique, vous devez le faire dans le MasterViewController (pas le DetailViewController!). Cela ne semble pas intuitif car le bouton apparaît sur le DetailViewController. Cependant, une fois que vous comprenez que la façon de le personnaliser consiste à définir une propriété sur un élément de navigation, cela commence à avoir plus de sens.

- (void)viewDidLoad { // MASTER view controller
    [super viewDidLoad];

    UIBarButtonItem *buttonItem = [[UIBarButtonItem alloc] initWithTitle:@"Testing"
                                                                   style:UIBarButtonItemStylePlain
                                                                  target:nil
                                                                  action:nil];
    NSDictionary *barButtonTitleTextAttributes =
    @{
      NSFontAttributeName: [UIFont fontWithName:@"HelveticaNeue-Light" size:12.0],
      NSForegroundColorAttributeName: [UIColor purpleColor]
      };
    [buttonItem setTitleTextAttributes:barButtonTitleTextAttributes forState:UIControlStateNormal];
    self.navigationItem.backBarButtonItem = buttonItem;
}

Remarque: si vous tentez de définir titleTextAttributes après avoir défini self.navigationItem.backBarButtonItem ne semble pas fonctionner, vous devez donc le définir avant d'affecter la valeur à cette propriété.

0
Senseful

Créez une classe 'TTNavigationViewController' qui est une sous-classe de 'UINavigationController' et créez votre contrôleur de navigation existant de cette classe soit dans storyboard/class, Exemple de code dans la classe - 

    class TTNavigationViewController: UINavigationController, UIGestureRecognizerDelegate {

override func viewDidLoad() {
    super.viewDidLoad()
    self.setNavigationBarHidden(true, animated: false)

    // enable slide-back
    if self.responds(to: #selector(getter: UINavigationController.interactivePopGestureRecognizer)) {
        self.interactivePopGestureRecognizer?.isEnabled = true
        self.interactivePopGestureRecognizer?.delegate  = self
    }
}

func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
    return true
}}
0
Prakash Raj