View hierarchy error while presenting a viewcontroller - ios

I have a following class:
class HomeViewController: UIViewController {
override func viewdidload() {
super.viewdidload()
callOtherVC()
}
func callOtherVC() {
let viewController = StepsViewController()
let rootViewController = UINavigationController(rootViewController: viewController)
self.presentViewController(rootViewController, animated: true, completion: nil)
}
}
StepsViewController is just another viewcontroller. In StepsViewController, I try to dismiss current StepsViewController and present other viewcontroller. Following is code.
class StepsViewController: UIViewController {
override func viewdidload() {
super.viewdidload()
callSecondOtherVC()
}
func callSecondOtherVC() {
let vc = ViewController()
self.addChildViewController(vc)
self.parentViewController!.dismissViewControllerAnimated(true, completion: nil)
vc.callOtherVC()
}
}
I initialize ViewController() because I need to call same function callOtherVC from ViewController. Basically the model in ViewController changes but I'm essentially calling same UINavigationController from callOtherVC function.
Whenever I do this, I get an error like below:\
Warning: Attempt to present (UINavigationController: 0x7d991600) on
(HomeViewController: 0x7a6e00a0) whose view is not in the window
hierarchy!
UINavigationController is from callSecondOtherVC and HomeViewController is as it is.
How should I order the VCs? And if someone can more explain about the view hierarchy, I would greatly appreciate.

I think what you need to do here, is call your method from viewDidAppear, rather than viewDidLoad. The reason for this is that the view is not in the view hierarchy at the time of viewDidLoad.

Related

presenting whose view is not in the window hierarchy warning

I am new in Programming and swift, and I have tried to read some solutions in stack overflow, but to be honest I don't really grasp with the answer :(
I have 2 view controllers. a homeVC and a LoginVC. homeVC is my initial view controller. in viewDidLoad I have firebase function that can check if the user has logged in before or not. if not, then the user will be send to loginVC. here is my simplified code in the HomeVC
import UIKit
import Firebase
class HomeVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// to check whether the user has already logged in or not
Auth.auth().addStateDidChangeListener { (auth, user) in
if user == nil {
let login = self.storyboard?.instantiateViewController(withIdentifier: "login") as! LoginVC
self.present(login, animated: true, completion: nil)
}
}
print("user enter homeVC")
}
}
and here is my loginVC
import UIKit
import Firebase
import GoogleSignIn
class LoginVC : UIViewController, GIDSignInUIDelegate {
#IBOutlet weak var googleButton: GIDSignInButton!
#IBOutlet weak var emailButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// delegate declaration
GIDSignIn.sharedInstance().uiDelegate = self
}
#IBAction func googleButtonDidPressed(_ sender: Any) {
GIDSignIn.sharedInstance().signIn()
}
}
the app can perform as I expected. but there is a warning in my debugging area :
Warning: Attempt to present LoginVC: 0x7fc315714f40 on
HomeVC: 0x7fc3155095c0 whose view is not in the window
hierarchy!
of course the problem is in this lines of code
let login = self.storyboard?.instantiateViewController(withIdentifier: "login") as! LoginVC
self.present(login, animated: true, completion: nil)
as far as I know, if the view is stacked in the layer of navigation controller, then if I want to move to another view controller I have to use perform segue method.
But for this case, between homeVC and LoginVC are not stacked in the same navigation controller. so no hierarchy. thats why I use that line of code to move to another view controller (loginVC). but I don't understand why it is said "view is not in the window hierarchy!"
So what should I do to omit that warning?
Move code to viewDidAppear
override func viewDidAppear(_ animated:Bool) {
super.viewDidAppear(true)
// to check whether the user has already logged in or not
Auth.auth().addStateDidChangeListener { (auth, user) in
if user == nil {
let login = self.storyboard?.instantiateViewController(withIdentifier: "login") as! LoginVC
self.present(login, animated: true, completion: nil)
}
}
print("user enter homeVC")
}
Your LoginVC is perfectly fine.
However, you need to change your HomeVC as #Sh_Khan suggested and move the testing code from viewDidLoad to viewDidAppear:
import UIKit
import Firebase
class HomeVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
// HomeVC.view was added to a view hierarchy
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// to check whether the user has already logged in or not
Auth.auth().addStateDidChangeListener { (auth, user) in
if user == nil {
let login = self.storyboard?.instantiateViewController(withIdentifier: "login") as! LoginVC
self.present(login, animated: true, completion: nil)
}
}
}
}
Explanation
Your viewDidLoad method gets called before the viewController gets presented, so it at that moment it cannot really present another view controller (since it itself is not presented), viewDidLoad documentation:
Called after the controller's view is loaded into memory.
This method is called after the view controller has loaded its view hierarchy into memory. This method is called regardless of whether the view hierarchy was loaded from a nib file or created programmatically in the loadView() method. You usually override this method to perform additional initialization on views that were loaded from nib files.
In that moment the viewController is not in the window hierarchy yet.
viewDidAppear however gets called when the view is presented and becomes a part of the window hierarchy, viewDidAppear documentation:
Notifies the view controller that its view was added to a view hierarchy.
You can override this method to perform additional tasks associated with presenting the view. If you override this method, you must call super at some point in your implementation.
Don't forget to call super.viewDidAppear during overriding it.
TLDR; You should move your code to viewDidAppear
viewDidLoad()
This method is called after the view controller has loaded its view hierarchy into memory. This method is called regardless of whether the view hierarchy was loaded from a nib file or created programmatically in the loadView() method. You usually override this method to perform additional initialization on views that were loaded from nib files.
Apple docs
So the view is only in memory yet and not in the hierarchy. You should move it to viewDidAppear
viewDidAppear()
Notifies the view controller that its view was added to a view hierarchy. Apple docs
As Sh_Khan said, move the lines:
let login = self.storyboard?.instantiateViewController(withIdentifier: "login") as! LoginVC
self.present(login, animated: true, completion: nil)
in viewDidAppear() method; when viewDidLoad() gets called, the view controller it's not added on the view hierarchy yet, it's not visible and it cannot present another view controller.
The reason for such kind of error is: You are trying present (open) two view controllers simultaneously (view of first presenting view controller is just started and you may be trying to present second view controller).
You should move your code (for view controller presentation/navigation) to viewDidAppear. Your main view of existing view controller (from where you are presenting new view controller) is not ready/loaded.
You should move it to viewDidAppear.
Here is sample code:
Swift 4
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// instantiate your view controller either using storyboard or class or any other way....
if let newVC = self.storyboard?.instantiateViewController(withIdentifier: "NewViewController") as? NewViewController {
self.present(newVC, animated: true, completion: nil)
}
}
In your case/code, solution is:
override func viewDidLoad() {
super.viewDidLoad()
// Move your code from here (viewDidLoad) to viewDidAppear
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// to check whether the user has already logged in or not
Auth.auth().addStateDidChangeListener { (auth, user) in
if user == nil {
let login = self.storyboard?.instantiateViewController(withIdentifier: "login") as! LoginVC
self.present(login, animated: true, completion: nil)
}
}
print("user enter homeVC")
}
Look at the difference between both view controller life cycle.
viewDidLoad
This method is called after the view controller has loaded its view hierarchy into memory. This method is called regardless of whether the view hierarchy was loaded from a nib file or created programmatically in the loadView() method. You usually override this method to perform additional initialization on views that were loaded from nib files.
See more about: viewDidLoad
viewDidAppear
Notifies the view controller that its view was added to a view hierarchy.
See more about: viewDidAppear
At this point in your code, the view controller's view has only been created but not added to any view hierarchy. If you want to present from that view controller as soon as possible you should do it in viewDidAppear to be safest.

