iOS 7で戻るボタンを変更すると、スワイプして戻ることができなくなります


81

次のようなカスタムの戻るボタンを設定しているiOS7アプリがあります。

    UIImage *backButtonImage = [UIImage imageNamed:@"back-button"];
    UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom];

    [backButton setImage:backButtonImage forState:UIControlStateNormal];
    backButton.frame = CGRectMake(0, 0, 20, 20);

    [backButton addTarget:self
                   action:@selector(popViewController)
         forControlEvents:UIControlEventTouchUpInside];

    UIBarButtonItem *backBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:backButton];
    viewController.navigationItem.leftBarButtonItem = backBarButtonItem;

ただし、これにより、iOS7の「左から右にスワイプ」ジェスチャを無効にして前のコントローラに移動できます。カスタムボタンを設定し、このジェスチャーを有効にしたままにする方法を知っている人はいますか?

編集:代わりにviewController.navigationItem.backBarButtonItemを設定しようとしましたが、これは私のカスタム画像を表示していないようです。


私はまだこれに対する適切な解決策を見つけていませんか?良い解決策を見つけて、それがうまくいく理由を説明する人はいますか??。
user1010819 2013年

よくできたサードパーティライブラリを使用するのはどうですか:SwipeBack
devxoul 2015

回答:


82

重要: これはハックです。この回答をご覧になることをお勧めします

leftBarButtonItem私のために働いたものを割り当てた後、次の行を呼び出します:

self.navigationController.interactivePopGestureRecognizer.delegate = self;

編集:initメソッドで 呼び出された場合、これは機能しません。viewDidLoadまたは同様のメソッドで呼び出す必要があります。


これをViewControllerで設定しますか?またはナビゲーションコントローラー?私は同じ問題を抱えていますが、これはうまくいかないようですか?
Kevin Renskers 2013

1
@mixedCaseサンプルプロジェクトをダウンロードした後、問題を理解しました。このコードは、コレクションビューのコンテンツがビューコントローラーの水平方向の幅を超えない限り機能します。ただし、コレクションビューが水平方向にスクロール可能になるとすぐに、interactivePopGestureRecognizerが上書きされます。回避策を見つけることができるかどうかを確認します。
ポールハンター

8
このコードに問題があります。5〜10回のバックスワイプVCがフリーズした後、それほど長くは機能しません。解決策はありますか?
Timur Bernikovich 2013年

1
ティムール・ベルニコウィッチ私も同じことを経験していますが、これには何か理由がありますか?
user1010819 2013年

4
デリゲートを設定するとself、クラスのオブジェクトからデリゲートの設定が解除されます_UINavigationInteractiveTransition。ナビゲーションコントローラーがすでに遷移しているときにポップするように指示されないようにするのは、そのオブジェクトの責任です。戻るボタンがカスタムの場合、このジェスチャーを有効にすることが可能かどうかはまだ調査中です。
Saltymule 2013

56

可能であれば、UINavigationBarのbackIndicatorImageプロパティとbackIndicatorTransitionMaskImageプロパティを使用します。これらをUIAppearanceProxyに設定すると、アプリケーション全体の動作を簡単に変更できます。しわは、それらをios 7でしか設定できないことですが、とにかくios 7でしかポップジェスチャを使用できないため、それはうまくいきます。通常のiOS6のスタイリングはそのままにしておくことができます。

UINavigationBar* appearanceNavigationBar = [UINavigationBar appearance];
//the appearanceProxy returns NO, so ask the class directly
if ([[UINavigationBar class] instancesRespondToSelector:@selector(setBackIndicatorImage:)])
{
    appearanceNavigationBar.backIndicatorImage = [UIImage imageNamed:@"back"];
    appearanceNavigationBar.backIndicatorTransitionMaskImage = [UIImage imageNamed:@"back"];
    //sets back button color
    appearanceNavigationBar.tintColor = [UIColor whiteColor];
}else{
    //do ios 6 customization
}

InteractivePopGestureRecognizerのデリゲートを操作しようとすると、多くの問題が発生します。


4
ダンに感謝します。これは本当に重要であり、受け入れられる答えになるはずです。デリゲートを再割り当てするだけで、多くの奇妙な振る舞いをすることができます。特に、ユーザーがをスワイプして戻そうとした場合topViewController
クリスワグナー

1
これは、戻るボタンの画像を変更するだけでよいと想定した場合の解決策です。しかし、戻るボタンのテキストや、戻るボタンをクリックしたときに実行されるアクションを変更したい場合はどうでしょうか。
user102008 2014年

その時点で、UINavigationItem.leftBarButtonItemを設定する方が適切です。グーグルまたはstackoverflowでleftBarButtonItemを検索することで見つけることができるさまざまな答えがあります。
Saltymule 2014年

