"whose view is not in view hierarchy" on Segue - ios

I try to perform a segue programatically from one view to another, way after it's been loaded (server sends an order to proceed) but it won't happen because "whose view is not in view hierarchy". Any ideas on how to solve it?
P.S. I don't perform any segues from viewDidLoad() method, all of them are performed from a method which is also called from another method in this class (which reads a dictionary, sent from the server). Also I tried to perform segues like this
dispatch_async(dispatch_get_main_queue(), {
self.performSegueWithIdentifier("seg_wait_ready", sender: self)
})
and just like this (even though I know this is wrong):
performSegueWithIdentifier("seg_wait_ready", sender: self)
None of these work, I still get the same warning.

I solved this issue by changing the logic of how my application changes VCs. Now I do it directly through the delegate by doing the following (roughly):
delegate.window!.rootViewController = destinationViewController
It cost me all the system transition animations but I like to do custom ones anyway. But remember to keep it in main queue. To do it safe put it in the main queue manually like this:
dispatch_async(dispatch_get_main_queue(), {delegate.window! ... })

Related

Beginner question on DispatchQueue.main.async

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.

Segue very slow on first call in iOS

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

Blank ViewController after programmatically performing segue in XCode

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)
})

iOS UICollectionView navigation

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()

performSegueWithIdentifier very slow when segue is modal

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.

Resources