Нет проведите пальцем назад при скрытии панели навигации в UINavigationController


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

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

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

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

нет ли способа скрыть верхнюю панель навигации и все еще иметь салфетки?

15 63

15 ответов:

хак, который работает, чтобы установить interactivePopGestureRecognizer'ы делегат UINavigationController до nil такой:

[self.navigationController.interactivePopGestureRecognizer setDelegate:nil];

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

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

корневой контроллер вида

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/

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

задание 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
}

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

Вы можете подкласс 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

строительство ответ охотника Максимилиана Монка, Я сделал подкласс для 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

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

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

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

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

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

import Foundation

class AlwaysPoppableNavigationController : UINavigationController {

    private var alwaysPoppableDelegate: AlwaysPoppableDelegate!

    override func viewDidLoad() {
        super.viewDidLoad()

        self.alwaysPoppableDelegate = AlwaysPoppableDelegate(navigationController: self, originalDelegate: self.interactivePopGestureRecognizer!.delegate!)
        self.interactivePopGestureRecognizer!.delegate = self.alwaysPoppableDelegate
    }
}

private class AlwaysPoppableDelegate : NSObject, UIGestureRecognizerDelegate {

    weak var navigationController: AlwaysPoppableNavigationController?
    var originalDelegate: UIGestureRecognizerDelegate

    init(navigationController: AlwaysPoppableNavigationController, originalDelegate: UIGestureRecognizerDelegate) {
        self.navigationController = navigationController
        self.originalDelegate = originalDelegate
    }

    @objc func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        if let nav = navigationController, nav.isNavigationBarHidden && nav.viewControllers.count > 1 { {
            return true
        }
        else {
            return self.originalDelegate.gestureRecognizer!(gestureRecognizer, shouldReceive: touch)
        }
    }

    override func responds(to aSelector: Selector) -> Bool {
        if aSelector == #selector(gestureRecognizer(_:shouldReceive:)) {
            return true
        }
        else {
            return self.originalDelegate.responds(to: aSelector)
        }
    }

    override func forwardingTarget(for aSelector: Selector!) -> Any? {
        return self.originalDelegate
    }

}

похоже, что решение, предоставленное @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

вы можете сделать это с помощью прокси-делегат. При создании навигационного контроллера возьмите существующий делегат. И передайте его в прокси. Затем передайте все методы делегата существующему делегату, кроме 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
    }  
}

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;

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

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

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

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

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

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

мое решение заключается в прямом расширении 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
    }

}

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

есть очень простое решение, которое я пробовал и работает отлично, это в 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);
    }

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

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