48

I have a simple table view where I handle the select action on the table view. This action follows a segue.

If the segue is a push segue, the next view shows immediately. If the segue is a modal segue, the next view either:

  • takes 6 seconds or so to display
  • shows immediately if I tap again (second tap)

I tried looking around for some ideas, but none seem applicable to my situation. In particular:

  • I'm performing the segue on the main UI thread
  • My view is very simple (so there's no issue in viewDidLoad). Plus the fact that it shows up near instantaneous when the segue is push indicates that there is no problem loading the target view
  • I tried passing nil to the sender; same effect.

Does anyone have any ideas on this?

tng
  • 4,286
  • 5
  • 21
  • 30
  • what code do you have in the next view? For example, if you are making a synchronous network request in the viewDidLoad that could cause the slow down. – Alex Popov Feb 13 '15 at 21:48
  • Are you testing it on the simulator on in a device? The results may vary a lot between both. – jherran Feb 13 '15 at 21:48
  • How are you presenting your segue? performseguewithidentifier? If so have you tried `dispatch_async(dispatch_get_main_queue(), {performSegueWithIdentifier(mysegueIdentifier, self)})` – boidkan Feb 13 '15 at 21:51
  • @jherran i'm testing on both emulate and device. On emulator the delay is significant, on device, its about 1-2 seconds. As I mentioned, if I only change the segue from "modal" to "push", it's instant. So the problem is with "modal" segues... – tng Feb 14 '15 at 05:47

8 Answers8

94

Trust me and try this. I have run into this problem a few times.

In Swift 2:

dispatch_async(dispatch_get_main_queue(),{
    self.performSegue(withIdentifier:mysegueIdentifier,sender: self)
})

or for Swift 3:

DispatchQueue.main.async {
    self.performSegue(withIdentifier: mysegueIdentifier,sender: self)
}

As discussed here and here.

Community
  • 1
  • 1
boidkan
  • 4,691
  • 5
  • 29
  • 43
  • 1
    I tried that - it didn't work for me, as i mentioned, i verified that I was on the main thread already, so the dispatch doesn't do anything... – tng Feb 14 '15 at 05:46
  • Everything you are describing sounds like you are not on the main thread. If you do `dispatch_sync(dispatch_get_main_queue(),{})` does it the app freeze? – boidkan Feb 16 '15 at 16:09
  • Can you post some code? Are you doing modal with display over current context no animation? – boidkan Feb 16 '15 at 16:10
  • I need to clean up my code a bit before I can paste it (it has some client information in it right now). I'm curious why you think I'm not on the main thread? In XCode if I put a break point where I call `performSegueWithIdentifier` it's happening on thread #1. When I put in `dispatch_async` it does not freeze, but `dispatch_sync` does freeze. – tng Feb 16 '15 at 18:39
  • 2
    Because it is happening in a modal segue taking about 6 seconds but responds immediately to a second tap. Every time I have run into this issues those same things were involved and using the code in the answer above solved the problem. Very surprised it's nor working for you. Try fidgeting by turning animation on/off and not displaying over current context perhaps? – boidkan Feb 16 '15 at 18:58
  • 3
    So I ended up re-writing a bunch of code, and using `dispatch_async` appeared to solve the problem for me. I can't explain why, as I'm sure I'm on the UI thread, but so be it. – tng Feb 18 '15 at 21:35
  • Can you please tell me what you did? I run into the same problem, the system prints true for isMainThread but dispatch_sync does freeze up my app too. – Razvan Soneriu Mar 07 '15 at 13:46
  • This worked for me. I was doing modal when table cell was clicked and I had a 3-8 second delay. I've done lots of apps and never run into this before; this app didn't do any custom dispatching so everything should be on the correct thread. – Jason Leach Apr 15 '15 at 16:43
  • I can confirm that this works. Had exactly the same issue. A modal segue, would lead to a long delay, and after a second tap it would open immediately. After dispatching on main queue, everything worked fine. – swennemen Jul 24 '15 at 09:31
  • Works for me, and I was already on the main thread. Everyone should report this as a bug to Apple. – John Scalo Jun 23 '16 at 16:54
  • 1
    I had this issue on only one screen of the app. I removed the segue from the cell to the modal screen and added a segue at the controller level triggering it on `didSelectRowAtIndexPath`. I verified the code was on the main thread with `assert(NSThread.isMainThread())`. Still the issue persisted. I cannot explain why, but postponing the "performSegue" call to the next run-loop solves this issue. – Eneko Alonso Jul 18 '16 at 18:28
  • @xlsmearlx Interesting, have you tested this yourself? – boidkan Aug 28 '17 at 16:37
  • Great solution. This bug still exists in Xcode 9 and it was making me crazy during the whole day. Thanks for the solution – Tigran Iskandaryan Apr 14 '18 at 20:43
7

It seems (to me...) that this problem happens only when the cell selectionType is not .none.

You may change it to any other option (at the storyboard Attribute inspector, set the Selection field) and it will work fine (working for me...). The cons is that it messing up the cell UI.

You can call the segue in DispatchQueue.main.async{} block at the didSelect delegate function of UITableViewDelegate as people mention before.

I used the first solution and added at the cell itself -

override func setSelected(_ selected: Bool, animated: Bool) {
    super.setSelected(false, animated: false)
}

This will make the cell 'highlight' at the tap, but it will return to its usual UI immediately and it fine for me...

Yedidya Reiss
  • 5,316
  • 2
  • 17
  • 19
  • 1
    You are right, I had the same problem, my cell selectionType was .none and the delay happened, so I Tried calling the perform seguen "DispatchQueue.main.async" and it worked, and then I tried your solution and now the segue appear instantly. Something is wrong when the selectionType is .none – xlsmearlx Aug 25 '17 at 22:27
4

There seem to be various situations when performing a segue will not work properly. For example, if you call performSegue from the action handler of an unwind segue, you will run into various issues, even though you are on the main thread. On my current project, I am calling performSegue from the didSelectRowAt method of a table view. This is one of the most basic segues there is and of course I am on the main thread, yet I was seeing the exact symptoms that the OP described.

I do not know why this happens in some cases and not others, but I have found that deferring the performSegue call using async fixes any potential issues. This used to seem like a hack and made me nervous, but at this point I have several mature projects using this approach and it now seems like the "right" way to do a manual segue.

Here is the Swift 3 version of the code (see the other posts for Swift 2 and Obj-C versions):

DispatchQueue.main.async {
    self.performSegue(withIdentifier: "theIdentifier", sender: theSender)
}
phatmann
  • 18,161
  • 7
  • 61
  • 51
2

The accepted solution worked for me. Code updated for Swift 2.0 below:

dispatch_async(dispatch_get_main_queue(),{
     self.performSegueWithIdentifier(mysegueIdentifier, sender:self)
})
1

Hope help this yo can create programatically modal transition like this in Swift:

       let myStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
        let modalViewController = myStoryboard.instantiateViewControllerWithIdentifier("myModalViewController") as! myModalViewController
        modalViewController.modalTransitionStyle = UIModalTransitionStyle.CoverVertical
        let navController = UINavigationController(rootViewController: accountManager)
        dispatch_async(dispatch_get_main_queue(),{
            self.presentViewController(navController, animated: true, completion: nil)
        })
Dasoga
  • 5,489
  • 4
  • 33
  • 40
1

For developers organizing their code through subclassing, I've come to quite simple a solution I'd like to share (Swift 4):

import UIKit

class ABCViewController: UIViewController {

  // ... Other useful methods like overriding deinit and didReceiveMemoryWarning

  // Performs a segue on the main thread to ensure it will 
  // transition once a UI-slot is available
  func performSegueOnMainThread(with identifier: String, sender: Any?) {
    DispatchQueue.main.async {
      self.performSegue(with: identifier, sender: sender)
    }
  }
}

Then, simply call it from your implementation:

myViewController.performSegueOnMainThread(with: "ShowDetailsSegue", sender: self)
Hans Knöchel
  • 11,422
  • 8
  • 28
  • 49
0

For me it was the that I had too many "Clear" colored views in my next view, so when the segue was animated, it seemed like it was delaying because of this. I went through the view controller's UI hierarchy looking for clear colors and replacing them with solid black or white, and making sure the alpha is 1 (if it didn't need to be otherwise). My delay is now gone and my modal presentation is smooth.

Joshua Hart
  • 772
  • 1
  • 21
  • 31
-1

I tried fixing this multiple ways including moving it to the main thread above. This worked for me:

In storyboard, select the table view in question, (select it in document outline to make sure you've got the right thing. Then in attributes inspector you will be able to see the attributes for the table view as well as the containing scrollview underneath it (all table views are based on scroll view). There is this stupid little box there called "delays content touches". Uncheck it. There is also one "allows cancellable touches" which I'll imagine you want to make sure is unchecked as well, as I think double touches were messing mine up.

Bec Martin
  • 23
  • 1
  • 1
  • 7
  • If you do this, you will make scrolling your table view harder to do since swipes within a cell might not be recognized as such. – phatmann Oct 02 '16 at 10:24