ios 戻る UINavigationControllerでナビゲーションバーを隠すときにスワイプしない




uinavigationcontroller カスタマイズ (12)

私はあなたのビューをUINavigationControllerに埋め込むことから継承したスワイプパックを愛しています。 残念ながら、私はナビゲーションバーを隠す方法を見つけることができないように見えるが、タッチパネルをスワイプバックのジェスチャーを持っている。 私はカスタムジェスチャーを書くことができますが、代わりにUINavigationControllerのスワイプジェスチャーに頼るのではなく、その代わりに使うことを好みます。

ストーリーボードでチェックを外すと、後ろのスワイプは機能しません

代わりに、私はそれをプログラムで隠す場合、同じシナリオです。

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

一番上のナビゲーションバーを隠す方法はないのですか?


あなたはプロキシデリゲートでそれを行うことができます。 ナビゲーションコントローラを構築するときは、既存のデリゲートを取得します。 それをプロキシに渡します。 次に、すべてのデリゲートメソッドをgestureRecognizer:shouldReceiveTouch:以外の既存のデリゲートにgestureRecognizer:shouldReceiveTouch: using 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
    }  
}

私の解決策は、 UINavigationControllerクラスを直接拡張すること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
    }

}

このようにして、すべてのナビゲーションコントローラはスライドすることで解除できます。


代わりに、animated YES使用してsetNavigationBarHiddenメソッドを呼び出すことによって成功した人もいます。


私は、デリゲートをオーバーライドする他のポストされた解決策、またはそれをnilに設定すると予期しない動作が発生することがわかりました。

私の場合は、ナビゲーションスタックの一番上にいて、もう一度ポップするためにジェスチャーを使用しようとすると、(期待どおり)失敗しますが、スタックにプッシュしようとすると、奇妙なグラフィカルグリッチが発生します。ナビゲーションバー。 これは理にかなっています。なぜなら、デリゲートは、ナビゲーションバーが隠されているときにジェスチャが認識されないようにするかどうかと、他のすべての動作がスローされているかどうかを処理するだけではありません。

私のテストでは、 gestureRecognizer(_:, shouldReceiveTouch:)は、 gestureRecognizerShouldBegin(_:)ではなく、ナビゲーションバーが隠されているときにジェスチャが認識されないように元のデリゲートが実装しているメソッドです。 gestureRecognizer(_:, shouldReceiveTouch:)実装が不足しているため、すべての接触を受け取るデフォルトの動作が発生するため、代理人の作業でgestureRecognizerShouldBegin(_:)を実装する他のソリューション

@ Nathan Perryの解決策は近づいていますが、responsToSelector 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

作業中のハックは、 UINavigationControllernilに設定するinteractivePopGestureRecognizerです:

[self.navigationController.interactivePopGestureRecognizer setDelegate:nil];

しかし、いくつかの状況では、それは奇妙な効果を生むことがあります。


私はこれを試して、それは完全に動作しています: どのようにスライドバック能力を失うことなくナビゲーションバーを非表示にする

アイデアはあなたの.hに "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;  
}

ユーザーがViewControllerからスライドしたときにジェスチャ認識機能を無効にする方法は次のとおりです。 viewWillAppear()またはViewDidLoad()メソッドに貼り付けることができます。

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

ナビゲーションバーが表示されていないビューコントローラでは、

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を次のようにサブクラス化できます。

@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

他のメソッドの問題

interactivePopGestureRecognizer.delegate = nil設定する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
}

トップコントローラにテーブル、コレクション、またはスクロールビューのサブビューがある場合でも、副作用のない非表示のナビゲーションバーをお楽しみください。


Hunter Maximillion Monkの答えを捨てて、私はUINavigationControllerのサブクラスを作成し、私のストーリーボードに私のUINavigationControllerのカスタムクラスを設定しました。 2つのクラスの最終コードは次のようになります。

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

ストーリーボード:





uigesturerecognizer