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.
Related
My sender class for delegation:
import UIKit
protocol tapDelgation:class {
func tapConfirmed(message:String)
}
class ViewController: UIViewController {
weak var delegate:tapDelgation?
#IBAction func deligateSenderAction(_ sender: Any) {
var data = "hello world"
print(data)
self.delegate?.tapConfirmed(message: data)
}
}
My reciever class:
import UIKit
class NextViewController: UIViewController {
weak var vc:ViewController? = ViewController()
override func viewDidLoad() {
super.viewDidLoad()
vc?.delegate = self
}
}
extension NextViewController : tapDelgation {
func tapConfirmed(message: String) {
print(message)
}
}
What is expected: A button on sender vc is pressed and from reciever vc a console print would be popped. But in vain, nothing happens. Does any one know why it is happening? If it is not possible then why?
It looks like a memory management problem to me.
First problem: Creating a view controller with a default initializer like ViewController() is almost never the right thing to do. because it won't have any view contents.
You don't explain how your NextViewController and your ViewController get created and displayed.
It looks like NextViewController has a weak reference to ViewController, and ViewController's delegate point is also weak (delegate references should almost always be weak.)
This line:
weak var vc:ViewController? = ViewController()
Will cause NextViewController to create an instance of ViewController that isn't owned by anybody, so it will immediately be deallocated and the vc variable will go back to being nil. By the time you get to NextViewController's viewDidLoad, vc will be nil, so the optional binding in the line vc?.delegate = self won't do anything.
NextViewController's vc variable should almost certainly be a strong reference, not weak, but you don't show how ViewController ever gets displayed to the screen, so it isn't clear what you're trying to do.
weak var vc:ViewController? = ViewController()
Remove weak if you don't set the vc somewhere else and any other instance doesn't keep a strong reference to it.
If there is another instance with a strong reference, please share the related code.
The answer from the https://stackoverflow.com/users/205185/duncan-c is totally correct unless there is any other code which affects the presentation of the NextViewController and reference to the vc: ViewController
I changed viewController to SenderViewController but no luck and Sender and receiver is connected via navigation controller. i.e. If i press a button on sender a recieve comes via push transition. my aim was to since it is triggered an IBAction then the second view controller would implements the tap confirmed function. thanks for your answer. Learned a lot :)
Due to this comment, you need to implement prepareForSegue() method in your ViewController (original one) and set the vc property of the "next" view controller there instead of = ViewController() in the "next" make the extension on the ViewController:
extension ViewController {
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let nextController = segue.destinationViewController as! NextViewController
nextController.vc = self
}
}
Explanation based on the comment:
You get a new instance of the NextViewController with the new instance of the ViewController instantiated on its init (instead of passing the original instance of ViewController to it). That's where you can ge a strange behaviour with delegation.
weak var vc:ViewController? = ViewController()
Remove weak for vc it will release the view controller memory after disappear
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.
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.
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
}
I'm trying to add a navigation controller to my UIViewController subclass programmatically (I'm not using storyboards) and I wanted to find the best place to init it and configure such.
I have tried viewDidLoad (the views weren't initialized by init) and a convenience init (just to make sure) method but no luck.
Here's how I'm creating it:
override init!(nibName nibNameOrNil: String!, bundle nibBundleOrNil: NSBundle!) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
let nav = UINavigationController(rootViewController: self)
}
What would be the most appropriate method to place this logic in? I would like to keep this logic contained within this VC.
I am able to accomplish this by creating the nav. with a root vc from the presenting VC, but this leaks this logic and I'd rather not do that.
I'm not sure this is what you're looking for, but you could put some logic within a method in the controller you're presenting to determine whether it should be presented with or without a navigation controller; doing it this way means the presenting view controller doesn't need to know whether the controller it's presenting is embedded in a navigation controller or not. The presenting controller, would call this method after instantiating your controller. The presented controller could look something like this,
class NextViewController: UIViewController {
var wantsNavigationcontroller = true
func viewControllerWithOrWithoutNavigationController() -> UIViewController {
if wantsNavigationcontroller {
let nav = UINavigationController(rootViewController: self)
return nav
}else{
return self
}
}
}
The presenting controller would do this,
#IBAction func PresentNextcontroller(sender: UIButton) {
var nextVC = NextViewController()
self.presentViewController(nextVC.viewControllerWithOrWithoutNavigationController(), animated: true, completion: nil)
}