presentViewController:animated:YESビューは、ユーザーがもう一度タップするまで表示されません


93

で奇妙な動作が発生していpresentViewController:animated:completionます。私が作っているのは、本質的に推測ゲームです。

私が持っているUIViewController(frequencyViewController)を含むUITableView(frequencyTableViewを)。ユーザーが正解を含むquestionTableViewの行をタップすると、ビュー(correctViewController)がインスタンス化され、そのビューがモーダルビューとして画面の下から上にスライドするはずです。これは、ユーザーに正しい答えがあることを知らせ、次の質問に備えて、その背後にあるfrequencyViewControllerをリセットします。ボタンを押すと、次の質問を表示するためにcorrectViewControllerが閉じます。

これはすべて正しく機能し、正しいビューコントローラのビューはがある限り即座に表示されpresentViewController:animated:completionますanimated:NO

設定するとanimated:YES、correctViewControllerが初期化され、を呼び出しますviewDidLoad。しかしviewWillAppearviewDidAppear、およびから完了ブロックがpresentViewController:animated:completion呼び出されません。アプリはそこに座って、2回目のタップを行うまでfrequencyViewControllerを表示しています。これで、viewWillAppear、viewDidAppear、および完了ブロックが呼び出されます。

私はもう少し調査しました、そしてそれがそれを続けることを引き起こすもう一つのタップではありません。iPhoneを傾けたり振ったりすると、viewWillLoadなどがトリガーされる可能性があります。他のユーザー入力を待ってから進行するようです。これは、実際のiPhoneとシミュレーターで発生します。これは、shakeコマンドをシミュレーターに送信して確認しました。

私はこれについて何をすべきかについて本当に途方に暮れています...私は誰もが提供できるあらゆる助けに本当に感謝します。

ありがとう

これが私のコードです。とてもシンプルです...

これは、questionTableViewのデリゲートとして機能するquestionViewControllerのコードです。

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{

    if (indexPath.row != [self.frequencyModel currentFrequencyIndex])
    {
        // If guess was wrong, then mark the selection as incorrect
        NSLog(@"Incorrect Guess: %@", [self.frequencyModel frequencyLabelAtIndex:(int)indexPath.row]);
        UITableViewCell *cell = [self.frequencyTableView cellForRowAtIndexPath:indexPath];
        [cell setBackgroundColor:[UIColor colorWithRed:240/255.0f green:110/255.0f blue:103/255.0f alpha:1.0f]];            
    }
    else
    {
        // If guess was correct, show correct view
        NSLog(@"Correct Guess: %@", [self.frequencyModel frequencyLabelAtIndex:(int)indexPath.row]);
        self.correctViewController = [[HFBCorrectViewController alloc] init];
        self.correctViewController.delegate = self;
        [self presentViewController:self.correctViewController animated:YES completion:^(void){
            NSLog(@"Completed Presenting correctViewController");
            [self setUpViewForNextQuestion];
        }];
    }
}

これは正しいViewControllerの全体です

@implementation HFBCorrectViewController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self)
    {
        // Custom initialization
        NSLog(@"[HFBCorrectViewController initWithNibName:bundle:]");
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.
    NSLog(@"[HFBCorrectViewController viewDidLoad]");
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    NSLog(@"[HFBCorrectViewController viewDidAppear]");
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (IBAction)close:(id)sender
{
    NSLog(@"[HFBCorrectViewController close:sender:]");
    [self.delegate didDismissCorrectViewController];
}


@end

編集:

私は以前にこの質問を見つけました:UITableViewとpresentViewControllerは2クリックで表示されます

そして、didSelectRowコードをこれに変更すると、アニメーションで非常にうまく機能します...しかし、面倒で、そもそもなぜ機能しないのか理解できません。だから私はそれを答えとして数えません...

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{

    if (indexPath.row != [self.frequencyModel currentFrequencyIndex])
    {
        // If guess was wrong, then mark the selection as incorrect
        NSLog(@"Incorrect Guess: %@", [self.frequencyModel frequencyLabelAtIndex:(int)indexPath.row]);
        UITableViewCell *cell = [self.frequencyTableView cellForRowAtIndexPath:indexPath];
        [cell setBackgroundColor:[UIColor colorWithRed:240/255.0f green:110/255.0f blue:103/255.0f alpha:1.0f]];
        // [cell setAccessoryType:(UITableViewCellAccessoryType)]

    }
    else
    {
        // If guess was correct, show correct view
        NSLog(@"Correct Guess: %@", [self.frequencyModel frequencyLabelAtIndex:(int)indexPath.row]);

        ////////////////////////////
        // BELOW HERE ARE THE CHANGES
        [self performSelector:@selector(showCorrectViewController:) withObject:nil afterDelay:0];
    }
}

-(void)showCorrectViewController:(id)sender
{
    self.correctViewController = [[HFBCorrectViewController alloc] init];
    self.correctViewController.delegate = self;
    self.correctViewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
    [self presentViewController:self.correctViewController animated:YES completion:^(void){
        NSLog(@"Completed Presenting correctViewController");
        [self setUpViewForNextQuestion];
    }];
}

1
私は同じ問題を抱えています。実行に長い時間がかかるメソッドを呼び出さないことをNSLogsで確認しました。presentViewController:開始が大幅に遅れてトリガーされるはずのアニメーションにすぎないようです。これはiOS 7のバグのようで、Apple Dev Forumsでも議論されています。
Theo

私は同じ問題を抱えています。iOS7のバグのようです-iOS6では発生しません。また、私の場合は、表示しているビューコントローラーを開いてから初めて発生します。@ Theo、Apple Devフォーラムへのリンクを提供できますか?
AX

回答:


157

今日、同じ問題に遭遇しました。私はトピックを掘り下げました、そしてそれは眠っている主なrunloopに関連しているようです。

実際には非常に微妙なバグです。コードにわずかなフィードバックアニメーションやタイマーなどがある場合、これらのソースによって実行ループが維持されるため、この問題は表面化しません。がに設定されているUITableViewCellを使用して問題を見つけたため、行選択ハンドラーの実行後に選択アニメーションが実行ループをトリガーしませんでした。selectionStyleUITableViewCellSelectionStyleNone

これを修正するには(Appleが何かするまで)、いくつかの方法でメインの実行ループをトリガーできます。

最も煩わしくない解決策は、以下を呼び出すことCFRunLoopWakeUpです。

[self presentViewController:vc animated:YES completion:nil];
CFRunLoopWakeUp(CFRunLoopGetCurrent());

または、空のブロックをメインキューにエンキューすることもできます。

[self presentViewController:vc animated:YES completion:nil];
dispatch_async(dispatch_get_main_queue(), ^{});

おかしいですが、デバイスを振ると、メインループもトリガーされます(モーションイベントを処理する必要があります)。タップについても同じですが、それは元の質問に含まれています:)また、システムがステータスバーを更新した場合(たとえば、クロックの更新、WiFi信号強度の変更など)、メインループが起動し、ビューが表示されます。コントローラ。

