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.
Related
I want to create a navigation hierarchy where I want to go to a SecondViewController from FirstViewController using a NavigationController. The FirstViewController contains a button B that I intend to use to go to a SecondViewController. I am employing the navigation controller concept since I want to return to the FirstViewController later. I have done the following steps to achieve it:
I have embedded a NavigationController into a storyboard X containing both the FirstViewController and SecondViewController.
I have created a segue from the button B present in the FirstView (has FirstViewController associated with it) to the SecondView (has SecondViewController).
I set the navigationViewController nvc as following after I have presented the firstViewController:
nvc = UINavigationController.init(rootViewController: firstViewController)
Later on, when the button B gets pressed, I execute the following code to push the secondViewController onto the navigation hierarchy:
self.nvc?.pushViewController(secondViewController, animated: true)
However, the secondView doesn't present itself even after pushing.
I have named the segue between the button B and secondViewController as kSegue, so I tried to perform the segue as an addition to see if the secondViewController presents itself or not:
self.performSegue(withIdentifier: "kSegue", sender: self)
An exception occurs when both the 4th and 5th steps are performed together. The exception states that I'm pushing a viewController that already exists on the navigation hierarchy, but the second view still doesn't open even if I comment the performSegue code.
May I ask what mistake I am making here?
In the storyboard, make sure there is a rootViewController segue from the navigation controller to the first view controller. Also, make sure the navigation controller is marked as the initial view controller.
In the code, change
self.nvc?.pushViewController...
To
self.navigationController?.pushViewController...
1) Take a navigation controller on your storyboard
2) Set navigation controller as initial view controller
3) set view controller A as a root view controller of your navigation controller
4) in "GoToB" method access View controller B's instance and push it in navigation controller
5) On View controller B's "Go Back" method write code to pop it.
6) Dont forget to set storyboard Id on both A & B view controller
class FirstViewController: UIViewController
{
lazy var secondViewController : SecondViewController? =
{
let secondViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "SecondViewController") as? SecondViewController
return secondViewController
}()
override func viewDidLoad()
{
super.viewDidLoad()
}
#IBAction func goToB(sender : UIButton)
{
guard let secondViewController = self.secondViewController else
{
return
}
self.navigationController?.pushViewController(secondViewController, animated: true)
}
}
class SecondViewController: UIViewController
{
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func goBack(sender : UIButton)
{
self.navigationController?.popViewController(animated: true)
}
}
I have in my project tow view controller and I linked the first VC with navigation controller but the problem is : I used present to go to second VC (that mean I didn't use segue) ... how can I set back to first VC in navigation Controller by code (without segue) .
picture from my storyboard
my code :
let storyboard = self.storyboard
let viewcontroller = storyboard?.instantiateViewController(withIdentifier: "contact_detail") as! ViewController2
viewcontroller.arr2 = arr
present(viewcontroller, animated: true, completion: nil)
With the dismiss method it will work.
Dismisses the view controller that was presented modally by the view controller. (Apple Docs)
self.dismiss(animated: true, completion: nil)
If you are presenting a viewcontroller with present method, you can dismiss it with dismiss method.
If you are adding any view controller with push method then only it will get added to your navigation stack and you can remove it by calling popviewcontroller on it's back action.
Here, you are presenting a viewcontroller, hence it will not get added to your first navigation stack and you can not remove it with pop action on click of back.
If you are looking for a back button like feature on presented view controller, you can add a back button in toolbar, dismiss a viewcontroller on back button action, and can animate it like a popnavigation while dismissing.
extension UINavigationController {
public func removeViewController(classes : [String]) {
var vcs = [UIViewControllers]()
for viewController in self.viewControllers {
let name = viewController.className
if !classes.contains(name) {
vcs.append(viewController)
}
}
if classes.count < vcs.count {
self.viewControllers = vcs
}
}
}
now think you have 4 viewControllers , A, B, C, D, you want to remove B and C and Move Back To A
In D's View Controller
override func viewDidLoad() {
super.viewDidLoad()
//your works
let viewControllersToRemove = [String(describing: type(of:B)), String(describing: type(of:C))]
navigationController.removeViewControoler(classes : viewControllersToRemove)
}
I solved this by using pushViewController
self.navigationController?.pushViewController(MyViewController, animated: true)
We are looking to change the way a user logs out of our app. In order to do that, we want to dismiss all the VCs below the current VC and put another VC on top as the root VC. Right now we are doing this which I believe does not dismiss any VC below from memory.
let viewController = storyboard?.instantiateViewController(withIdentifier: "SignIn")
if let unwrappedViewController = viewController {
self.present(unwrappedViewController, animated: true, completion: {})
}
The problem is that the VC that we want to put on top is not embedded in a Navigation Controller or tab bar controller. How would we dismiss the VCs and set the new VC as the main VC as if the user was opening the app for the first time without having previously logged in? We also do want the transition to be animated with whatever animation is normal for that event (modal animation is fine). I have read a bunch of different ways on doing it but I want to know which way is best practice and should be implemented specifically dismissing all VCs and putting a new VC that isn't in a Nav controller on top.
If you can access the UIWindow of the app, you can set its rootViewController property to your sign-in view controller, effectively removing all current view controllers and adding the sign-in view controller instead. Here's an example:
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
// Should remove all subsequent view controllers from memory.
appDelegate.window?.rootViewController.dismiss(animated: true, completion: nil)
// Set the root view controller to a new instance of the sign in view controller.
appDelegate.window?.rootViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "SignIn")
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.
I want to navigate between view controllers as it is shown. I do not want to use segues.
When I try to navigate form FirstViewController to SecondViewController by clicking a button:
let next = self.storyboard?.instantiateViewControllerWithIdentifier("secondViewController") as! SecondViewController
self.presentViewController(next, animated: true, completion: nil)
I get:
Warning: Attempt to present SecondViewController on FirstViewController whose view is not in the window hierarchy!
Any ideas how to insert Navigation Controllers in-between?
Edit: It is TabBarController not TabViewController on the figure.
I navigate from First Scene's controller named "A-Controller" to First View Controller with :
#IBAction func navigateToFirstViewController(sender: AnyObject) {
let next = self.storyboard?.instantiateViewControllerWithIdentifier("firstViewController") as! FirstViewController
self.presentViewController(next, animated: true, completion: nil)
}
It is not shown on the figure.
If you try to present modal view over TabBarController, then :
let next = self.storyboard?.instantiateViewControllerWithIdentifier("secondViewController") as! UIViewController
self.tabBarController.presentViewController(next, animated: true, completion: nil)
Warning: Attempt to present SecondViewController on FirstViewController whose view is not in the window hierarchy!
Turns out this error meant I have to do the navigation from First View to Second View after my First View appears and is ready.
Putting the navigation lines inside viewDidAppear() function solved the issue.