No Swipe Back при скрытии панели навигации в UINavigationController

Мне очень нравится набор свайпов, унаследованный от встраивания ваших взглядов в UINavigationController. К сожалению, я не могу найти способ спрятать NavigationBar, но все еще имею сенсорное панорамирование назад gesture. Я могу писать собственные жесты, но я предпочитаю этого не делать, а вместо этого полагаюсь на UINavigationController назад gesture.

если я уберу галочку в раскадровке, обратный удар не будет работать

enter image description here

в качестве альтернативы, если я программно скрываю это, тот же сценарий.

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

Нет ли способа спрятать верхнюю часть NavigationBar и все еще иметь удар?

Ответ 1

Обработчик, который работает, должен установить делегат interactivePopGestureRecognizer от UINavigationController до nil следующим образом:

[self.navigationController.interactivePopGestureRecognizer setDelegate:nil];

Но в некоторых ситуациях это может создать странные эффекты.

Ответ 2

Проблемы с другими методами

Установка interactivePopGestureRecognizer.delegate = nil имеет непреднамеренные побочные эффекты.

Настройка navigationController?.navigationBar.hidden = true работает, но не позволяет скрывать изменение панели навигации.

Наконец, лучше всего создать модельный объект, который является UIGestureRecognizerDelegate для вашего навигационного контроллера. Установка его на контроллер в стеке UINavigationController вызывает ошибки EXC_BAD_ACCESS.

Полное решение

Сначала добавьте этот класс в свой проект:

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

Затем установите ваш контроллер навигации interactivePopGestureRecognizer.delegate в экземпляр вашего нового класса 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
}

Наслаждайтесь скрытой навигационной панелью без каких-либо побочных эффектов, которая работает, даже если ваш верхний контроллер имеет представление в виде таблицы, коллекции или прокрутки.

Ответ 3

В моем случае, чтобы предотвратить странные эффекты

Контроллер корневого представления

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/

Ответ 4

Вы можете подклассифицировать UINavigationController следующим образом:

@interface CustomNavigationController : UINavigationController<UIGestureRecognizerDelegate>

@end

Реализация:

@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

Ответ 5

(Обновлено) Swift 4.2

Я обнаружил, что другие опубликованные решения, переопределяющие делегата или устанавливающие его на ноль, вызвали неожиданное поведение.

В моем случае, когда я был на вершине стека навигации и пытался использовать жест, чтобы вытолкнуть еще один, он потерпел бы неудачу (как и ожидалось), но последующие попытки протолкнуть его в стек начнут вызывать странные графические сбои в Панель навигации. Это имеет смысл, поскольку делегат используется не только для того, чтобы блокировать распознавание жеста, когда панель навигации скрыта, а также для того, чтобы выбрасывать все это другое поведение.

Из моего тестирования gestureRecognizer(_:, shouldReceiveTouch:) что gestureRecognizer(_:, shouldReceiveTouch:) - это метод, который реализует исходный делегат, чтобы заблокировать распознавание жеста, когда панель навигации скрыта, а не gestureRecognizerShouldBegin(_:). Другие решения, которые реализуют gestureRecognizerShouldBegin(_:) в их gestureRecognizerShouldBegin(_:) работают, потому что отсутствие реализации gestureRecognizer(_:, shouldReceiveTouch:) вызовет поведение по умолчанию получения всех касаний.

Решение @Nathan Perry приближается, но без реализации responsedsToSelector respondsToSelector(_:) код UIKit, который отправляет сообщения делегату, будет считать, что не существует реализации ни для одного из других методов делегата, и forwardingTargetForSelector(_:) никогда не получит называется.

Итак, мы берем контроль над 'gestRecognizer (_ :, shouldReceiveTouch :) в одном конкретном сценарии, в котором мы хотим изменить поведение, а в остальном перенаправляем все остальное делегату.

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

Ответ 6

Создание ответа Хантер Максимиллион Монк, я сделал подкласс для UINavigationController, а затем установил пользовательский класс для моего UINavigationController в своем раскадровке. Окончательный код для двух классов выглядит следующим образом:

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 nav controller custom class

Ответ 7

Похоже, что решение, предоставленное @ChrisVasseli, является лучшим. Я хотел бы предоставить такое же решение в Objective-C, потому что вопрос о Objective-C (см. Теги)

@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

Ответ 8

Мое решение состоит в том, чтобы напрямую расширить класс 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
    }

}

Таким образом, все навигационные контроллеры будут сбрасываться с помощью скольжения.

Ответ 9

Вы можете сделать это с помощью прокси-делегата. Когда вы строите контроллер навигации, возьмите существующий делегат. И передайте его в прокси. Затем передайте все методы делегата существующему делегату, кроме gestureRecognizer:shouldReceiveTouch:, используя forwardingTargetForSelector:

Настройка:

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

Прокси-делегат:

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

Ответ 10

Простой, без побочных эффектов Ответ

Хотя большинство ответов здесь хорошие, они, похоже, имеют непреднамеренные побочные эффекты (ломка приложения) или многословны.

Самым простым, но функциональным решением, которое я смог придумать, было следующее:

В ViewController, который вы скрываете панель навигации,

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

Другие ответы предлагают просто установить делегата на ноль. Проведение назад к начальному контроллеру представления в стеке навигации приводит к отключению всех жестов. Возможно, это своего рода недосмотр разработчиков UIKit/UIGesture.

Кроме того, некоторые ответы, которые я здесь реализовал, привели к нестандартному поведению Apple (в частности, к возможности прокрутки вверх или вниз при одновременном перемещении назад). Эти ответы также кажутся немного многословными, а в некоторых случаях неполными.

Ответ 11

Ответ на Xamarin:

Внедрите интерфейс IUIGestureRecognizerDelegate в определение класса ViewController:

public partial class myViewController : UIViewController, IUIGestureRecognizerDelegate

В вашем ViewController добавьте следующий метод:

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

В вашем ViewController ViewDidLoad() добавьте следующую строку:

NavigationController.InteractivePopGestureRecognizer.Delegate = this;

Ответ 12

Я пробовал это, и он отлично работает: Как скрыть навигационную панель, не теряя при этом слайд-возможности

Идея заключается в реализации "UIGestureRecognizerDelegate" в вашем .h и добавьте это в ваш .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;  
}

Ответ 13

Вот мое решение: я изменяю альфа на панели навигации, но панель навигации не скрыта. Все мои контроллеры представления являются подклассом моего BaseViewController, и там у меня есть:

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

Вы также можете создать подкласс UINavigationController и поместить этот метод туда.

Ответ 14

Некоторые люди добились успеха, вызвав метод setNavigationBarHidden с анимированным YES.

Ответ 15

На мой взгляд, контроллер без навигационной панели я использую

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

Во время интерактивного увольнения будет прокручиваться кнопка "Назад", поэтому я спрятал ее.

Ответ 16

Есть очень простое решение, которое я попробовал и которое отлично работает, оно есть в Xamarin.iOS, но может быть применено и к нативному:

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

Ответ 17

Вот как отключить распознаватель жестов, когда пользователь выходит из ViewController. Вы можете вставить его в свой viewWillAppear() или в методы ViewDidLoad().

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