web-dev-qa-db-ja.com

UINavigationControllerのナビゲーションバーを非表示にするときにスワイプバックしない

ビューをUINavigationControllerに埋め込むことから継承されたスワイプパックが大好きです。残念ながら、NavigationBarを非表示にする方法を見つけることはできませんが、タッチパンをスワイプバックgestureします。カスタムジェスチャを書くことはできますが、代わりにUINavigationControllerバックスワイプgestureを使用せず、依存することを好みます。

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

enter image description here

あるいは、同じシナリオでプログラムで非表示にした場合。

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

トップNavigationBarを非表示にして、スワイプする方法はありませんか?

69
mihai

動作しているハックは、次のようにinteractivePopGestureRecognizerUINavigationControllerのデリゲートをnilに設定することです。

[self.navigationController.interactivePopGestureRecognizer setDelegate:nil];

しかし、状況によっては奇妙な効果が生じる可能性があります。

88
HorseT

他の方法の問題

interactivePopGestureRecognizer.delegate = nilを設定すると、意図しない副作用があります。

navigationController?.navigationBar.hidden = trueの設定は機能しますが、ナビゲーションバーの変更を非表示にすることはできません。

最後に、Navigation Controllerの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
    }
}

次に、Navigation Controllerの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
}

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

56
Hunter Monk

私の場合、奇妙な効果を防ぐために

ルートビューコントローラー

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/

52
saranpol

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

(更新)Swift 4.2

他の投稿されたソリューションがデリゲートをオーバーライドするか、nilに設定すると、予期しない動作が発生することがわかりました。

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

私のテストから、gestureRecognizer(_:, shouldReceiveTouch:)は、ナビゲーションバーが非表示のときにジェスチャーを認識しないように元のデリゲートが実装しているメソッドであり、gestureRecognizerShouldBegin(_:)ではないようです。デリゲートでgestureRecognizerShouldBegin(_:)を実装する他のソリューションは、gestureRecognizer(_:, shouldReceiveTouch:)の実装がないためにすべてのタッチを受信するデフォルトの動作が発生するためです。

@Nathan Perryのソリューションは近づきますが、respondsToSelector(_:)の実装がなければ、デリゲートにメッセージを送信するUIKitコードは、他のデリゲートメソッドの実装はないと信じてしまい、forwardingTargetForSelector(_:)は呼び出されません。

そのため、振る舞いを変更したい特定のシナリオでは、 `gestureRecognizer(_ :, 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
        }
    }
}
15
Chris Vasselli

Hunter Maximillion Monk's answer を基に、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)
    }
}

ストーリーボード:

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
7

シンプル、副作用なし

ここでの回答のほとんどは良いものですが、意図しない副作用(アプリの破損)があるか、冗長です。

私が思いついた最もシンプルで機能的なソリューションは次のとおりでした:

NavigationBarを非表示にしている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
    }
}

他の答えは、単にデリゲートをnilに設定することを示唆しています。ナビゲーションスタックの最初のView Controllerに後方にスワイプすると、すべてのジェスチャーが無効になります。おそらく、UIKit/UIGesture開発者の何らかの監視。

同様に、ここで実装したいくつかの回答は、非標準のAppleナビゲーション動作(具体的には、後方にスワイプしながら上下にスクロールする機能を可能にします)。これらの回答も少し冗長で、場合によっては不完全です。

3
CodyB

私の解決策は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
    }

}

このように、すべてのNavigation Controllerはスライドすることにより非表示になります。

3
fredericdnd

Proxy Delegateでそれを行うことができます。 Navigation Controllerを作成するときに、既存のデリゲートを取得します。そして、プロキシに渡します。次に、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回答:

ViewControllerのクラス定義にIUIGestureRecognizerDelegateインターフェイスを実装します。

_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;
_
1
Ahmad

私の解決策は次のとおりです。ナビゲーションバーのアルファを変更していますが、ナビゲーションバーは非表示ではありません。私のすべてのView ControllerはBaseViewControllerのサブクラスであり、次のものがあります。

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

UINavigationControllerをサブクラス化し、そこにそのメソッドを配置することもできます。

1

私はこれを試しましたが、完全に機能しています: スライドバック機能を失うことなくナビゲーションバーを非表示にする方法

アイデアは、.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;  
}
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

一部の人々 代わりにアニメーション化されたsetNavigationBarHiddenYESメソッドを呼び出すことで成功しました。

0
Mundi

ナビゲーションバーなしのView Controllerで使用します

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