it-swarm.com.de

Kein Rückstreichen beim Ausblenden der Navigationsleiste in UINavigationController

Ich liebe das Swipe-Pack, das durch das Einbetten Ihrer Ansichten in einen UINavigationController übernommen wurde. Leider kann ich anscheinend keine Möglichkeit finden, die Navigationsleiste auszublenden, habe aber immer noch die Touch Pan-Wischbewegung. Ich kann benutzerdefinierte Gesten schreiben, aber ich ziehe es vor, mich nicht auf die UINavigationController-Rückwischgeste zu verlassen.

wenn ich das Kontrollkästchen im Storyboard deaktivieren, funktioniert der Back-Swipe nicht

enter image description here

alternativ, wenn ich es programmgesteuert verstecke, dasselbe Szenario.

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.navigationController setNavigationBarHidden:YES animated:NO]; // and animated:YES
}

Gibt es keine Möglichkeit, die obere Navigationsleiste auszublenden und trotzdem zu wischen?

65
mihai

Ein funktionierender Hack besteht darin, den interactivePopGestureRecognizer-Delegierten der UINavigationController auf nil so zu setzen:

[self.navigationController.interactivePopGestureRecognizer setDelegate:nil];

In manchen Situationen kann es jedoch zu seltsamen Effekten kommen.

87
HorseT

In meinem Fall, um seltsame Effekte zu verhindern

Root View Controller

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-ohne-navigation-bar/

48
saranpol

Probleme mit anderen Methoden

Die Einstellung von interactivePopGestureRecognizer.delegate = nil hat unbeabsichtigte Nebenwirkungen.

Die Einstellung von navigationController?.navigationBar.hidden = true funktioniert, erlaubt jedoch nicht, dass Ihre Änderung in der Navigationsleiste ausgeblendet wird.

Schließlich ist es in der Regel besser, ein Modellobjekt zu erstellen, das die UIGestureRecognizerDelegate für Ihren Navigationscontroller ist. Die Einstellung auf einen Controller im UINavigationController-Stack verursacht die EXC_BAD_ACCESS-Fehler.

Komplettlösung

Fügen Sie diese Klasse zunächst Ihrem Projekt hinzu:

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
    }
}

Legen Sie dann interactivePopGestureRecognizer.delegate Ihres Navigationscontrollers auf eine Instanz Ihrer neuen InteractivePopRecognizer-Klasse fest.

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
}

Genießen Sie eine verborgene Navigationsleiste ohne Nebeneffekte, die auch dann funktioniert, wenn Ihr oberster Controller über Tabellen-, Auflistungs- oder Scroll-Ansicht verfügt.

Sie können UINavigationController wie folgt subclassieren:

@interface CustomNavigationController : UINavigationController<UIGestureRecognizerDelegate>

@end

Implementierung:

@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
16

(Aktualisiert) Swift 4.2

Ich habe festgestellt, dass andere veröffentlichte Lösungen, die den Delegierten überschreiben oder auf null setzen, unerwartetes Verhalten verursacht haben. 

Wenn ich mich oben auf dem Navigationsstapel befand und versuchte, die Geste zu verwenden, um eine weitere zu platzieren, schlägt dies fehl (wie erwartet), aber nachfolgende Versuche, auf den Stapel zu pushen, fangen an, merkwürdige grafische Störungen in der Navigationsleiste. Dies ist sinnvoll, da der Delegat nicht nur dazu dient, zu handhaben, als nur zu blockieren, ob die Geste erkannt wird oder nicht, wenn die Navigationsleiste ausgeblendet ist und dass das gesamte andere Verhalten verworfen wurde.

Bei meinen Tests scheint es, dass gestureRecognizer(_:, shouldReceiveTouch:) die Methode ist, die der ursprüngliche Delegat implementiert, um zu verhindern, dass die Geste erkannt wird, wenn die Navigationsleiste ausgeblendet ist, nicht gestureRecognizerShouldBegin(_:). Andere Lösungen, die gestureRecognizerShouldBegin(_:) in ihren Delegierten implementieren, arbeiten, da das Fehlen einer Implementierung von gestureRecognizer(_:, shouldReceiveTouch:) das Standardverhalten des Empfangs aller Berührungen verursacht.

Die Lösung von @Nathan Perry kommt näher, aber ohne eine Implementierung von respondsToSelector(_:) glaubt der UIKit-Code, der Nachrichten an den Delegierten sendet, dass es keine Implementierung für eine der anderen Delegat-Methoden gibt, und forwardingTargetForSelector(_:) wird niemals aufgerufen.