興味がある人のために、私は問題の最小限のデモンストレーションプロジェクトを書いて、runloop仮説を検証しました:https : //github.com/tzahola/present-bug

バグをAppleにも報告しました。


Tamásに感謝します。それは素晴らしい情報です。実行ループを開始するためにタップする必要がある場合や、待機するだけで機能する理由を説明しています。バグが修正されるまで他に何もできるように見えないため、これを解決策としてマークしました。
HalfNormalled 2015年

アップルは素晴らしい穴を掘り、私はそれに落ちて、ひどく傷ついたと感じます。
OpenThread

7
OMG、これはとても陽気です!ところで、このバグはまだ存在しています。
igrrik 2016

1
私はまったく同じ問題が本当に迷惑だった、ありがたいことにこれはそれを修正しました。
マーク

1
この問題がiOS 13でも引き続き発生することを確認
Eric Horacek

69

:このチェックアウトhttps://devforums.apple.com/thread/201431を あなたはそれをすべてを読みたくない場合は- (私を含め)何人かの人々のためのソリューションを作ることだったpresentViewControllerメインスレッド上で明示的に呼び出しを:

Swift 4.2:

DispatchQueue.main.async { 
    self.present(myVC, animated: true, completion: nil)
}

Objective-C:

dispatch_async(dispatch_get_main_queue(), ^{
    [self presentViewController:myVC animated:YES completion:nil];
});

おそらくiOS7がのスレッドをめちゃくちゃにしていdidSelectRowAtIndexPathます。


うわー-これは私のために働いた。私も遅れを感じていました。なぜこれが必要なのか理解できません。これを投稿してくれてありがとう。
drudru 2014年

4
この問題についてAppleにレーダーを提出しました:rdar:// 19563577 openradar.appspot.com/19563577
mluisbrown

1
iOS 8のIMとこれは私のためにそれを修正しません-それは常にメインスレッド上にあることを報告していますが、それでも説明されている問題が表示されます。
Robert

7

次のコードを使用して、Swift 3.0でバイパスしました。

DispatchQueue.main.async { 
    self.present(UIViewController(), animated: true, completion: nil)
}

1
問題を解決します。presentクロージャーコールバックで呼び出していたため、同じ問題が発生しました。
Jeremy Piednoel 2017

2

[viewController view]提示されているビューコントローラーを呼び出すと、私にとってはうまくいきました。


これはiOS 8ではうまくいきました。dispatch_asyncよりも優れていると思います。
Robert

0

[self setUpViewForNextQuestion];ができるのか知りたいです。

[self.correctViewController.view setNeedsDisplay];完了ブロックの最後に呼び出してみてくださいpresentViewController


現在、setUpViewForNextQuestionはテーブルビューのreloadDataを呼び出すだけです(誤った推測によって背景色が赤に設定されていた場合、すべての背景色を白に戻します)。しかし、そこで何か変更を加えても違いはありますか?問題は、useが再度タップするまでcorrectViewControllerが表示されないため、この完了ブロックは2回目のタップが終了するまで呼び出されません。
HalfNormalled 2014年