Present view controller within details view controller of master details view controller in swift 3

I have some problem to present a view controller within details view controller of master details view controller. Is it possible to do that?
I want to present a view controller when the user taps a button on details view controller exactly like details view controller though it is not.
If it is possible then help me. If not then guide me some way to do that.
You can try it from Storyboard like,
In the storyboard, select the Segue as present modally, and go to the Identity Inspector, and choose Current Context for the Presentation option.
Hope it helps.
It's possible, but your details VC must be a navigation Controller, so you can present.
Try this :
class func presentViewCoontroller(vc : UIViewController) -> Void{
let viewController : UIViewController = ((UIApplication.shared.delegate as! AppDelegate).window?.rootViewController)!
if (viewController.presentedViewController != nil) {
viewController.presentedViewController?.present(vc, animated: true, completion: nil)
}
else{
viewController.present(vc, animated: true, completion: nil)
}
}
Calling
self.presentViewCoontroller(vc: yourcontroller)
create a static instance of master view controller and then try to present a new view controller with that reference.
class MasterViewController : UIViewController{
static var masterVC : UIViewController?
override func viewDidLoad() {
super.viewDidLoad()
MasterViewController.masterVC = self
}
}
class DetailViewController : UIViewController{
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func didTapButton(_ sender: UIButton) {
MasterViewController.masterVC?.present(<newViewController>, animated: true, completion: nil)
}
}

Call method of presenting viewcontroller from modal

