In MainViewController, in viewDidLoad() I'm calling a function which in turn tests if Auth.auth().currentUser == nil; If the condition is met then the statement to execute presents another view controller.
In the if statement, why does the statement to execute need to be preceded by DispatchQueue.main.async (if I don't write DispatchQueue.main.async then the view controller doesn't present and it's just stuck on MainViewController).
Because at the time viewDidLoad is called your view controller has not yet been added to the view hierarchy. A view controller that is not part of the view hierarchy can't present another view controller. You should get a log message in the console saying something similar to that when you try to present the other view controller without async dispatch.
Putting the call in the DispatchQueue.main.async causes the presentation to be delayed until the next runloop cycle which happens to be enough that your view controller has been added to the view hierarchy once it gets called.
A better solution would be to put your current user check in a more appropriate place, possibly viewDidAppear.
Dispatch.main.async is used for reason being that all the UI related have to be performed on the mainQueue. Since UIViewController presentation is a UI task, hence performed on mainQueue
Also, to answer why it's not presenting when Dispatch.main is not used is perhaps you are doing it on a thread which isn't main.
I have a few simple Segues --> Show(e.g. Push)
On some Segues the first time calling the Segue I get a delay (about 2 Seconds).
There is no Code being Executed on shouldPerformSegue and prepare.
ViewDidLoad of the next View gets called after the Delay.
When the delay occurs I often get this warning:
objc[3993]: Class PLBuildVersion is implemented in both...
There seems to be no solution to this though --> Link
I have also tried calling the Segue Manually like this:
DispatchQueue.main.async {
self.performSegue(withIdentifier: "theIdentifier", sender: theSender)
}
Is there any way to make this more responsive?
Thanks!
Try to embed view controller in a navigation controller
In my XCode project, which implements MultipeerConnectivity framework, I have a segue between two ViewControllers which is triggered programatically after MPCHandler successfully establishes connection with another peer. After segue is performed, the destination VC appears being completely blank (all white). However, if I try to access the destination VC with help of a button (with segue dragged directly from the button to the destination VC), it appears as it should.
Here is the line of code, which performs the segue:
performSegueWithIdentifier("seg_wait", sender: self)
So, yeah, I know how to perform segues and, yes, I made sure identifier is unique and is the same as in storyboard. I had tons of segues in many of my projects, and that's the first time I've faced a problem like this. Did anyone face a problem like this? Any ideas on how to solve it?
UI updates always have to be done on the main thread ("the law"). So try to wrap the call:
dispatch_async(dispatch_get_main_queue(), {
performSegueWithIdentifier("seg_wait", sender: self)
})
I'm trying to figure out how to navigate around my app. But i'm a little lost.
I have a UIViewController that loads some data, then displays the data in a CollectionView. Then I have another UIViewController for the detailed view. I then trigger a segue to go to it, I pass the data etc.
self.performSegueWithIdentifier("detailViewSeque", sender: nil)
But the part i'm lost on is getting back to my main view, if I just trigger another segue then it loads all the data / view again. The data has already been loaded once, I really don't want to keep loading it.
I feel like I'm doing things wrong, that theres some super obvious way to handle this scenario.
Could someone point me in the right direction?
This is good situation to use an unwind segue (for more information: What are Unwind segues for and how do you use them?). Here's how to setup one up:
Firstly, create an #IBAction in the view controller you want to segue to, that takes a UIStoryboardSegue as its only argument. For example:
#IBAction func unwindToHere(segue: UIStoryboardSegue) {
// If you need you have access to the previous view controller
// through the segue object.
}
Secondly, you need to create the unwind segue in IB. To do this ctrl-drag from the view controller you want to segue from, to Exit and select the unwindToHere method:
Thirdly, you need to give your segue and identifier. To do this select your segue (see below - your segue will not be visible like normal segues); then use the Attribute Editor to give your segue an identifier.
Now you can use your segue. On the view controller you want to segue from, call:
self.performSegueWithIdentifier("YourID", sender: self)
To rephrase your needs "I have data that I need to keep around somewhere that isn't associated with a view controller".
You have a few options here. Your goal is basically to store it somewhere that isn't going to go out of memory.
The AppDelegate gets used for this purpose a lot but Singleton variable works as well.
I would personally create a singleton, say CatPictureRetriever with
private let _CatPictureRetriever SharedInstance = CatPictureRetriever()
class CatPictureRetriever {
static let sharedInstance = CatPictureRetriever()
var catPictures : NSArray?;
func gimmeCatPictures -> NSArray? {
return catPictures
}
}
Now you can get your pictures though your CatPictureRetriever anywhere
var pictures = CatPictureRetriever.sharedInstance.gimmeCatPictures()
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?
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.
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...
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)
}
The accepted solution worked for me. Code updated for Swift 2.0 below:
dispatch_async(dispatch_get_main_queue(),{
self.performSegueWithIdentifier(mysegueIdentifier, sender:self)
})
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)
})
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)
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.
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.