iOS segue freeze for many seconds before showing new view

4.9k views Asked by At

In the main view of my app I have a table view and two prototype cells. I have configured the segues for each cell using the storyboard. In the view controller I override prepareForSegue to pass information on the selected cell to the destination view.

The destination view isn't particularly complex and certainly doesn't require any heavy processing to load.

THE PROBLEM

When I tap on a cell in the main controller for the very first time, the destination view appears after a long delay, from 5 to 40 seconds.

EDIT #2: subsequent taps are generally faster

Note that:

  • If I tap on the same cell again before the destination view has appeared, this triggers the destination view to appear immediately.
  • As above, but tapping on a different cell results in the view appearing immediately but with the data from the first cell.
  • As above, but tapping on a different control (with no associated segues) triggers the destination view to appear immediately.
  • Subsequent "taps" generally manifest less delay.
  • The Time Profiler - for what I can see - shows that absolutely nothing is happening during those many seconds of delay.
  • I have tried different type of segues, but it made no difference
  • A few println's show that the following sequence of events occurs:

    • in the main view, prepareForSegue is executed (no delays)
    • then the destination viewDidLoad is executed (no delays)
    • ... long delay ...
    • the collection and table views in the destination controller start invoking the data source related methods to fetch the data from the controller.
    • the view finally appears (with an unwanted animation, BTW, but that's a different problem)

From what I have read on this topic, I suspect the problem is potentially related to some of the above operations happening in a background thread.

Any idea what I might be doing wrong?

EDIT #1: added some code

In the main view controller the segues have been link using the story board (CTRL-drag the two prototype cells into the destination view).

The code looks a bit like below:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
{
    var assetIndex = assetsTable.indexPathForSelectedRow()?.row

    println("prepare for segue - start: \(assets[assetIds[assetIndex!]]!.Name)")

    if let destination = segue.destinationViewController as? AssetThingsListViewController
    {
        destination.bundlesRepository = bundlesRepository!
        destination.asset = assets[assetIds[assetIndex!]]
    }

    println("prepare for segue - end")
}

EDIT #3 I have made a sample project available on BitBucket

4

There are 4 answers

5
pteofil On BEST ANSWER

I checked your project. And while I also couldn't find anything else, I also suspect that it's a problem with threading.

I managed to fix the problem by implementing a delegate for the tableview and present the new controller in code:

func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {

    let destination = storyboard?.instantiateViewControllerWithIdentifier("BuilderToysListViewController") as! BuilderToysListViewController
    destination.botsRepository = botsRepository!
    destination.builder = builders[builderIds[indexPath.row]]

    dispatch_async(dispatch_get_main_queue(), { () -> Void in
        self.presentViewController(destination, animated: true) { () -> Void in

        }
    })

}

Notice you have the set the view controller storyboard id: BuilderToysListViewController and also set the tableview delegate. Don't forget to remove the segues.

At the end to dissmis the view in the new view controller use this code:

@IBAction func backButton(sender: AnyObject)
{
    dismissViewControllerAnimated(true, completion: { () -> Void in

    })
//        performSegueWithIdentifier("segueToysByBuilder", sender: nil)        

}

This would allow you to properly close the view instead of wrongly creating a new one.

1
Duncan C On

Hard to say unless you post your code that is responding to the tap on the cell and presenting the new view controller.

One common cause of long delays in UI changes (or having the UI change never happen) is trying to make UI changes from a background thread. Is it possible that your code that is invoking the segue is running on a different thread? You can tell this easily by setting a breakpoint on that code and observing the thread number when it breaks. If the thread number is 0 you're running on the main thread. If it's some other thread number, that's your problem.

0
PhilModin On

I was able to solve this for iOS 13 with Swift 5 by implementing the following:

DispatchQueue.main.async {
     self.performSegue(withIdentifier: "YOURSEGUEID", sender: nil)
}

inside of

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {}

final result

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    DispatchQueue.main.async {
         self.performSegue(withIdentifier: "YOURSEGUEID", sender: nil)
    }
}
0
Naishta On

Also worth moving the code if any from viewWillAppear to viewDidAppear