I am creating an application which shows a PageVC: UIPageViewController as intro and guide to app.
After navigating through the intro, a Next Button leads to "SettingsVC" which is used to store default settings for the app.
Settings have to be chosen by the user initially although can be changed later.
PageVC ---> SettingsVC
A Save and a Cancel button on the SettingsVC leads to the MainVC of the app.
A button on MainVC leads to SettingsVC.
SettingsVC <---> MainVC
The app would work as follows:
if isFirstLaunch{
instantiate PageVC
}else{
instantiate MainVC
}
in PageVC
nextButtonPressedinPageVC{
instantiate SettingsVC
}
in SettingsVC
if saveButtonPressed && cameFromPageVC{
instantiate MainVC
}
if cancelButtonPressed && cameFromPageVC {
do Nothing
}
if saveButtonPressed && cameFromMainVC{
dismiss currentVC
}
if cancelButtonPressed && cameFromMainVC {
dismiss currentVC
}
in MainVC
if settingsButtonPressedinMainVC {
instantiate SettingsVC
}
I have made sure that if it is application's first launch, PageVC will be instantiated else MainVC will be instantiated.
How can I move between the viewControllers without a possible memory leak i.e. where to performSegue and where to dismiss current VC?
Please include code for reference.
There are many ways to do this, here is one that I find very straightforward because you can do most of the work in your Storyboard:
Think of your MainVC as the rootViewController and the other two as accessory views that will only temporarily be shown. The MainVC should always be your entry point, so set it as the initial VC in your Storyboard.
The other two should be displayed modally so that you can easily return to the MainVC by dismissing them, no matter how you opened them in the first place.
To do this, draw a segue from your MainVC button to the PageVC and name it "showPageVC". From the Next button in your PageVC, draw another segue to the SettingsVC. Now you need some code to handle the dismiss actions: put this snippet in your MainVC:
#IBAction func unwindToMain(segue:UIStoryboardSegue) {
}
This function is just a marker, so it doesn't need a body. It just enables you to create a unwind segue back to MainVC: For each of the buttons in SettingsVC, hold Ctrl and draw from the button to the right exit icon in the header of the SettingsVC storyboard scene and choose unwindToMain in the tiny black popup.
Now you only have to implement the logic to decide if you want to show the PageVC or not in viewDidAppear() of the MainVC. So the whole code would look something like this:
class MainVC: UIViewController {
var didDisplayPageVC = false
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if (didDisplayPageVC == false) {
performSegue(withIdentifier: "showPageVC", sender: self)
didDisplayPageVC = true
}
}
#IBAction func unwindToMain(segue:UIStoryboardSegue) {
}
}
The rest is in the storyboard. If this little proof-of-concept is working, you can go and configure the segues (you might want to remove the animation) etc.
Related
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.
I start of with a tableViewController that has a list of names. When the user taps on a name, they are segued to a view controller.
While in that viewController the user may press a button that will take them to another table view Controller.
The layout is like this:
TableViewController(1) -> ViewController -> TableViewController(2)
My question is, how can I pop back to the first TableViewController from the Second TableViewController.
My rootViewController is my signIn View controller so I cannot pop back to root.
You can run this to pop to your rootViewController:
self.navigationController?.popToRootViewController(animated: true)
Update:
Since your rootViewController is not where you want to end up then you can iterate through your controllers and pop to a specific one:
for controller in self.navigationController!.viewControllers {
if controller.isKind(of: TableViewControllerOne.self) {
self.navigationController!.popToViewController(controller, animated: true)
break
}
}
Instead of TableViewControllerOne.self update to your desired controller.
If you're familiar with segues, you can implement an unwind segue. That would give you the added benefit of passing information back to TableViewController(1) if you needed to. To make that work in TableViewController1 you would add some code that looked like:
#IBAction func unwind(fromTableVC2 segue: UIStoryboardSegue) {
if (segue.source is TableVC2) {
if let svc = segue.source as? TableVC2 {
// pass information back
}
}
}
Then in your storyboard you would go to where you have your TableVC2 and drag the yellow VC circle to the exit and choose the function we created above. Name the segue (for this example we'll call it "UnwindToTableVC1"), and then somewhere in TableVC2 add the code:
func setVariableToPassBack () {
// Set up variables you want to pass back
performSegue(withIdentifier: "UnwindToTableVC1", sender: self) }
And that will take you back to your chosen destination with any information you wanted to pass back.
If you don't want to pass anything back, you really just need call the below line in your TableVC2:
performSegue(withIdentifier: "UnwindToTableVC1", sender: self)
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.
I am currently editing a code previously written,
In the application, The first viewController is a table view controller named: ListViewController that has several elements, each selection of row creates a new instance of a view controller and presents it modally. but in those view controllers, instead of dismissing them, the previous developer again created the instance of the ListViewController and presents it modally to go back.
The application is obviously using alot of memory.
Dismissing the view controllers is not an option.
if I pop view controllers in the stack one by one, this doesn't work, each view has popups etc presented on viewdidAppear.
I need to remove all previously loaded ViewControllers from memory and present a viewController such that there are no instances of any ViewControllers left in the memory.
Is it possible?
Is there a way i can goto say a new ViewController called HomeViewController ensuring that all previously loaded instances of all view controllers are released.
The scenario is as following:
ListViewController
/ | \
AViewController BViewController CViewController
ListViewController has 3 elements
A
B
C
user can tap any of them, that results in presenting a ViewController.
and from each of the view controllers, when back button is pressed, The ListViewController is presented.
Views are presented using the following code:
if let listViewController = storyboard!.instantiateViewControllerWithIdentifier("ListViewController") as? ListViewController {
self.presentViewController(listViewController, animated: true, completion: nil)
}
I am not sure it will work. Try this. Before presenting any new ViewController.
Make a method in AppDelegate
func switchControllers(viewControllerToBeDismissed:UIViewController,controllerToBePresented:UIViewController) {
if (viewControllerToBeDismissed.isViewLoaded && (viewControllerToBeDismissed.view.window != nil)) {
// viewControllerToBeDismissed is visible
//First dismiss and then load your new presented controller
viewControllerToBeDismissed.dismiss(animated: false, completion: {
self.window?.rootViewController?.present(controllerToBePresented, animated: true, completion: nil)
})
} else {
}
}
Now lets say you move like this
ViewController --> You click a button and present a SecondViewController
So currently we have ViewController and SecondViewController in memory.
Now when you click some button in SecondViewController in order to present a ThirdViewController, then SecondViewController must dismiss. So in SecondViewController button Press
#IBAction func buttonPress(_ sender: AnyObject) {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let controllerToBePresented = self.storyboard?.instantiateViewController(withIdentifier: "ThirdViewController") as! ThirdViewController
appDelegate.switchControllers(viewControllerToBeDismissed: self, controllerToBePresented: controllerToBePresented)
}
So now we have ViewController and ThirdViewController in memory.
SecondViewController is removed from memory.
Better solution is to keep your controllers in UINavigationController stack because you can get an array of all ViewControllers pushed on stack.
I have this situation :
I have a first view controller , when tap on button in it I open in modal mode another view controller , in this view controller when I tap another button I open in modal view another view controller and in it there is a button and when I tap on it I want to go to first view controller without re-initialize it.
How do I do it?
This is the perfect situation for an unwind segue.
Put this in your first viewController (the one you want to return to):
#IBAction func backFromVC3(_ segue: UIStoryboardSegue) {
print("We are back in VC1!")
}
Then in the Storyboard in your 3rd viewController, control-drag from your button to the exit icon at the top of the viewController and choose backFromVC3 from the pop-up.
Now, when the user presses the button in VC3, both VC3 and VC2 will be dismissed and you will return to VC1.
If you are not using Storyboards, you can dismiss the viewControllers with code. Here is code for a button's handler to dismiss two levels of viewController:
func doDismiss(_ sender: UIButton) {
// Use presentingViewController twice to go back two levels and call
// dismissViewController to dismiss both viewControllers.
self.presentingViewController?.presentingViewController?.dismiss(animated: true, completion: nil)
}
Thanks all for reply and edited my question :)
I found 2 line code to resolved my problem:
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.window!.rootViewController?.dismissViewControllerAnimated(true, completion: nil).
And that work well.
Thanks very much