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