Swift: Popups and ViewDidAppear etc - ios

I'm a novice Swift-programmer who wants a popup window (in a separate view/view controller) to popup immediately when an app is loaded (even before the main view is displayed if possible).
I use the following code to popup
if let vc = storyboard?.instantiateViewController(withIdentifier: "PasswordPopUp") as? PasswordPopUp {
vc.modalPresentationStyle = .overCurrentContext
present(vc, animated: true, completion: {
//self.cleanP2action()
})
} else {
print("error creating PasswordPopUp")
}
This works fine if I use it by clicking a button for instance. But I would like it to happen automatically. When I add the code to viewDidLoad i get an error msg
Warning: Attempt to present <Mygame.PasswordPopUp: 0x7fbfd2c252f0> on
<Mygame.ViewController: 0x7fbfd3876000> whose view is not in the
window hierarchy!
I tried to stick it in viewDidAppear but this does not seem to be called. Here is the essentials of the main view controller:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
What's wrong and how do I fix it?

Related

If I present a ViewController programmatically without using a Navigation Controller, does the new VC "replace" the old one, or does it stack on top?

I'm new to iOS.
I have an app where the path through the app can vary depending on the configuration I fetch from an API. Because of this, I don't use segues because I would need to create a segue from each ViewController (VC) to EVERY other VC. It creates a mess of segues that I don't want. So Instead I navigate from screen to screen like this:
func navigate(to viewController: String) {
let storyboard = UIStoryboard(name: K.mainStoryBoard, bundle: nil)
let nextVC = storyboard.instantiateViewController(identifier: viewController)
self.present(nextVC, animated: true, completion: nil)
}
My question is this: If I would have embedded my VCs in a NavigationController I know it would have created a stack. When I get to the last screen I would call func popToRootViewController(animated: Bool) -> [UIViewController]? and start from the beginning. However, I don't use a NavigationController. So, does that mean that when I present the next VC it replaces the previous one or does it stack on top of the previous one? I'm trying to prevent memory leaks so I want to make sure I don't keep stacking the VCs on top of each other until my app runs out of memory and crashes.
Thanks in advance
Edit
So, in my final VC I created an unwind segue. And I call it like this: performSegue(withIdentifier: "unwindToMain", sender: self)
and In my first VC (the initial VC in my app) I write this:
#IBAction func unwind( _ seg: UIStoryboardSegue) {
}
Everything works fine the first trip through the app. The last VC unwinds back to the fist VC. The problem is now that when I try to run through the app again (starting from VC 1 and then going to the next one) I now get this error:
MyApp[71199:4203602] [Presentation] Attempt to present <MyApp.DOBViewController: 0x1038760c0> on <MyApp.ThankYouViewController: 0x112560c30> (from <MyApp.ThankYouViewController: 0x112560c30>) whose view is not in the window hierarchy.
To make sense of this, DOBViewController would be the second VC I want to go to from the MainVC. ThankYouViewController is my last VC. It looks as if it isn't completely removed from the stack. Can anyone tell me what's going on?
Here is a very simple, basic example...
The controllers are setup in Storyboard, each with a single button, connected to the corresponding #IBAction.
The DOBViewController has its Storyboard ID set to "dobVC".
The ThankYouViewController has its Storyboard ID set to "tyVC".
MainVC is embedded in a navigation controller (in Storyboard) and the navigation controller is set to Initial View Controller:
class MainVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.setNavigationBarHidden(true, animated: false)
}
#IBAction func pushToDOB(_ sender: Any) {
if let vc = storyboard?.instantiateViewController(withIdentifier: "dobVC") as? DOBViewController {
navigationController?.pushViewController(vc, animated: true)
}
}
}
class DOBViewController: UIViewController {
#IBAction func pushToTY(_ sender: Any) {
if let vc = storyboard?.instantiateViewController(withIdentifier: "tyVC") as? ThankYouViewController {
navigationController?.pushViewController(vc, animated: true)
}
}
}
class ThankYouViewController: UIViewController {
#IBAction func popToRoot(_ sender: Any) {
navigationController?.popToRootViewController(animated: true)
}
}
does that mean that when I present the next VC it replaces the previous one or does it stack on top of the previous one?
The new one stacks on top of the previous one.
When you present a view controller, like
self.present(nextVC, animated: true, completion: nil)
The one you called .present on (self in this case) becomes presentingViewController for the nextVC instance.
The one you presented (nextVC in this case) becomes presentedViewController for the self instance.

