私は立ち往生している問題がありますが、なぜそれが起こるのかさえわかりません。スタック上の詳細コントローラーをプッシュし、デフォルトの左エッジinteractivePopGestureRecognizer
を使用して非常にすばやく戻ると、親/ルートビューコントローラーのUINavigationBar
が破損しているか、組み込みのように見えます。 iOSの移行メカニズムには、詳細ビューがなくなった後にリセットするという仕事をする時間がありませんでした。また、明確にするために、この「破損した」UINavigationBar
のすべてはまだタッチ可能であり、私の親/ルートビューコントローラーのすべてが完全に機能します。
ソースコードがないために反対票を投じる人のために:ソースコードはありません!これはAppleバグです!
このUINavigationBar
を、親/ルートビューコントローラーのviewDidAppearメソッドが呼び出されたときの状態にリセットする方法はありますか?
左端interactivePopGestureRecognizer
を使用する代わりに左上の戻るボタンをタップしても、このバグは発生しないことに注意してください。
編集:NSLogを追加して、親/ルートビューコントローラーのviewDidAppearでnavigationBarのサブビューカウントを確認しました。カウントは常に同じで、破損しているかどうかに関係なく、ポップされたコントローラーがなぜ大混乱を引き起こしているのかを知りたいです。 UINavigationBar
。
よろしければ、よろしくお願いします!ありがとうございました。
外観のスクリーンショットを添付しました。バックシェブロンは親/ルートビューコントローラーの一部ではなく、スタックからポップされたものの一部であることに注意してください。 Testing123は、親/ルートビューコントローラーのタイトルであり、スタックからポップされたもののタイトルではありません。頭と歯車のアイコンは、親/ルートビューコントローラーの一部です。
編集:私はこのような何かが問題を解決できると思っていましたが、それは解決しないことがわかりました、そしてIMOも本当に悪い経験です。これは私が探している種類の解決策ではありません。これを正しく解決できるように、私は大きな報奨金を投稿しています! ????。この奇妙なUIの動作を本番品質のアプリに含めることはできません。
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self.navigationController pushViewController:[UIViewController new] animated:NO];
[self.navigationController popToRootViewControllerAnimated:YES];
}
UIViewController
にカテゴリを作成しました。これにより、この問題が修正されることを願っています。デバイスでナビゲーションバーの破損を実際に再現することはできませんが、シミュレーターでかなり頻繁に再現できます。このカテゴリで問題が解決します。うまくいけば、それはまたあなたのためにデバイス上でそれを解決します。
何が原因なのか正確にはわかりませんが、ナビゲーションバーのサブビューのレイヤーのアニメーションが2回実行されているか、完全に完了していないか...何かが発生しているようです。とにかく、これらのサブビューにいくつかのアニメーションを追加するだけで、それらを本来あるべき場所に強制的に戻すことができることがわかりました(適切な不透明度、色など)。秘訣は、ViewControllerのtransitionCoordinator
オブジェクトを使用して、いくつかのイベントにフックすることです。つまり、指を離してインタラクティブなポップジェスチャレコグナイザーが終了し、残りのアニメーションが開始したときに発生するイベントです。次に、アニメーションの非インタラクティブな半分が終了したときに発生するイベント。
transitionCoordinator
のいくつかのメソッド、具体的にはnotifyWhenInteractionEndsUsingBlock:
とanimateAlongsideTransition:completion:
を使用して、これらのイベントにフックできます。前者では、ナビゲーションバーのサブビューのレイヤーの現在のすべてのアニメーションのコピーを作成し、それらをわずかに変更して保存します。これにより、後でアニメーションの非インタラクティブ部分が終了したときに適用できるようになります。これは完了ブロックにあります。これらの2つの方法の後者の。
そして、これがUIViewController
カテゴリのコードです。
@interface UIViewController (FixNavigationBarCorruption)
- (void)fixNavigationBarCorruption;
@end
@implementation UIViewController (FixNavigationBarCorruption)
/**
* Fixes a problem where the navigation bar sometimes becomes corrupt
* when transitioning using an interactive transition.
*
* Call this method in your view controller's viewWillAppear: method
*/
- (void)fixNavigationBarCorruption
{
// Get our transition coordinator
id<UIViewControllerTransitionCoordinator> coordinator = self.transitionCoordinator;
// If we have a transition coordinator and it was initially interactive when it started,
// we can attempt to fix the issue with the nav bar corruption.
if ([coordinator initiallyInteractive]) {
// Use a map table so we can map from each view to its animations
NSMapTable *mapTable = [[NSMapTable alloc] initWithKeyOptions:NSMapTableStrongMemory
valueOptions:NSMapTableStrongMemory
capacity:0];
// This gets run when your finger lifts up while dragging with the interactivePopGestureRecognizer
[coordinator notifyWhenInteractionEndsUsingBlock:^(id<UIViewControllerTransitionCoordinatorContext> context) {
// Loop through our nav controller's nav bar's subviews
for (UIView *view in self.navigationController.navigationBar.subviews) {
NSArray *animationKeys = view.layer.animationKeys;
NSMutableArray *anims = [NSMutableArray array];
// Gather this view's animations
for (NSString *animationKey in animationKeys) {
CABasicAnimation *anim = (id)[view.layer animationForKey:animationKey];
// In case any other kind of animation somehow gets added to this view, don't bother with it
if ([anim isKindOfClass:[CABasicAnimation class]]) {
// Make a pseudo-hard copy of each animation.
// We have to make a copy because we cannot modify an existing animation.
CABasicAnimation *animCopy = [CABasicAnimation animationWithKeyPath:anim.keyPath];
// CABasicAnimation properties
// Make sure fromValue and toValue are the same, and that they are equal to the layer's final resting value
animCopy.fromValue = [view.layer valueForKeyPath:anim.keyPath];
animCopy.toValue = [view.layer valueForKeyPath:anim.keyPath];
animCopy.byValue = anim.byValue;
// CAPropertyAnimation properties
animCopy.additive = anim.additive;
animCopy.cumulative = anim.cumulative;
animCopy.valueFunction = anim.valueFunction;
// CAAnimation properties
animCopy.timingFunction = anim.timingFunction;
animCopy.delegate = anim.delegate;
animCopy.removedOnCompletion = anim.removedOnCompletion;
// CAMediaTiming properties
animCopy.speed = anim.speed;
animCopy.repeatCount = anim.repeatCount;
animCopy.repeatDuration = anim.repeatDuration;
animCopy.autoreverses = anim.autoreverses;
animCopy.fillMode = anim.fillMode;
// We want our new animations to be instantaneous, so set the duration to zero.
// Also set both the begin time and time offset to 0.
animCopy.duration = 0;
animCopy.beginTime = 0;
animCopy.timeOffset = 0;
[anims addObject:animCopy];
}
}
// Associate the gathered animations with each respective view
[mapTable setObject:anims forKey:view];
}
}];
// The completion block here gets run after the view controller transition animation completes (or fails)
[coordinator animateAlongsideTransition:nil completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
// Iterate over the mapTable's keys (views)
for (UIView *view in mapTable.keyEnumerator) {
// Get the modified animations for this view that we made when the interactive portion of the transition finished
NSArray *anims = [mapTable objectForKey:view];
// ... and add them back to the view's layer
for (CABasicAnimation *anim in anims) {
[view.layer addAnimation:anim forKey:anim.keyPath];
}
}
}];
}
}
@end
次に、ViewControllerのviewWillAppear:
メソッドでこのメソッドを呼び出すだけです(テストプロジェクトの場合は、ViewController
クラスになります)。
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self fixNavigationBarCorruption];
}
デバッグコンソール、Instruments and Revealを使用してこの問題をしばらく調査した後、次のことがわかりました。
1)シミュレータでは、プロファイル/自動化テンプレートを使用して次のスクリプトを追加すると、バグを毎回再現できます。
var target = UIATarget.localTarget();
var appWindow = target.frontMostApp().mainWindow();
appWindow.buttons()[0].tap();
target.delay(1);
target.flickFromTo({x:2, y: 100}, {x:160, y: 100});
2)実際のデバイス(iPhone 5s、iOS 7.1)では、このスクリプトによってバグが発生することはありません。フリック座標と遅延のさまざまなオプションを試しました。
3)UINavigationBarの構成:
_UINavigationBarBackground (doesn't seem to be related to the bug)
_UIBackdropView
_UIBackgropEffectView
UIView
UIImageView
UINavigationItemView
UILabel (visible in the bug)
_UINavigationBarBackIndicatorView (visible in the bug)
4)バグが発生すると、UILabelは半分透明になり、位置が正しくなくなりますが、UILabelの実際のプロパティは正しいです(アルファ:1、通常の状況と同じフレーム)。また、_UINavigationBarBackIndicatorViewの外観は実際のプロパティに対応していません。アルファは0ですが表示されます。
このことから、これはSimulatorのバグであり、コードから何かが間違っていることを検出することすらできないと結論付けます。
だから@ troop231-これはデバイスでも起こると100%確信していますか?
ビューコントローラを押すときにジェスチャレコグナイザを無効にし、ビューが表示されたときに有効にします。
UINavigationController
とUIViewController
をサブクラス化して、破損を防ぐことができます。
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
[super pushViewController:viewController animated:animated];
self.interactivePopGestureRecognizer.enabled = NO; // disable
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
self.navigationController.interactivePopGestureRecognizer.enabled = YES; // enable
}
MyNavigationController
とMyViewController
の代わりに、UINavigationController
とUIViewController
を使用する必要があります。UITableViewController
、UICollectionViewController
などのサブクラス化が必要です。これは、UINavigationController
メソッドとUIViewController
メソッドをスウィズリングすることで実行できます。メソッドのスウィズリングについて知りたい場合は、 ここ にアクセスしてください。
以下の例では、メソッドのスウィズリングを簡単にする JRSwizzle を使用しています。
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self jr_swizzleMethod:@selector(viewDidLoad)
withMethod:@selector(hack_viewDidLoad)
error:nil];
[self jr_swizzleMethod:@selector(pushViewController:animated:)
withMethod:@selector(hack_pushViewController:animated:)
error:nil];
});
}
- (void)hack_viewDidLoad
{
[self hack_viewDidLoad];
self.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)self;
}
- (void)hack_pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
[self hack_pushViewController:viewController animated:animated];
self.interactivePopGestureRecognizer.enabled = NO;
}
+ (void)load
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self jr_swizzleMethod:@selector(viewDidAppear:)
withMethod:@selector(hack_viewDidAppear:)
error:nil];
});
}
- (void)hack_viewDidAppear:(BOOL)animated
{
[self hack_viewDidAppear:animated];
self.navigationController.interactivePopGestureRecognizer.enabled = YES;
}
SwipeBackコードなしで自動的に実行します。
CocoaPods の場合、Podfile
に以下の行を追加するだけです。コードを書く必要はありません。 CocoaPodsは、SwipeBackをグローバルに自動的にインポートします。
pod 'SwipeBack'
ポッドをインストールすれば完了です!