I have two view controllers which I am interested in passing variables from one view controller to the next in a backwards manner. To achieve this I used a protocol, however, the variable in the first view controller are not updating when going back from view controller two to view controller one:
Below is my code for the first view controller:
import UIKit
class BlueBookUniversalBeamsVC: UIViewController {
var lastSelectedTableRowByTheUser: Int = 0
var lastSelectedTableSectionByTheUser: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
print(lastSelectedTableRowByTheUser)
print(lastSelectedTableSectionByTheUser)
}
}
extension BlueBookUniversalBeamsVC: ProtocolToPassDataBackwardsFromDataSummaryVcToPreviousVc {
func dataToBePassedUsingProtocol(passedSelectedTableSectionNumberFromPreviousVc: Int, passedSelectedTableRowNumberFromPreviousVc: Int) {
self.lastSelectedTableRowByTheUser = passedSelectedTableRowNumberFromPreviousVc
self.lastSelectedTableSectionByTheUser = passedSelectedTableSectionNumberFromPreviousVc
print("Last selected row passed back from SummaryVC is equal to \(passedSelectedTableRowNumberFromPreviousVc)")
print("Last selected section passed back from SummaryVC is equal to \(passedSelectedTableSectionNumberFromPreviousVc)")
}
Below is my code inside the second view controllerL
import UIKit
class BlueBookUniversalBeamDataSummaryVC: UIViewController {
var delegate: ProtocolToPassDataBackwardsFromDataSummaryVcToPreviousVc?
#objc func navigationBarLeftButtonPressed(sender : UIButton) {
let main = UIStoryboard(name: "Main", bundle: nil)
let previousViewControllerToGoTo = main.instantiateViewController(withIdentifier: "BlueBookUniversalBeamsVC")
if delegate != nil {
delegate?.dataToBePassedUsingProtocol(passedSelectedTableSectionNumberFromPreviousVc: self.selectedTableSectionNumberFromPreviousViewController, passedSelectedTableRowNumberFromPreviousVc: self.selectedTableRowNumberFromPreviousViewController)
}
self.present(previousViewControllerToGoTo, animated: true, completion: nil)
}
}
The weird thing is that in Xcode console when I go back from VC2 to VC1, inside the protocol function extension in VC1 I can see the values being printed correctly. However, when the values get printed from inside viewDidLoad() both of them are showing as 0. Any idea why this is happening, is there something I am missing out here?
Your second view controller is instantiating a new instance of the first view controller rather than using the instance that was already there. The second view controller shouldn’t present the first view controller again, but rather dismiss (or pop) back to it, depending upon the first presented or pushed to it.
By the way, the delegate property of the second view controller that points back to the first one should be a weak property. You never want a child object maintaining a strong reference to a parent object. Besides, delegates are almost always weak...
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 UICollectionViewController with an inputAccessoryView. Everything works great until I present a UIViewController, and then the accessory view disappears. Trying to get basic Chat application features.
I have implemented in the collection view:
override var inputAccessoryView: UIView? {
get {
return inputContainerView
}
}
override var canBecomeFirstResponder: Bool {
return true
}
override func becomeFirstResponder() -> Bool {
return true
}
As suggested in multiple other threads, I also call (in the collection view),
view.resignFirstResponder()
view.inputAccessoryView?.reloadInputViews()
view.becomeFirstResponder()
after dismissing the UIViewController but to no avail. print(view.isFirstResponder) still prints false. I have tried almost every combination of the above three lines in numerous different places in my code. I think I'm missing something simple.
The animation to present and dismiss view controller might be causing issue or You haven't maintained a global ivar for the view you have setted as input accessory view. Try creating a readonly ivar for the accessoryview so only one instance is allocated and maintained through out VC life cycle. Then ensure to set it back to the textfields before calling reloadInputViews.
After a few days I finally figured out something that works... I think I was trying to present the loginController before the collectionView was set as the first responder. Instead of just calling present I called this function:
func presentLoginControllerAfterImFirstResponder(fromUser: Bool) {
// Starts a timer
Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { (timer) in
// Is this view the first responder?
if self.isFirstResponder {
// Creates the loginController
let loginController = LoginController()
// Presents it
self.present(loginController, animated: fromUser, completion: {
// Once presented, sets rootViewController = self
loginController.rootViewController = self
})
// Stop the timer
timer.invalidate()
}
}
}
This works now. I guess the collectionView needed some time to set itself as the first responder? I can present and dismiss the loginController no problem and the inputAccessoryView remains.
I have a single view controller containing the some text labels and multiple other properties. I want to use that view controller for both editing the view and viewing the contents of view. Now what I want to do is pass the Flag so that it indicates whether the request is for editing the fields or viewing the fields.
I have done this but did not work. Lets say my view controller containing the view is third View Controller and I am accessing this view controller from first and second view controller.
//In third View COntroller
var isEdit: Bool! = false
func viewDidLoad(){
self.loadData()
}
override func loadData{
if isEdit == false{
//print this is edit mode
}
else if isEdit == true{
//This is view mode
}
else{
//print error navigtion
}
}
and I am accessing to this view controller from first view controller on button click action as
let mapViewFirst = self.storyboard?.instantiateViewControllerWithIdentifier("ThirdViewController") as? ThirdViewController
mapViewFirst.isEdit == true
self.navigationController?.pushViewController(mapViewFirst!, animated: true)
and from secondViewController as
let mapViewSecond = self.storyboard?.instantiateViewControllerWithIdentifier("ThirdViewController") as? ThirdViewController
mapViewSecond.isEdit == false
self.navigationController?.pushViewController(mapViewSecond!, animated: true)
it always runs on isEdit == false
i.e the view controller is always on Edit mode it never moves to else condition. Can anyone find the better solution to my issue.
Assuming the difference between isEdit and iseditMode is a typo in your question and not in your actual code, and also that the difference between mapViewFirst and mapViewControllerObj and mapViewSecond and mapViewControllerObj are also just typos in your question and not your actual code.
Then it is not working because viewDidLoad() is called when the view controller is loaded into memory, which is occurring when instantiateViewControllerWithIdentifier is called.
To get the functionality you want move loadData() from viewDidLoad to viewWillAppear which will get called when the view controller is pushed to the stack. (and make sure you override viewWillAppear as you are supposed to, not like you have omitted from viewDidLoad())
I have a controller that chooses one of 2 segues and executes them immediately at viewDidAppear:. One of them leads to a UINavigationController and the other leads to a UITabBarController and both of them implement preferredStatusBarStyle.
At some point the user can open a overlay controller that will check for the presented view controller and replicate the preferredStatusBarStyle
My problem is that I can never get the current view controller being displayed. I'm using the code bellow to get the current controller but it is always returning the first controller ever showed (the storyboard root view controller) and not the current one.
internal override func preferredStatusBarStyle() -> UIStatusBarStyle {
if let rootViewController = UIApplication.sharedApplication().delegate?.window??.rootViewController {
return rootViewController.preferredStatusBarStyle()
} else {
return .Default
}
}
Am I doing something wrong?