22

UINavigationBarアプリで のタイトルが登場すると、奇妙な問題が発生しinteractivePopGestureRecognizerます。このバグを紹介するデモアプリ作成しました。

設定:

  • rootViewController はUINavigationController.
  • FirstViewControllerナビゲーション バーが非表示になっているinteractivePopGestureRecognizer.enabled = NO;
  • SecondおよびThirdViewControllers では、ナビゲーション バーが表示され、popgesture が有効になっています。

バグ:

このバグは、ポップジェスチャを使用して 2 番目のビューから 1 番目のビューに戻るときに発生します。2 番目のビューを途中まで引いてから 2 番目のビューに戻ると、ナビゲーション タイトルは (予想どおり) "2 番目のビュー" と表示されますが、3 番目のビューに移動すると、タイトルは "3 番目のビュー" に変わりません。そして、3番目のビューの戻るボタンをクリックすると、ナビゲーションバーが台無しになります。

私のデモアプリをチェックしてください。このバグが発生する理由を説明する助けをいただければ幸いです。ありがとう!

4

5 に答える 5

51

赤いニシンを取り除く

まず第一に、あなたの例は大幅に単純化できます。viewDidLoadそれは完全な赤いニシンであり、問​​題を複雑にするだけなので、すべてのものを削除する必要があります. ビュー コントローラーを変更するたびにポップ ジェスチャ レコグナイザー デリゲートをいじってはいけません。また、ポップ ジェスチャ レコグナイザーのオフとオンの切り替えは、この例とは無関係です (既定ではオンになっているため、この例ではオンのままにしておく必要があります)。したがって、3 つのビュー コントローラーすべてでこの種のものを削除します。

- (void)viewDidLoad {
    [super viewDidLoad];
    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
        self.navigationController.interactivePopGestureRecognizer.enabled = NO;
        self.navigationController.interactivePopGestureRecognizer.delegate = self;
    }
}

( を設定するコードを削除しないでください。ただし、各ビュー コントローラーのファイルでself.titleそれを行うことで、作業をさらに簡単にすることもできます。)xib

init...また、メソッドやメモリ アラート メソッドなど、使用されていない他のメソッドを完全に削除することもできます。

superところで、もう 1 つの問題は、 の実装を呼び出すのを忘れていることですviewWillAppear:。これを行う必要があります。それがバグに影響を与えるとは思いませんが、これらのことを追跡しようとする前に、すべての規則に従うことをお勧めします。

バグはまだ発生していますが、コードがはるかに単純になったため、問題の切り分けを開始できます。

ポップ ジェスチャーのしくみ

それで、問題の原因は何ですか?それを理解する最も明白な方法は、ポップジェスチャーがどのように機能するかを理解することだと思います. これは、インタラクティブなビュー コントローラーの遷移アニメーションです。そうです、アニメーションです。仕組みとしては、スーパービュー レイヤーにポップ アニメーション (左からスライド) をアタッチしますが、aspeedを 0 にして、実際には実行しないようにします。ジェスチャが進むにつれて、timeOffsetレイヤの が絶えず更新され、対応するアニメーションの「フレーム」が表示されます。したがって、ビューをドラッグしているように見えますが、そうではありません。あなたはジェスチャーをしているだけで、アニメーションは同じ速度で同じ程度に進んでいます。この回答でこのメカニズムを説明しました: https://stackoverflow.

最も重要なことは (この部分に注意してください)、ジェスチャが途中で放棄された場合 (ほぼ確実にそうなるでしょう)、ジェスチャが半分以上完了したかどうかが決定され、これに基づいて次のいずれかです。アニメーションが最後まで急速に再生される (つまり、speedが のように設定されている3) か、アニメーションが最初から逆方向に実行されている(つまり、speedが のように設定されている-3)。

ソリューションとその理由

それでは、バグについて話しましょう。ここには、誤ってぶつかった 2 つの複雑な問題があります。

  • ポップ アニメーションとポップ ジェスチャが始まるとviewWillAppear:、ビューが最終的に表示されない場合でも、前のビュー コントローラに対して呼び出されます (これはインタラクティブなジェスチャであり、ジェスチャがキャンセルされる可能性があるため)。ビューが実際に画面を引き継ぐ (そして呼び出される)ことviewWillAppear:が常に続くという仮定に慣れている場合、これは深刻な問題になる可能性があります。(Apple が WWDC 2013 のビデオで言っているように、「ビューが表示される」は実際には「ビュー表示される可能性がある」という意味です。)viewDidAppear:

  • アニメーションの 2 番目のセットがあります。つまり、ナビゲーション バーに関連するすべてのものです。タイトルの変更 (ビューにフェードインするはずです) と、この場合は非表示と非表示の間の変更です。ランタイムは、アニメーションのセカンダリ セットをスライド ビュー アニメーションと調整しようとしています。しかし、バーが非表示または表示されているときにアニメーションを呼び出さないようにすることで、これを困難にしています。