Swift: Cannot modify images in popup

Beginner swift-user here. I want to open a pop up connected to a seperate view controller (P2_Gift_Pop_Up) from the main view controller. To this end I include the following in a code snippet in my main view controller
let vc = P2_Gift_Pop_Up()
vc.modalPresentationStyle = .overCurrentContext
present(vc, animated: true, completion: nil)
This starts running code in the popup window (a print statement works anyway), so so far, so good.
However, when I try to modify some elements in the view connected to the view controller I get a
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an
Optional value. Here is the code in its entirety.
import UIKit
class P2_Gift_Pop_Up: UIViewController {
#IBOutlet weak var Slot1: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
Slot1.setImage(UIImage(named: "Card 2 Red"), for: .normal)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
Although I have (via another answer on this site) an understanding of what the msg means, I don't understand why I get it in this context, and how to fix it. It might also bear emphasis that although code starts running after the P2_Gift_Pop_Up call, the corresponding view is not shown.
You need to present your controller like this(you need to set your storyboard ID and then add this in identifier):
let storyBoard = UIStoryboard(name: "main", bundle: Bundle.main)
let vc = storyBoard.instantiateViewController(withIdentifier: "your storyboard id") as! P2_Gift_Pop_Up
vc.modalPresentationStyle = .overCurrentContext
present(vc, animated: true, completion: nil)
Your app is crashing because of your button(Slot1) not have a memory so you need to present like above.
#JogendarChoudhary told you what to do, but didn't really explain why.
When you create your P2_Gift_Pop_Up with
let vc = P2_Gift_Pop_Up()
You aren't initializing it properly. It doesn't get a chance to load it's views from it's XIB file/storyboard.
Assuming you have your view controller defined in your main app storyboard, you need to load the view controller from the storyboard.
You should add a unique identifier to your view controller in your storyboard, and then load it using that identifier. (Using the name of the class as the identifier is as good a choice as any.)
The UIViewController class has a property storyboard which will contain the storyboard from which it's loaded. Usually that's your app's main storyboard, and what you want. Thus:
if let vc = storyboard?.instantiateViewController(withIdentifier: "P2_Gift_Pop_Up id")
as? P2_Gift_Pop_Up {
vc.modalPresentationStyle = .overCurrentContext
present(vc, animated: true, completion: nil)
} else {
print("error creating P2_Gift_Pop_Up")
}

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.

Using viewDidAppear to present a View Controller, re-opening it when it's closed

In my App, I've created a new storyboard that serves as a very basic tutorial for how to use certain features. (Instructions.storyboard). This storyboard has it's own class - InstructionsVC.swift
I want to present InstructionsVC when MainVC loads within viewDidAppear.
It works great. Fires up on App load just like it's supposed to. The problem occurs when I press the [Close] button on the Instructions interface. It closes the VC, fades to the main screen, and then immediately fires the Instructions VC back up.
How can I prevent the Instructions VC from loading back up once it's closed?
func openInstructions() {
let storyboard = UIStoryboard(name: "Instructions", bundle: nil)
let instructionsView = storyboard.instantiateViewController(withIdentifier: "instructionsStoryboardID")
instructionsView.modalPresentationStyle = .fullScreen
instructionsView.modalTransitionStyle = .crossDissolve
self.present(instructionsView, animated: true, completion:nil)
}
override func viewDidAppear(_ animated: Bool) {
openInstructions()
}
And within my instructions class, I have the following action on the close button:
#IBAction func closeButtonPressed(_ sender: UIButton) {
let presentingViewController: UIViewController! = self.presentingViewController
presentingViewController.dismiss(animated: true, completion: nil)
}
Note - I'd rather not use UserDefaults to resolve this, because I'm going to be incorporating something similar in other parts of the App and don't want to resort to UserDefaults to achieve the desirable behavior.
Thanks in advance buddies!
viewWillAppear and viewDidAppear are called every time a view controller's content view becomes visible. That includes the first time it's rendered and when it's shown again after being covered by a modal or by another view controller being pushed on top of it in a navigation stack.
viewDidLoad is only called once when a view controller's content view has been loaded, but before it is displayed. Thus when viewDidLoad is called it may be too soon to invoke your second view controller.
You might want to add an instance variable hasBeenDisplayed to your view controller. In viewDidAppear, check hasBeenDisplayed. If it's false, display your second view controller and set hasBeenDisplayed to true.

View hierarchy error while presenting a viewcontroller

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.

Resources