私はあなたの提案を試しましたが、何の違いもありませんでした。言ったように、ビューは表示されないので、2回目のタップまたはシェイクジェスチャーが完了するまで、完了ブロックは呼び出されません。
HalfNormalled 2014年

うーん。まず、ブレークポイントを使用して、2回目のタップまでプレゼンテーションコードが呼び出されないことを確認します。(最初のタップで呼び出されるのではなく、しばらくしてから画面に描画されます)。後者に該当する場合は、プレゼンテーションをメインスレッドで強制的に実行してみてください。「performSelectorWithDelay:0」を使用すると、アプリは実行ループの次の反復まで待機してから実行します。スレッドに関連している可能性があります。
Nick

ありがとう、ニック。ブレークポイントを使用してどのように確認しますか?現時点では、NSLogを使用して、各メソッドが呼び出されていることを確認しています。タップ1:これは私が今持っているCorrect Guess: 1 kHz 18:04:57.173 Frequency[641:60b] [HFBCorrectViewController initWithNibName:bundle:] 18:04:57.177 Frequency[641:60b] [HFBCorrectViewController viewDidLoad] タップ2:今すぐ何もするまで起こりません18:05:00.515 Frequency[641:60b] [HFBCorrectViewController viewDidAppear] 18:05:00.516 Frequency[641:60b] Completed Presenting correctViewController 18:05:00.517 Frequency[641:60b] [HFBFrequencyViewController setUpViewForNextQuestion]
HalfNormalled

申し訳ありませんが、もっと明確にすべきでした。ブレークポイントの使い方を理解しています。NSLogと同じストーリーが表示されます。正しいビューコントローラのviewDidLoadとviewDidAppearにブレークポイントを設定します。それはviewDidLoadで中断し、次に続行すると、座って次のタップを待機してから、viewDidAppearで中断します。時々それはタップを必要としないようで、それは時間ベースです、私が他に何が呼び出されているかを見て物事をステップスルーした時までに、それはviewDidLoadの呼び出しに回っています。
HalfNormalled 2014年

0

問題を解決するUIViewControllerのメソッドスウィズリングを使用して拡張機能(カテゴリ)を作成しました。実装のヒント(swift / objective-c)を提供してくれたAXとNSHipsterに感謝します。

迅速

extension UIViewController {

 override public class func initialize() {
    struct DispatchToken {
      static var token: dispatch_once_t = 0
    }
    if self != UIViewController.self {
      return
    }
    dispatch_once(&DispatchToken.token) {
      let originalSelector = Selector("presentViewController:animated:completion:")
      let swizzledSelector = Selector("wrappedPresentViewController:animated:completion:")

      let originalMethod = class_getInstanceMethod(self, originalSelector)
      let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)

      let didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))

      if didAddMethod {
        class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
      }
      else {
        method_exchangeImplementations(originalMethod, swizzledMethod)
      }
    }
  }

  func wrappedPresentViewController(viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?) {
    dispatch_async(dispatch_get_main_queue()) {
      self.wrappedPresentViewController(viewControllerToPresent, animated: flag, completion: completion)
    }
  }
}  

Objective-C

#import <objc/runtime.h>

@implementation UIViewController (Swizzling)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];

        SEL originalSelector = @selector(presentViewController:animated:completion:);
        SEL swizzledSelector = @selector(wrappedPresentViewController:animated:completion:);

        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

        BOOL didAddMethod =
            class_addMethod(class,
                originalSelector,
                method_getImplementation(swizzledMethod),
                method_getTypeEncoding(swizzledMethod));

        if (didAddMethod) {
            class_replaceMethod(class,
                swizzledSelector,
                method_getImplementation(originalMethod),
                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

- (void)wrappedPresentViewController:(UIViewController *)viewControllerToPresent 
                             animated:(BOOL)flag 
                           completion:(void (^ __nullable)(void))completion {
    dispatch_async(dispatch_get_main_queue(),^{
        [self wrappedPresentViewController:viewControllerToPresent
                                  animated:flag 
                                completion:completion];
    });

}

@end

0

ストーリーボードのセルにSelection = noneがあるかどうかを確認します

もしそうなら、それを青または灰色に変更し、それはうまくいくはずです


0

XCode Vesion:9.4.1、Swift 4.1

私の場合、セルをタップして別のビューに移動すると、これが発生します。私はより深くデバッグしviewDidAppearますが、次のコードが含まれているために内部で発生しているようです

if let indexPath = tableView.indexPathForSelectedRow {
   tableView.deselectRow(at: indexPath, animated: true)
}

その後、上記のコードセグメントを内部に追加しprepare(for segue: UIStoryboardSegue, sender: Any?)、完璧に動作しました。

私の経験では、私の解決策は、2番目のビューから再び戻ったときに、テーブルビューの新しい変更(たとえば、テーブルのリロード、選択したセルの選択解除など)を行う場合はviewDidAppear、上記のtableView.deselectRowコードの代わりにデリゲートを使用することです2番目のビューコントローラーを移動する前のセグメント

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.