presentViewController:animated:YES view will not appear until user taps again

I’ve encountered the same issue today. I dug into the topic and it seems that it’s related to the main runloop being asleep.

Actually it’s a very subtle bug, because if you have the slightest feedback animation, timers, etc. in your code this issue won’t surface because the runloop will be kept alive by these sources. I’ve found the issue by using a UITableViewCell which had its selectionStyle set to UITableViewCellSelectionStyleNone, so that no selection animation triggered the runloop after the row selection handler ran.

To fix it (until Apple does something) you can trigger the main runloop by several means:

The least intrusive solution is to call CFRunLoopWakeUp:

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

Or you can enqueue an empty block to the main queue:

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

It’s funny, but if you shake the device, it’ll also trigger the main loop (it has to process the motion events). Same thing with taps, but that’s included in the original question 🙂 Also, if the system updates the status bar (e.g. the clock updates, the WiFi signal strength changes etc.) that’ll also wake up the main loop and present the view controller.

For anyone interested I wrote a minimal demonstration project of the issue to verify the runloop hypothesis: https://github.com/tzahola/present-bug

I’ve also reported the bug to Apple.

Leave a Comment