53

I have a tab bar based app. There are navigation controllers in all 5 tabs with instances of custom view controller setup properly as root view controllers. This loads just fine. A couple of these view controllers contain table views. I want to show a modal view controller to the user when they select a row in the table view. The (relevant part of) didSelectRowAtIndexPath delegate method looks as follows:

SampleSelectorViewController *sampleVC = [[SampleSelectorViewController alloc] init];
[self presentViewController:sampleVC animated:YES completion:NULL];

The modal view controller appears BUT it appears after a very noticeable delay. At times it even requires the user to tap the row a second time. A few things that I have already verified are:

  • Table view's didSelectRowAtIndexPath method is called when the user taps the row
  • The didSelectRowAtIndexPath method does not contain any blocking calls. There are no network operations being performed and the modal view controller's setup does not involve any processing intensive task. The data it displays is static.
  • If I push the new view controller onto the navigation stack (everything else remaining exactly same), it behaves perfectly without any delays. It is only when presented modally that the delay is encountered.

Any ideas/suggestions?

Numan Tariq
  • 1,296
  • 1
  • 9
  • 18
  • Out of interest, is it equally slow with `animated:NO`? – pbasdf Oct 20 '14 at 15:30
  • It is. The animation seems to have no effect on this odd behaviour. – Numan Tariq Oct 20 '14 at 15:40
  • interesting. i have the same problem of a modal presentation being delayed (or having to tap the screen to make it appear). in my case, its not directly, but indirectly triggered by didSelectRowAtIndexPath. that calls a delegate methods, which calls a delegate method, which presents modally. hmm.. – Alex Bollbach Aug 10 '17 at 05:29
  • sounds very similar to the scenario I had. Would you mind sharing how you solved the problem? I have never seen the issue before or since and the scenario for this no longer exists in the project so can't be much help myself – Numan Tariq Aug 18 '17 at 09:22
  • 1
    Same issue here on iOS 11, Xcode 9, no luck yet.. – Stas Oct 31 '17 at 17:44
  • old thread, but try to use modalPresentationStyle = .overCurrentContext. It worked for me – CZ54 Nov 07 '18 at 18:11
  • modalPresentationStyle = .overCurrentContext did not work for me. – thecloud_of_unknowing Feb 27 '19 at 22:47

10 Answers10

76

It seems calling presentViewController:animated:completion from within tableView:didSelectRowAtIndexPath: is problematic. It's difficult to find anything that stands out when using the Time Profiler in Instruments, also. Sometimes my modal view comes up in less than a second and other times it takes 4s or even 9s.

I think it's related to the underlying UIPresentationController and layout, which is one of the few things I see when selecting the region of time between tapping on a row and seeing the modal presentation in the Time Profiler.

A Radar exists describing this issue: http://openradar.appspot.com/19563577

The workaround is simple but unsatisfying: delay the presentation slightly to avoid whatever contentious behavior is causing the slowdown.

dispatch_async(dispatch_get_main_queue(), ^{
   [self presentViewController:nav animated:YES completion:nil];
});
azsromej
  • 1,619
  • 13
  • 15
  • 1
    Thanks for this. Your fix worked for me, though agreed that it is a little unsatisfying. – Rogare Feb 07 '15 at 02:01
  • 1
    Thanks azsromej for your reply. I am a little stuck on some other things. I will try to verify the fix asap and accept your answer if it works but it might be a little while before I can do that. – Numan Tariq Feb 09 '15 at 12:21
  • This fixed it for me http://stackoverflow.com/questions/29830698/uisearchcontroller-wont-dismiss-searchbar-and-overlap-for-ios-8-swift – DogCoffee Nov 21 '15 at 11:26
  • wrapping presentation code in dispatch_async worked for me, but this is really some crazy stuff is going on there – east Dec 10 '15 at 14:32
  • Like east said, wrapping the presentation code in a async dispatch call on the main thread solved the issue. Huge thanks for sniffing this out azsromej! :) – D6mi Mar 01 '16 at 13:54
  • My speculation as to the reason why this works is because apple is calling the tableView:didSelectRowAtIndexPath: method from a background thread. The dispatch_async moves the call back to the main thread (the UI thread). I would argue that it is a bug on Apple's part to call this method from a background thread, but perhaps there is a reason why they needed to. – aepryus Aug 15 '16 at 18:02
  • You are genius! Saved my life! ..... (y) , It was very strange that I was trying to dismiss UINavigation based UIViewController from a tabbarcontroller , and the dismissal was delayed by approx by 10 sec when I was handling dismisal complition block. Its was resolved by approaching your way! (y) – Prasanna Jul 27 '17 at 14:30
  • This wrapping seems to improve performance a little bit but not really the way I want. – Stas Oct 31 '17 at 17:45
  • In my case, reason behind delay was because of UITableViewCellSelectionStyleNone. I needed for table view cell to have selection style none, so this workaround worked for me. – Sihad Begovic Nov 11 '18 at 14:57