Wir übernehmen also die Kontrolle über `gestureRecognizer (_ :, shouldReceiveTouch :)). In dem einen bestimmten Szenario möchten wir das Verhalten ändern und ansonsten alles andere an den Delegaten weiterleiten.

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
        }
    }
}
10
Chris Vasselli

Aufbauend auf Hunter Maximillion Monks Antwort habe ich eine Unterklasse für UINavigationController erstellt und die benutzerdefinierte Klasse für meinen UINavigationController in meinem Storyboard festgelegt. Der letzte Code für die beiden Klassen sieht folgendermaßen aus:

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:

Storyboard nav controller custom class

8
tylermilner

Sieht aus, als wäre die Lösung von @ChrisVasseli die beste. Ich möchte dieselbe Lösung in Objective-C anbieten, da es sich bei der Frage um Objective-C handelt (siehe Tags).

@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
6

Einfache, nebenwirkungsfreie Antwort

Während die meisten Antworten hier gut sind, scheinen sie unbeabsichtigte Nebenwirkungen zu haben (App-Breaking) oder sind wortreich.

Die einfachste und dennoch funktionellste Lösung, die ich finden konnte, war die folgende:

In dem ViewController, in dem Sie die Navigationsleiste verstecken,

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
    }
}

Andere Antworten haben lediglich vorgeschlagen, den Delegierten auf Null zu setzen. Wenn Sie auf dem Navigationsstapel zurück zum ursprünglichen Ansichts-Controller wischen, werden alle Gesten deaktiviert. Vielleicht eine Art Versehen der UIKit/UIGesture-Entwickler.

Außerdem haben einige Antworten, die ich hier implementiert habe, zu einem nicht standardmäßigen Apple -Navigationsverhalten geführt (insbesondere ermöglicht sie das Scrollen nach oben oder unten, während ich gleichzeitig rückwärts wische). Diese Antworten wirken ebenfalls etwas ausführlich und teilweise unvollständig.

3
CodyB

Meine Lösung ist, die UINavigationController-Klasse direkt zu erweitern:

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
    }

}

Auf diese Weise werden alle Navigationssteuerungen durch Schieben verworfen.

3
fredericdnd

Sie können dies mit einem Stellvertreter tun. Wenn Sie den Navigationscontroller erstellen, greifen Sie nach dem vorhandenen Delegaten. Und gib es in den Stellvertreter. Übergeben Sie dann alle Delegatmethoden an den vorhandenen Delegierten, außer gestureRecognizer:shouldReceiveTouch: mit forwardingTargetForSelector:.

Konfiguration:

let vc = UIViewController(nibName: nil, bundle: nil)
let navVC = UINavigationController(rootViewController: vc)
let bridgingDelegate = ProxyDelegate()
bridgingDelegate.existingDelegate = navVC.interactivePopGestureRecognizer?.delegate
navVC.interactivePopGestureRecognizer?.delegate = bridgingDelegate

Vertretungsbeauftragter:

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
    }  
}
3
Nathan Perry

Xamarin Antwort:

Implementieren Sie die IUIGestureRecognizerDelegate-Schnittstelle in der Klassendefinition Ihres ViewControllers:

public partial class myViewController : UIViewController, IUIGestureRecognizerDelegate

Fügen Sie in Ihrem ViewController die folgende Methode hinzu:

[Export("gestureRecognizerShouldBegin:")]
public bool ShouldBegin(UIGestureRecognizer recognizer) {
  if (recognizer is UIScreenEdgePanGestureRecognizer && 
      NavigationController.ViewControllers.Length == 1) {
    return false;
  }
  return true;
}

Fügen Sie in Ihrem ViewController ViewDidLoad() die folgende Zeile hinzu:

NavigationController.InteractivePopGestureRecognizer.Delegate = this;
1
Ahmad

Ich habe es ausprobiert und es funktioniert perfekt: Wie blende ich die Navigationsleiste aus, ohne die Slide-Back-Fähigkeit zu verlieren

Die Idee ist, "UIGestureRecognizerDelegate" in Ihre .h Zu implementieren und dies Ihrer .m-Datei hinzuzufügen.

- (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;  
}
1
KarimIhab

Es gibt eine wirklich einfache Lösung, die ich ausprobiert habe und perfekt funktioniert. Diese ist in Xamarin.iOS enthalten, kann aber auch auf native angewendet werden:

    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);
    }
0
João Palma

Hier ist meine Lösung: Ich ändere das Alpha in der Navigationsleiste, aber die Navigationsleiste wird nicht ausgeblendet. Alle meine View-Controller sind eine Unterklasse meines BaseViewControllers, und dort habe ich:

    override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    navigationController?.navigationBar.alpha = 0.0
}

Sie können UINavigationController auch in Unterklassen unterteilen und diese Methode dort ablegen.

0

Einige Leute hatten Erfolg, indem sie stattdessen die setNavigationBarHidden-Methode mit animierter YES aufrufen. 

0
Mundi

In meinem View Controller ohne Navigationsleiste verwende ich 

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()
}

Während der interaktiven Entlassung leuchtet der Back-Button jedoch durch, weshalb ich ihn versteckt habe.

0
fruitcoder