I have a ViewController: if a user is not logged in, I open modally a new controller, so that the user can login. The opening is done this way:
if(!loggedIn){
self.performSegue(withIdentifier:"loginView", sender:self)
}
After login, I want to dismiss this modal and come back to my viewcontroller: this is very easy to do, but I want another thing. I want the presenting view controller call this method before I dismiss my modal:
func goToContent(animated:Bool){
let viewController:ContentViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Content") as! ContentViewController
self.navigationController!.pushViewController(viewController, animated: animated)
}
Is this possible? From my modal I could not get a reference to the presenting viewController. That is: I tried the following, but I don't know if this is the right way to proceed:
let vc:ViewController = ViewController()
vc.goToContent(animated:false)
This works but aren't there better solutions?
[Edit to reply to a question in the comments]
I implemented a delegate this way:
in the ViewController:
class ViewController: UIViewController, LoginViewControllerDelegate {
var loginViewController:LoginViewController = LoginViewController()
override func viewDidLoad() {
super.viewDidLoad()
loginViewController.delegate = self
}
In LoginViewController file, just before the class declaration:
protocol LoginViewControllerDelegate: class {
func goToContent(animated:Bool)
}
Inside LoginViewController:
weak var delegate: LoginViewControllerDelegate?
in its view did load (just for testing: I simply put a print inside the body of goToContent):
delegate?.goToContent()
You need to create a delegate of ContentViewController and in that delegate you need to add this method goToContent.
Now when you are logged in successfully, you need to call this delegate method. It will work.

How to redraw my view in SWIFT?

On my iPad app, I have a UIViewController with a button that open a modalView.
#IBAction func showPostCommentViewController(sender: AnyObject){
let modalView = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("PostCommentViewController") as! PostCommentViewController
modalView.modalTransitionStyle = UIModalTransitionStyle.CoverVertical
modalView.modalPresentationStyle = UIModalPresentationStyle.FormSheet
modalView.delegate=self
self.presentViewController(modalView, animated: true, completion: nil)
}
When I close the modalView with dismissViewControllerAnimated, I would like "refresh" my view controller (because I added new content). But as the modal view is a "formsheet" style, viewDidAppear or viewWillAppear aren't called.
I tried to use setNeedsDisplay, but it doesn't work.
I don't know how to do.
This would be a perfect use case for the delegate pattern.
1) define a protocol within PostCommentViewController.
protocol PostCommentVCInformationDelegate {
func hasDismissedPostCommentViewController(controller:PostCommentViewController)
}
2) Set a delegate variable within PostCommentViewController
var delegate: PostCommentVCInformationDelegate?
3) When you dismiss PostCommentViewController, you will call delegate?.hasDismissedPostCommentViewController(self)
This will send information back to the presenting VC.
4) Now we have our presenting View Controller conform to this protocol.
class ViewController: UIViewController, PostCommentVCInformationDelegate
5) When presenting the modal View:
modalView.delegate = self
6) Finally, we implement:
func hasDismissedPostCommentViewController(controller: PostCommentViewController) {
//Update
}

iOS Storyboard: Not able to instantiate view controller properly

I'm really having trouble instantiating a custom view controller.
This is my storyboard set up. The third view controller is the one I'm trying to present.
I've tried both of these methods.
1: This results in black screen.
var searchController: SearchController = SearchController()
self.presentViewController(searchController, animated: true, completion: nil)
2: This results in a white, empty view controller popping up.
let mainStoryboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
let searchController : UIViewController = mainStoryboard.instantiateViewControllerWithIdentifier("searchController") as! UIViewController
self.presentViewController(searchController, animated: true, completion: nil)
Here is the code for the actual view controller:
class SearchController: UIViewController {
lazy var searchBar: UISearchBar = UISearchBar(frame: CGRectMake(0, 0, 200, 20))
override func viewDidLoad() {
super.viewDidLoad()
var leftItem = (UIBarButtonItem)(customView: searchBar)
self.title = "Search"
self.navigationItem.leftBarButtonItem = leftItem
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
It's very confusing because another custom view controller I am is presented properly while using method #1 above. Why wouldn't it work for this controller?
Thanks a lot everyone.
When using Storyboards you don't need to use presentViewController or instantiate your view controllers manually. Instead it's best to use a segue to move from one UIViewController to the next.
1. In your case, you want a show segue, which it looks like you've already done judging by your storyboard.
2. You need to give your segue an Identifier which you can do by selecting your segue and going to the Attributes Editor.
3. To perform your segue just call the following from your first view controller (instead of presentViewController).
self.performSegueWithIdentifier("YourIdHere", sender: self)
This will cause the storyboard to instantiate your SearchViewController and present it in the way that you've defined for that segue.
4. If you want to pass any values to your SearchViewController, you can override prepareForSegue in UIViewController. In your first view controller:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let searchViewController = segue.destinationViewController where segue.identifier == "YourIdHere") {
// Now you can set variables you have access to in `searchViewController`.
}
}
And that should be it!

Resources