あなたがあなた自身のUIに外観変化を制限したいとない場合で作成された、たとえばあるナビゲーションバーに影響ABPeoplePickerNavigationControllerカスタム使用することができますUINavigationController:サブクラス[[UINavigationBar appearanceWhenContainedIn:[THNavigationController class], nil] setBackIndicatorImage:[UIImage imageNamed:@"btn_back_arrow"]]; [[UINavigationBar appearanceWhenContainedIn:[THNavigationController class], nil] setBackIndicatorTransitionMaskImage:[UIImage imageNamed:@"btn_back_arrow_highlighted"]];
トム

2
残念ながら、これはiOS8では機能しません。戻るボタンは、私のカスタム画像の外観を変更しません。
レンズフレア2014年

29

UINavigationControllerをサブクラス化するこのソリューションhttp://keighl.com/post/ios7-interactive-pop-gesture-custom-back-button/を見ました。コントローラが所定の位置に配置される前にスワイプした場合を処理するため、より優れたソリューションです。これにより、クラッシュが発生します。

これに加えて、ルートビューコントローラーをスワイプすると(1つを押した後、もう一度元に戻すと)UIが応答しなくなることに気付きました(上記の回答でも同じ問題があります)。

したがって、サブクラス化されたUINavigationControllerのコードは次のようになります。

@implementation NavigationController

- (void)viewDidLoad {
    [super viewDidLoad];
    __weak NavigationController *weakSelf = self;

    if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
        self.interactivePopGestureRecognizer.delegate = weakSelf;
        self.delegate = weakSelf;
    }
}

- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
    // Hijack the push method to disable the gesture
    if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
        self.interactivePopGestureRecognizer.enabled = NO;
    }
    [super pushViewController:viewController animated:animated];
}

#pragma mark - UINavigationControllerDelegate

- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animate {
    // Enable the gesture again once the new controller is shown
    self.interactivePopGestureRecognizer.enabled = ([self respondsToSelector:@selector(interactivePopGestureRecognizer)] && [self.viewControllers count] > 1);
}

@end

1
ルートビューコントローラーをスワイプしても同じ問題が発生しました。ありがとうございます。
Michael Rose

ページ全体での最良の解決策。完全にうまく機能します。ありがとう
Shahid Iqbal 2015

19

私が使う

[[UINavigationBar appearance] setBackIndicatorImage:[UIImage imageNamed:@"nav_back.png"]];
[[UINavigationBar appearance] setBackIndicatorTransitionMaskImage:[UIImage imageNamed:@"nav_back.png"]];

[UIBarButtonItem.appearance setBackButtonTitlePositionAdjustment:UIOffsetMake(0, -64) forBarMetrics:UIBarMetricsDefault];

2
私は他の場所で、これが現在Appleのコードのバグのために64ビットモードで問題を引き起こすというコメントを見ました-ちょうど私が警告を投稿すると思っていました
Peter Johnson

@PeterJohnsonどんなバグ?
art-divin 2014

単に最良の解決策!
Igotit 2014

これまでで最も簡単なソリューション。
tounaobun 2015

6

また、戻るボタンを非表示にして、カスタムのleftBarItemに置き換えます。
プッシュアクションが機能した後、interactivePopGestureRecognizerデリゲートを削除します。

[self.navigationController pushViewController:vcToPush animated:YES];

// Enabling iOS 7 screen-edge-pan-gesture for pop action
if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
    self.navigationController.interactivePopGestureRecognizer.delegate = nil;
}

4
この方法の問題の1つは、ルートビューでエッジパンを実行すると、UIがロックされることです。同じビューの複数のインスタンスをナビゲーションスタックにプッシュしているため、これが発生しました。
Nikola Lajic 2013年

@MrNickBarker教えてくれてありがとう!正確なシナリオを教えてください。ルートビューコントローラーをスワイプすると再現できませんでした
avishic 2013年

4
viewDidLoadメソッドで設定していました。私の最終的な解決策は、navスタックに複数のView Controllerがある場合に、「gestureRecognizerShouldBegin」に対してtrueを返すデリゲートとして別のクラスを設定することでした。
Nikola Lajic 2013年

MrNickBarkerがフリーズする理由私は同じことを経験しています
user1010819 2013年

6
navigationController.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)self;

これはhttp://stuartkhall.com/posts/ios-7-development-tips-tricks-hacksからのものですが、いくつかのバグが発生します。

  1. 画面の左端からスワイプするときに、別のviewControllerをnavigationControllerにプッシュします。
  2. または、topViewControllerがnavigationControllerからポップアップしているときに、画面の左端からスワイプします。

