赤いニシンを取り除く
まず第一に、あなたの例は大幅に単純化できます。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];
}
}