it-swarm.com.ru

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

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

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

enter image description here

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

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

Разве нет возможности спрятать верхнюю панель навигации и все-таки провести пальцем?

65
mihai

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

[self.navigationController.interactivePopGestureRecognizer setDelegate:nil];

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

87
HorseT

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

Корневой контроллер

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/

48
saranpol

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

Установка 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
}

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

47
Hunter Maximillion Monk

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

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

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

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

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

Решение @Nathan Perry приближается, но без реализации 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
        }
    }
}
10
Chris Vasselli

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

8
tylermilner

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

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

}

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

3
fredericdnd

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

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

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

NavigationController.InteractivePopGestureRecognizer.Delegate = this;
1
Ahmad

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

Идея состоит в том, чтобы реализовать «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;  
}
1
KarimIhab

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

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

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

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

0
Mladen Ivastinovic

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

0
Mundi

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

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

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

0
fruitcoder