11

If you call present(:animated:completion:) in tableView(:didSelectRowAt:), selectionStyle == .none for selected tableview cell and you’ve got this strange behavior then try to call tableView.deselectRow(at:animated:) before any operations in tableView(_:didSelectRowAt:).

Did it help?

Artem Kirillov
  • 1,132
  • 10
  • 25
7

Swift 4: you can use as below.

DispatchQueue.main.async {
            let popUpVc = Utilities.viewController(name: "TwoBtnPopUpViewController", onStoryboard: "Login") as? TwoBtnPopUpViewController
            self.present(popUpVc!, animated: true, completion: nil)
        }

It works for me.

Arshad Shaik
  • 1,095
  • 12
  • 18
6

I guess you also set the cell's selectionStyle to UITableViewCellSelectionStyleNone. I change to UITableViewCellSelectionStyleDefault and it work perfect.

Neeeeeil
  • 61
  • 1
  • 3
3

You should display it modally from your root vc (e.g: customTabBarRootViewController). save a reference, and use the reference controller to display it.

Gilad
  • 43
  • 9
  • When you say root vc, do you mean the root vc in my window which would be my tab bar controller in this case? Also, I would be very interested in knowing the cause for it if possible – Numan Tariq Oct 21 '14 at 09:54
  • i don't know the cause i only know it worked perfectly for me without delay on iPhone 4 running iOS7 compiled by xCode6.0.1 – Gilad Oct 21 '14 at 11:08
  • yes i mean the tab bar controller , or the controller that hold it – Gilad Oct 21 '14 at 11:08
  • by the way if there is "heavy code" on the displayed view controller try wrap it with dispatch dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ //your heavy code here =) }); – Gilad Oct 21 '14 at 11:09
  • I will give it a try and update here. There is no heavy code in the new controller. It is a simple table view with 5 static rows for that the user can choose from. – Numan Tariq Oct 21 '14 at 13:42
  • how long is your dealy ? – Gilad Oct 21 '14 at 13:54
  • any success? I would like you to post the code of the initiate view controller – Gilad Nov 01 '14 at 02:11
  • My apologies for the late response. I ended up changing the controller hierarchy/interactions due to change in UI/UX. However, the problem was not caused because of of any heavy code in the modal controller. – Numan Tariq Dec 11 '14 at 17:02
3

I have also had this strange delay when presenting from tableView:didSelectRowAtIndexPath: looks like an Apple bug.

This solution seems to work well though.

CFRunLoopWakeUp(CFRunLoopGetCurrent()); // Fixes a bug where the main thread may be asleep, especially when using UITableViewCellSelectionStyleNone
trapper
  • 11,716
  • 7
  • 38
  • 82
1

Solution in Swift 3

In the SampleSelectorViewController(the presented view controller) use the below code

DispatchQueue.global(qos: .background).async {

// Write your code

}
Krishna Vivekananda
  • 257
  • 1
  • 5
  • 11
1

The common problem with this behaviour is as follows:

one sets selectionStyle = .none for a cell in the tableView (it seems that it doesn't depend on UITableViewController subclassing as was written at http://openradar.appspot.com/19563577) and uses at the delegate method

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)

animating deselect

tableView.deselectRow(at: indexPath, animated: true)

which implies animation for non-animating cell.

In this case the subsequent view controller presentation has a delay.

There are some workaround solutions including dispatch_async on main thread, but it is better not to call deselectRow even without animation on unselectable cells in your code.

malex
  • 9,874
  • 3
  • 56
  • 77
  • I no longer have access to that code base so I am unable to test your answer but this sounds like the most promising of all the answers on this thread! – Numan Tariq Mar 05 '19 at 09:24
1

As per @Y.Bonafons comment, In Swift, You can try like this, (for Swift 4.x & 5.0)

DispatchQueue.main.async {

                self.showAction() //Show what you need to present
            }
iHarshil
  • 739
  • 10
  • 22
0

Try this For swift version 5.2 you can use below code:

DispatchQueue.main.async(execute:{self.present(nav, animated: true)})
Martin Brisiak
  • 3,872
  • 12
  • 37
  • 51