たとえば、navigationControllerのrootViewControllerが表示されているときに、画面の左端からスワイプして、何か(すばやく)をタップしてanotherViewControllerをnavigationControllerにプッシュします。

  • rootViewControllerはタッチイベントに応答しません。
  • anotherViewControllerは表示されません。
  • 画面の端からもう一度スワイプすると、anotherViewControllerが表示されます。
  • カスタムの戻るボタンをタップしてanotherViewControllerをポップし、クラッシュします。

したがってUIGestureRecognizerDelegate、次のself.navigationController.interactivePopGestureRecognizer.delegateようにメソッドを実装する必要があります。

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
    if (gestureRecognizer == navigationController.interactivePopGestureRecognizer) {
        return !navigationController.<#TODO: isPushAnimating#> && [navigationController.viewControllers count] > 1;
    }
    return YES;
}

1
SwipeBackは、これらの問題の解決策です。
devxoul 2015

6

これがニックH247の答えのswift3バージョンです

class NavigationController: UINavigationController {
  override func viewDidLoad() {
    super.viewDidLoad()
    if responds(to: #selector(getter: interactivePopGestureRecognizer)) {
      interactivePopGestureRecognizer?.delegate = self
      delegate = self
    }
  }

  override func pushViewController(_ viewController: UIViewController, animated: Bool) {
    if responds(to: #selector(getter: interactivePopGestureRecognizer)) {
      interactivePopGestureRecognizer?.isEnabled = false
    }
    super.pushViewController(viewController, animated: animated)
  }
}

extension NavigationController: UINavigationControllerDelegate {
  func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
    interactivePopGestureRecognizer?.isEnabled = (responds(to: #selector(getter: interactivePopGestureRecognizer)) && viewControllers.count > 1)
  }
}

extension NavigationController: UIGestureRecognizerDelegate {}

3

試してみてください self.navigationController.interactivePopGestureRecognizer.enabled = YES;


いいえ、すでに有効になっています。問題は、そのデリゲートがジェスチャ認識機能の開始を許可していないことのようです。ここに別の回答としてソリューションを追加しました。
avishic 2013年

avishicデリゲートの設定が機能する理由を説明していただけませんか?..私は同じ問題で立ち往生しています。
user1010819 2013年

1

私はこれを書きませんでしたが、次のブログは大いに役立ち、カスタムナビゲーションボタンに関する私の問題を解決しました:

http://keighl.com/post/ios7-interactive-pop-gesture-custom-back-button/

要約すると、彼はポップジェスチャデリゲートを使用するカスタムUINavigationControllerを実装しています。とても清潔で持ち運び可能!

コード:

@interface CBNavigationController : UINavigationController <UINavigationControllerDelegate, UIGestureRecognizerDelegate>
@end

@implementation CBNavigationController

- (void)viewDidLoad
{
  __weak CBNavigationController *weakSelf = self;

  if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)])
  {
    self.interactivePopGestureRecognizer.delegate = weakSelf;
    self.delegate = weakSelf;
  }
}

// Hijack the push method to disable the gesture

- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
  if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)])
    self.interactivePopGestureRecognizer.enabled = NO;

  [super pushViewController:viewController animated:animated];
}

#pragma mark UINavigationControllerDelegate

- (void)navigationController:(UINavigationController *)navigationController
       didShowViewController:(UIViewController *)viewController
                    animated:(BOOL)animate
{
  // Enable the gesture again once the new controller is shown

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

編集します。ユーザーがルートビューコントローラーを左にスワイプしようとしたときの問題の修正を追加しました。

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {

    if ([self respondsToSelector:@selector(interactivePopGestureRecognizer)] &&
        self.topViewController == [self.viewControllers firstObject] &&
        gestureRecognizer == self.interactivePopGestureRecognizer) {

        return NO;
    }

    return YES;
}

ナビゲーションコントローラーのルートビューコントローラーを左にスワイプすると、UIがフリーズします。
JohnVanDijk 2015

@JohnVanDijk私はその問題を解決するために実装したと思う修正を加えて回答を編集しました。しばらく経ちましたが、それは理にかなっています。基本的に、トップビューコントローラーがルートビューコントローラーである場合、「interactivePopGestureRecognizer」に応答しません
kgaidis 2015

それは私の戻るボタンを隠しています
モハメッドフセイン2016

1

RootView

override func viewDidAppear(_ animated: Bool) {
    self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false
}

ChildView

override func viewDidLoad() {
    self.navigationController?.interactivePopGestureRecognizer?.isEnabled = true
    self.navigationController?.interactivePopGestureRecognizer?.delegate = self
} 

extension ChildViewController: UIGestureRecognizerDelegate {}

1

このロジックを使用して、スワイプジェスチャを有効または無効にします。

- (void)navigationController:(UINavigationController *)navigationController
       didShowViewController:(UIViewController *)viewController
                    animated:(BOOL)animate
{
    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)])
    {
        if (self.navigationController.viewControllers.count > 1)
        {
            self.navigationController.interactivePopGestureRecognizer.enabled = YES;
        }
        else
        {
            self.navigationController.interactivePopGestureRecognizer.enabled = NO;
        }
    }
}

