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.
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.
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.
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
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];
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 {}
navigationController.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)self;
C'est de http://stuartkhall.com/posts/ios-7-development-tips-tricks-hacks , mais cela cause plusieurs bugs:
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,
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;
}
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;
}
Essayez self.navigationController.
interactivePopGestureRecognizer
.enabled = YES;
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 {}
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;
}
}
}
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;
}
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.
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é.
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
}}