J'aime le pack de balayage qui a hérité de l'intégration de vos vues dans un UINavigationController. Malheureusement, je n'arrive pas à trouver un moyen de masquer la barre de navigation, mais j'ai toujours le toucher tactile. Je peux écrire des gestes personnalisés, mais je préfère ne pas le faire et me fier plutôt au geste de balayage arrière UINavigationController.
si je décoche le scénario, le glissement de dos ne fonctionne pas
sinon, si je le cache par programme, le même scénario.
- (void)viewDidLoad
{
[super viewDidLoad];
[self.navigationController setNavigationBarHidden:YES animated:NO]; // and animated:YES
}
N'y a-t-il pas moyen de cacher la barre de navigation supérieure tout en ayant le balayage?
Un hack qui fonctionne consiste à définir le délégué interactivePopGestureRecognizer
de UINavigationController
sur nil
comme ceci:
[self.navigationController.interactivePopGestureRecognizer setDelegate:nil];
Mais dans certaines situations, cela pourrait créer des effets étranges.
Dans mon cas, pour éviter des effets étranges
Contrôleur de vue racine
override func viewDidLoad() {
super.viewDidLoad()
// Enable swipe back when no navigation bar
navigationController?.interactivePopGestureRecognizer?.delegate = self
}
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if(navigationController!.viewControllers.count > 1){
return true
}
return false
}
http://www.gampood.com/pop-viewcontroller-with-out-navigation-bar/
La définition du interactivePopGestureRecognizer.delegate = nil
a des effets secondaires inattendus.
La définition de navigationController?.navigationBar.hidden = true
fonctionne, mais ne permet pas de masquer vos modifications dans la barre de navigation.
Enfin, il est généralement préférable de créer un objet de modèle qui correspond à UIGestureRecognizerDelegate
pour votre contrôleur de navigation. Le paramétrer sur un contrôleur de la pile UINavigationController
est à l'origine des erreurs EXC_BAD_ACCESS
.
Tout d’abord, ajoutez cette classe à votre projet:
class InteractivePopRecognizer: NSObject, UIGestureRecognizerDelegate {
var navigationController: UINavigationController
init(controller: UINavigationController) {
self.navigationController = controller
}
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return navigationController.viewControllers.count > 1
}
// This is necessary because without it, subviews of your top controller can
// cancel out your gesture recognizer on the Edge.
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
Ensuite, définissez le interactivePopGestureRecognizer.delegate
de votre contrôleur de navigation sur une instance de votre nouvelle classe InteractivePopRecognizer
.
var popRecognizer: InteractivePopRecognizer?
override func viewDidLoad() {
super.viewDidLoad()
setInteractiveRecognizer()
}
private func setInteractiveRecognizer() {
guard let controller = navigationController else { return }
popRecognizer = InteractivePopRecognizer(controller: controller)
controller.interactivePopGestureRecognizer?.delegate = popRecognizer
}
Profitez d'une barre de navigation cachée sans effets secondaires, qui fonctionne même si votre contrôleur principal a des sous-vues de table, de collection ou de défilement.
Vous pouvez sous-classer UINavigationController comme suit:
@interface CustomNavigationController : UINavigationController<UIGestureRecognizerDelegate>
@end
La mise en oeuvre:
@implementation CustomNavigationController
- (void)setNavigationBarHidden:(BOOL)hidden animated:(BOOL)animated {
[super setNavigationBarHidden:hidden animated:animated];
self.interactivePopGestureRecognizer.delegate = self;
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
if (self.viewControllers.count > 1) {
return YES;
}
return NO;
}
@end
J'ai trouvé que d'autres solutions publiées remplaçant le délégué ou le fixant à zéro provoquaient un comportement inattendu.
Dans mon cas, lorsque je me trouvais au-dessus de la pile de navigation et que j'essayais d’en faire un geste supplémentaire, la tentative échouait (comme prévu), mais les tentatives ultérieures d’application de Pousser sur la pile commençaient à causer d’étranges problèmes graphiques dans la barre de navigation. Cela a du sens, car le délégué est utilisé pour gérer plus que simplement pour empêcher le geste d'être reconnu lorsque la barre de navigation est masquée et que tout ce comportement a été jeté.
D'après mes tests, il apparaît que gestureRecognizer(_:, shouldReceiveTouch:)
est la méthode mise en œuvre par le délégué d'origine pour empêcher la reconnaissance du geste lorsque la barre de navigation est masquée, et non pas gestureRecognizerShouldBegin(_:)
. D'autres solutions qui implémentent gestureRecognizerShouldBegin(_:)
dans leur travail de délégué car l'absence d'une implémentation de gestureRecognizer(_:, shouldReceiveTouch:)
entraînera le comportement par défaut consistant à recevoir toutes les touches.
La solution de @Nathan Perry se rapproche, mais sans implémentation de respondsToSelector(_:)
, le code UIKit qui envoie des messages au délégué croira qu'il n'y a pas d'implémentation pour aucune des autres méthodes de délégué et que forwardingTargetForSelector(_:)
ne sera jamais appelé.
Nous prenons donc le contrôle de `gestureRecognizer (_ :, shouldReceiveTouch :) dans le scénario spécifique que nous voulons modifier le comportement et sinon transmettons tout le reste au délégué.
import Foundation
class AlwaysPoppableNavigationController: UINavigationController {
private let alwaysPoppableDelegate = AlwaysPoppableDelegate()
override func viewDidLoad() {
super.viewDidLoad()
alwaysPoppableDelegate.originalDelegate = interactivePopGestureRecognizer?.delegate
alwaysPoppableDelegate.navigationController = self
interactivePopGestureRecognizer?.delegate = alwaysPoppableDelegate
}
}
final class AlwaysPoppableDelegate: NSObject, UIGestureRecognizerDelegate {
weak var navigationController: UINavigationController?
weak var originalDelegate: UIGestureRecognizerDelegate?
override func responds(to aSelector: Selector!) -> Bool {
if aSelector == #selector(gestureRecognizer(_:shouldReceive:)) {
return true
} else if let responds = originalDelegate?.responds(to: aSelector) {
return responds
} else {
return false
}
}
override func forwardingTarget(for aSelector: Selector!) -> Any? {
return originalDelegate
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if let nav = navigationController, nav.isNavigationBarHidden, nav.viewControllers.count > 1 {
return true
} else if let result = originalDelegate?.gestureRecognizer?(gestureRecognizer, shouldReceive: touch) {
return result
} else {
return false
}
}
}
À partir de Réponse de Hunter Maximillion Monk , j'ai créé une sous-classe pour UINavigationController, puis défini la classe personnalisée de mon UINavigationController dans mon story-board. Le code final pour les deux classes ressemble à ceci:
InteractivePopRecognizer:
class InteractivePopRecognizer: NSObject {
// MARK: - Properties
fileprivate weak var navigationController: UINavigationController?
// MARK: - Init
init(controller: UINavigationController) {
self.navigationController = controller
super.init()
self.navigationController?.interactivePopGestureRecognizer?.delegate = self
}
}
extension InteractivePopRecognizer: UIGestureRecognizerDelegate {
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return (navigationController?.viewControllers.count ?? 0) > 1
}
// This is necessary because without it, subviews of your top controller can cancel out your gesture recognizer on the Edge.
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
HiddenNavBarNavigationController:
class HiddenNavBarNavigationController: UINavigationController {
// MARK: - Properties
private var popRecognizer: InteractivePopRecognizer?
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
setupPopRecognizer()
}
// MARK: - Setup
private func setupPopRecognizer() {
popRecognizer = InteractivePopRecognizer(controller: self)
}
}
Storyboard:
On dirait que la solution fournie par @ChrisVasseli est la meilleure. J'aimerais proposer la même solution en Objective-C car la question concerne Objective-C (voir les balises)
@interface InteractivePopGestureDelegate : NSObject <UIGestureRecognizerDelegate>
@property (nonatomic, weak) UINavigationController *navigationController;
@property (nonatomic, weak) id<UIGestureRecognizerDelegate> originalDelegate;
@end
@implementation InteractivePopGestureDelegate
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
if (self.navigationController.navigationBarHidden && self.navigationController.viewControllers.count > 1) {
return YES;
} else {
return [self.originalDelegate gestureRecognizer:gestureRecognizer shouldReceiveTouch:touch];
}
}
- (BOOL)respondsToSelector:(SEL)aSelector
{
if (aSelector == @selector(gestureRecognizer:shouldReceiveTouch:)) {
return YES;
} else {
return [self.originalDelegate respondsToSelector:aSelector];
}
}
- (id)forwardingTargetForSelector:(SEL)aSelector
{
return self.originalDelegate;
}
@end
@interface NavigationController ()
@property (nonatomic) InteractivePopGestureDelegate *interactivePopGestureDelegate;
@end
@implementation NavigationController
- (void)viewDidLoad
{
[super viewDidLoad];
self.interactivePopGestureDelegate = [InteractivePopGestureDelegate new];
self.interactivePopGestureDelegate.navigationController = self;
self.interactivePopGestureDelegate.originalDelegate = self.interactivePopGestureRecognizer.delegate;
self.interactivePopGestureRecognizer.delegate = self.interactivePopGestureDelegate;
}
@end
Bien que la plupart des réponses données ici soient bonnes, elles ont apparemment des effets secondaires non souhaités (coupure d'application) ou sont prolixes.
La solution la plus simple et fonctionnelle à laquelle je pouvais trouver était la suivante:
Dans le ViewController que vous cachez la barre de navigation,
class MyNoNavBarViewController: UIViewController {
// needed for reference when leaving this view controller
var initialInteractivePopGestureRecognizerDelegate: UIGestureRecognizerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
// we will need a reference to the initial delegate so that when we Push or pop..
// ..this view controller we can appropriately assign back the original delegate
initialInteractivePopGestureRecognizerDelegate = self.navigationController?.interactivePopGestureRecognizer?.delegate
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
// we must set the delegate to nil whether we are popping or pushing to..
// ..this view controller, thus we set it in viewWillAppear()
self.navigationController?.interactivePopGestureRecognizer?.delegate = nil
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(true)
// and every time we leave this view controller we must set the delegate back..
// ..to what it was originally
self.navigationController?.interactivePopGestureRecognizer?.delegate = initialInteractivePopGestureRecognizerDelegate
}
}
D'autres réponses suggèrent simplement de mettre le délégué à zéro. Si vous faites défiler l'écran vers le contrôleur de vue initial sur la pile de navigation, tous les gestes sont désactivés. Une sorte de surveillance, peut-être, des développeurs de UIKit/UIGesture.
De plus, certaines des réponses que j'ai implémentées ont abouti à un comportement de navigation non standard Apple (permettant en particulier de faire défiler vers le haut ou vers le bas tout en effectuant un balayage arrière). Ces réponses semblent également un peu verbeuses et, dans certains cas, incomplètes.
Ma solution consiste à étendre directement la classe UINavigationController
:
import UIKit
extension UINavigationController: UIGestureRecognizerDelegate {
override open func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.interactivePopGestureRecognizer?.delegate = self
}
public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return self.viewControllers.count > 1
}
}
De cette façon, tous les contrôleurs de navigation seront déplaçables en glissant.
Vous pouvez le faire avec un délégué par procuration. Lorsque vous construisez le contrôleur de navigation, prenez le délégué existant. Et passez-le dans le proxy. Puis passez toutes les méthodes de délégué au délégué existant, à l'exception de gestureRecognizer:shouldReceiveTouch:
à l'aide de forwardingTargetForSelector:
Installer:
let vc = UIViewController(nibName: nil, bundle: nil)
let navVC = UINavigationController(rootViewController: vc)
let bridgingDelegate = ProxyDelegate()
bridgingDelegate.existingDelegate = navVC.interactivePopGestureRecognizer?.delegate
navVC.interactivePopGestureRecognizer?.delegate = bridgingDelegate
Délégué par procuration:
class ProxyDelegate: NSObject, UIGestureRecognizerDelegate {
var existingDelegate: UIGestureRecognizerDelegate? = nil
override func forwardingTargetForSelector(aSelector: Selector) -> AnyObject? {
return existingDelegate
}
func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
return true
}
}
Implémentez l'interface IUIGestureRecognizerDelegate
dans la définition de classe de votre ViewController:
public partial class myViewController : UIViewController, IUIGestureRecognizerDelegate
Dans votre ViewController, ajoutez la méthode suivante:
[Export("gestureRecognizerShouldBegin:")]
public bool ShouldBegin(UIGestureRecognizer recognizer) {
if (recognizer is UIScreenEdgePanGestureRecognizer &&
NavigationController.ViewControllers.Length == 1) {
return false;
}
return true;
}
Dans ViewDidLoad()
de votre ViewController, ajoutez la ligne suivante:
NavigationController.InteractivePopGestureRecognizer.Delegate = this;
J'ai essayé cela et cela fonctionne parfaitement: Comment masquer la barre de navigation sans perdre la capacité de glisser en arrière
L'idée est d'implémenter "UIGestureRecognizerDelegate" dans votre fichier .h .__ et de l'ajouter à votre fichier .m.
- (void)viewWillAppear:(BOOL)animated {
// hide nav bar
[[self navigationController] setNavigationBarHidden:YES animated:YES];
// enable slide-back
if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
self.navigationController.interactivePopGestureRecognizer.enabled = YES;
self.navigationController.interactivePopGestureRecognizer.delegate = self;
}
}
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
return YES;
}
Il y a une solution très simple que j'ai essayée et qui fonctionne parfaitement, c'est dans Xamarin.iOS mais peut être appliquée aussi en natif
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
this.NavigationController.SetNavigationBarHidden(true, true);
}
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
this.NavigationController.SetNavigationBarHidden(false, false);
this.NavigationController.NavigationBar.Hidden = true;
}
public override void ViewWillDisappear(bool animated)
{
base.ViewWillDisappear(animated);
this.NavigationController.SetNavigationBarHidden(true, false);
}
Voici ma solution: je change alpha dans la barre de navigation, mais la barre de navigation n'est pas masquée. Tous mes contrôleurs de vue sont une sous-classe de mon BaseViewController, et voici:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
navigationController?.navigationBar.alpha = 0.0
}
Vous pouvez également sous-classer UINavigationController et y mettre cette méthode.
Certaines personnes ont eu du succès en appelant plutôt la méthode setNavigationBarHidden
avec YES
animée.
Dans mon contrôleur de vue sans barre de navigation, j'utilise
open override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
CATransaction.begin()
UIView.animate(withDuration: 0.25, animations: { [weak self] in
self?.navigationController?.navigationBar.alpha = 0.01
})
CATransaction.commit()
}
open override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
CATransaction.begin()
UIView.animate(withDuration: 0.25, animations: { [weak self] in
self?.navigationController?.navigationBar.alpha = 1.0
})
CATransaction.commit()
}
Pendant le renvoi interactif, le bouton de retour apparaîtra bien, c'est pourquoi je l'ai caché.