したがって、すでに説明したように、1 つの解決策はコード全体を変更animated:NOすることです。このように、ナビゲーション バーの表示と非表示は、アニメーションの一部animated:YESとして順序付けられます。したがって、ジェスチャがキャンセルされ、アニメーションが開始点まで逆方向に実行されると、ナビゲーションの表示/非表示開始点まで逆方向に実行されます。この 2 つのことは現在、調整されたままです。

しかし、本当にその変更を行いたくない場合はどうすればよいでしょうか? さて、別の解決策は全体に変更viewWillAppear:することです。viewDidAppear:既に述べviewWillAppear:たように、ジェスチャが完了しない場合でも、アニメーションの開始時に呼び出されるため、物事がうまくいかなくなります。ただしviewDidAppear:、ジェスチャが完了した (キャンセルされていない) 場合と、アニメーションが既に終了している場合にのみ呼び出されます。

これらの 2 つのソリューションのどちらが好みですか? どっちもない!どちらも、やりたくない変更を強制します。本当の解決策は、トランジションコーディネーターを使用することです。

移行コーディネーター

トランジション コーディネーターは、まさにこの目的のためにシステムによって提供されるオブジェクトです。つまり、インタラクティブなトランジションに関与していることを検出し、キャンセルされたかどうかに応じて異なる動作をします。

の OneViewController 実装だけに集中してくださいviewWillAppear:。これは物事がめちゃくちゃになっているところです。TwoViewController にいて、左からパン ジェスチャを開始すると、OneViewControllerviewWillAppear:が呼び出されます。しかし、その後キャンセルして、ジェスチャーを完了せずに手放します。その 1 つのケースだけで、OneViewController の で行っていたことを実行したくありませんviewWillAppear:。そしてそれこそが、移行コーディネーターがあなたにできることです

ここでは、OneViewController のviewWillAppear:. これにより、他の変更を加えることなく問題が修正されます。

-(void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    id<UIViewControllerTransitionCoordinator> tc = self.transitionCoordinator;
    if (tc && [tc initiallyInteractive]) {
        [tc notifyWhenInteractionEndsUsingBlock:
         ^(id<UIViewControllerTransitionCoordinatorContext> context) {
             if ([context isCancelled]) {
                 // do nothing!
             } else { // not cancelled, do it
                 [self.navigationController setNavigationBarHidden:YES animated:NO];
             }
         }];
    } else { // not interactive, do it
        [self.navigationController setNavigationBarHidden:YES animated:NO];
    }
}
于 2014-04-26T16:50:40.443 に答える
6

修正は簡単ですが、なぜこれが起こっているのか、現時点では説明がありません。

OneViewControllerviewWillAppearを に変更します。

-(void)viewWillAppear:(BOOL)animated{
   // [self.navigationController setNavigationBarHidden:YES animated:NO];
    self.navigationController.navigationBar.hidden = YES;
}

2 番目と 3 番目のビュー コントローラーで、次のように変更します。

 -(void)viewWillAppear:(BOOL)animated{
         //[self.navigationController setNavigationBarHidden:NO animated:NO];
         self.navigationController.navigationBar.hidden = NO;
    }

奇妙ですが、UINavigationBar の隠しプロパティを直接使用すると、これで問題が解決します。

于 2014-04-26T08:04:41.123 に答える
1

ナビゲーションスタックをポップする独自のスワイプ認識エンジンを使用して、この作業を行うことをあきらめました。

override func viewDidLoad() {
    super.viewDidLoad()

    // disable system swipe back gesture and add our own
    navigationController?.interactivePopGestureRecognizer?.enabled = false
    let swipeBackGestureRecognizer = UISwipeGestureRecognizer(target: self, action: "swipeBackAction:")
    swipeBackGestureRecognizer.direction = UISwipeGestureRecognizerDirection.Right
    tableView.addGestureRecognizer(swipeBackGestureRecognizer)
}

func swipeBackAction(sender: UISwipeGestureRecognizer) {

    navigationController?.popViewControllerAnimated(true)
}
  • システムの interactivePopGestureRecognizer を無効にします
  • 右方向で独自の UISwipeGestureRecognizer を作成する
  • スワイプが検出されたときにアニメーション化されたナビゲーション スタックをポップする
于 2016-03-10T22:55:11.347 に答える
1

「FirstViewController のナビゲーション バーが非表示になっている」をどのように作成するのかわかりません。

同じ問題があり、交換して修正しました

self.navigationController.navigationBarHidden = YES / NO;

[self.navigationController setNavigationBarHidden:YES / NO animated:animated];
于 2016-02-24T10:45:06.760 に答える
0

これが私のためにそれを修正したものです(Swift)

最初のビュー コントローラー:

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
    self.navigationController?.setNavigationBarHidden(true, animated: animated)
}

2 番目と 3 番目のビュー コントローラー:

override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
    self.navigationController?.setNavigationBarHidden(false, animated: animated)
}
于 2016-05-16T18:26:40.277 に答える