0

現在のビューコントローラーをインタラクティブポップジェスチャのデリゲートとして割り当てていた場合にも同様の問題が発生しましたが、プッシュされたビュー、またはナビゲーションスタックのビューの下にあるビューでジェスチャが中断されました。これを解決する方法は、デリゲートを-viewDidAppearに設定してから、nilに設定すること-viewWillDisappearでした。これにより、他のビューが正しく機能するようになりました。


0

Appleのデフォルトのマスター/詳細プロジェクトテンプレートを使用していると想像してください。マスターはテーブルビューコントローラーであり、それをタップすると詳細ビューコントローラーが表示されます。

詳細ビューコントローラに表示される戻るボタンをカスタマイズしたいと思います。これは、戻るボタンの画像画像の色テキスト、テキストの色、およびフォントをカスタマイズする方法です。


画像、画像の色、テキストの色、またはフォントをグローバルに変更するには、View Controllerが作成される前に呼び出される場所に以下を配置します(たとえばapplication:didFinishLaunchingWithOptions:、適切な場所です)。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    UINavigationBar* navigationBarAppearance = [UINavigationBar appearance];

    // change the back button, using default tint color
    navigationBarAppearance.backIndicatorImage = [UIImage imageNamed:@"back"];
    navigationBarAppearance.backIndicatorTransitionMaskImage = [UIImage imageNamed:@"back"];

    // change the back button, using the color inside the original image
    navigationBarAppearance.backIndicatorImage = [[UIImage imageNamed:@"back"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
    navigationBarAppearance.backIndicatorTransitionMaskImage = [UIImage imageNamed:@"back"];

    // change the tint color of everything in a navigation bar
    navigationBarAppearance.tintColor = [UIColor greenColor];

    // change the font in all toolbar buttons
    NSDictionary *barButtonTitleTextAttributes =
    @{
      NSFontAttributeName: [UIFont fontWithName:@"HelveticaNeue-Light" size:12.0],
      NSForegroundColorAttributeName: [UIColor purpleColor]
      };

    [[UIBarButtonItem appearance] setTitleTextAttributes:barButtonTitleTextAttributes forState:UIControlStateNormal];

    return YES;
}

appearanceWhenContainedIn:これらの変更によって影響を受けるビューコントローラをより細かく制御するために使用できますが、渡すことはできないことに注意してください。[DetailViewController class]は、DetailViewControllerではなくUINavigationController内に含まれているため、ください。つまり、影響を受けるものをより細かく制御する場合は、UINavigationControllerをサブクラス化する必要があります。

特定の戻るボタンアイテムのテキストまたはフォント/色をカスタマイズするには、(DetailViewControllerではなく)MasterViewControllerでカスタマイズする必要があります。ボタンがDetailViewControllerに表示されるため、これは直感的ではないようです。ただし、それをカスタマイズする方法がnavigationItemにプロパティを設定することであると理解すると、より意味がわかり始めます。

- (void)viewDidLoad { // MASTER view controller
    [super viewDidLoad];

    UIBarButtonItem *buttonItem = [[UIBarButtonItem alloc] initWithTitle:@"Testing"
                                                                   style:UIBarButtonItemStylePlain
                                                                  target:nil
                                                                  action:nil];
    NSDictionary *barButtonTitleTextAttributes =
    @{
      NSFontAttributeName: [UIFont fontWithName:@"HelveticaNeue-Light" size:12.0],
      NSForegroundColorAttributeName: [UIColor purpleColor]
      };
    [buttonItem setTitleTextAttributes:barButtonTitleTextAttributes forState:UIControlStateNormal];
    self.navigationItem.backBarButtonItem = buttonItem;
}

注:self.navigationItem.backBarButtonItemを設定した後にtitleTextAttributesを設定しようとすると機能しないように見えるため、このプロパティに値を割り当てる前に設定する必要があります。


0

'UINavigationController'のサブクラスであるクラス 'TTNavigationViewController'を作成し、このクラスの既存のナビゲーションコントローラーをストーリーボード/クラスのいずれかに作成します。クラスのサンプルコード-

    class TTNavigationViewController: UINavigationController, UIGestureRecognizerDelegate {

override func viewDidLoad() {
    super.viewDidLoad()
    self.setNavigationBarHidden(true, animated: false)

    // enable slide-back
    if self.responds(to: #selector(getter: UINavigationController.interactivePopGestureRecognizer)) {
        self.interactivePopGestureRecognizer?.isEnabled = true
        self.interactivePopGestureRecognizer?.delegate  = self
    }
}

func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
    return true
}}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.