Can't switch SCNView/UIViewControllers properly to prevent memory buildup - ios

I have a very linear app that uses a Navigation controller to go through a series of View Controllers. All segues are handled in the storyboard and set to "Push" (that's the only way I can get the navigation controller to work...)
Start with a 'menu' VC with options, select one and it takes you to another VC. Sometimes the next VC is another menu (more detailed) but sometimes the VC is an SCNView that shows an SCNNode. Navigating through the VCs is working perfectly, thanks to the Navigation Controller.
The problem comes whenever you go to one of the SCNViews and then back out (to a menu or previous VC). Particularly, switching from menu to SCNView, back to menu, back to same SCNView, over and over builds up memory until the app crashes.
ARC seems to not be releasing the SCNNodes/SCNViews when navigating out of them. Every SCNNode/Scene/View I set up as weak AND I've tried all kinds of combinations of the following code in each/all the VCs:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(true)
self.dismiss(animated: true, completion: nil)
}
I put this at the end of each VC but it still seems that the SCNNodes are getting retained and the memory builds up until it crashes.
How can I clear the SCNNodes when I exit the VC? Since I declare everything as weak inside ViewDidLoad, I can't call/set anything in the ViewWillDisappear func since it's outside of that.

Related

Swift call Function when ViewController is visible

I have a small problem I sadly couldn't solve. I have a few ViewControllers. I need to call a function when going back from a ViewController to the previous ViewController (which does not reload, so viewDidLoad / Appear is not called).
Mustn't there be anything like viewDidLoad, but gets called every time the ViewController is visible?
This is how I open the ViewControllers:
let vc = storyboard?.instantiateViewController(identifier: "levels_vc") as! LevelsViewController
present(vc, animated: true)
This is the way I try to get the event, but it only gets called the first time (when the ViewController loads the first time), but not when I open a new ViewController from this one and close the new one.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("VC appeared!")
}
You can use viewWillAppear or viewDidAppear for that purpose!.
viewWillAppear() is called every time the view will appear on the screen.
This can be useful for views that need to be updated every time they’re scrolled to. For example, the Now Playing section in a music app or any view that is updated when it is scrolled to.
viewDidAppear is called when the view has appeared on the users screen.
This is a good place to “lazy-load” any other elements onto your screen. If your user interface is very complicated, you don’t want to stall or cause a delay in the previous mentioned methods.
It’s better to load something onto the screen and then call some methods to update the views for some large applications.
This is also a create place to start any animations.
That would be ViewDidAppear() ViewDidAppear() - Apple Developer

How to correctly dismiss previous view controller

Apologies as I know there are some similar questions, but I've been looking for two weeks through every one I can find, and cannot figure it out (I'm a bit of a novice).
I have a few different View Controllers, not using a Navigation Controller. I can segue between them no problem. The issue is, I need each view to be dismissed when I segue to a new one. Here is some of what I've tried so far.
Option 1 (in new View Controller)
override func viewDidAppear(_ animated: Bool) {
presentingViewController?.dismiss(animated: false, completion: nil)
}
Option 2 (in old View Controller)
override func viewDidDisappear(_ animated: Bool) {
self.dismiss(animated: false, completion: nil)
}
In both of these cases, the new view gets dismissed and I'm taken back to the old view. I've tried about 20 versions of similar code.
Should I be using the first VC in my program as my "main" view controller, and presenting/dismissing all others on top of it? I didn't think this approach seemed memory efficient, when the "main" VC is not often used after initially loading the app.
It seems like I'm missing or not understanding something. Any help would be greatly appreciated.
Think of it this way: A view controller can't exist on an island. It has to be presented on top of something.
That means when you present one VC on top of another, the presenting view controller is the "foundation" for the new one you just presented.
If you don't want to present VCs on top of each other, you have a couple of options:
1) Use a navigation controller. This is probably the best approach. You can present or push any view controller. If you decide to push, you can remove the old one from the navigation stack, or you can keep it there so the user can go back. There are lots of ways to use a navigation controller, and it's easily the most flexible way to navigate between controllers.
2) Use a tab bar controller. This works best if you have just a few different view controllers in your app, but it's good for certain use cases.
3) Do exactly what you said in your post (use the root view controller to present/dismiss all other VCs). As I said, you can't present a view controller out of thin air-- there always has to be something behind it. Unless there's a ton of stuff going on in your root VC, this shouldn't cause any memory issues. This approach should be fine unless you're very particular about the animations between your view controllers.
In general, I wouldn't worry too much about memory usage until it becomes a problem. It should be fine to present view controllers on top of each other for 99% of normal use cases.
if you want to present VC B from VC A and want to dismiss VC A while Presenting you can use this Code
let parentVC = presentingViewController
dismiss(animated: true) {
let vc = self.storyboard!.instantiateViewController(withIdentifier...)
parentVC.present(vc, animated: true)`enter code here`
} `enter code here`

ViewController object recreated after navigate back?

Now I created a new project to test unwind segue.
In Storyboard the story entry is on VC0, a button in VC0 goes to VC1 (modally), a button in VC1 goes to VC2 (modally).
VC2 has a button to dismiss itself and in the function it looks like this:
#IBAction func btnDismiss(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
in VC1 I added an unwind function as follows:
#IBAction func unwindSecondView(segue: UIStoryboardSegue){
print("unwinded")
}
Then in storyboard I linked VC2's exit icon to unwindSecondview as action.
It works pretty well, however after I clicked "dismiss" button in VC2, VC1 appeared briefly and jumped back to VC0.
??? Anything wrong that caused jumping back to VC0?
--------------Initial question -----------------
I'm new to iOS and got a little confused for how VCs are created and activated.
Say I created 2 ViewControllers in my single view app. MainController(VC_M) and SettingsController(VC_S). I added the segues in storyboard, a setting button in VC_M goes to VC_S, VCS collects information, writes to standardDefaults, then ok button goes back to VC_M and refresh its view.
When I try to preserve some information in VC_M, I found that after I click ok button in VC_S and go back to VC_M, VC_M gets recreated and viewDidLoad() gets called. In the debugger, it shows the VC_M object (self) now has a different address in memory, so seems it's another instance of the class.
Is this a normal behavior? It's best that the old VC_M object gets saved so some data doesn't need to be reloaded. Initially I thought I could put some data loading stuff in init(), but when VC_M gets re-created init() got called, too. Some older posts suggested that ViewDidLoad() should not be called again but rather ViewDidAppear() should get called, but I'm not sure if it's the same case now.
In my case is it better to just use standardDefaults to reload everything, or is there a different kind of segue I should use? (I used push segue).

deinit in child view controllers

I have a tab bar controller and one of the tabs consists of a table view. When clicking on a row I segue to a new child controller.
In this new controller, I have a deinit which removes an observer and prints "controller is de-initialised here".
I can see when I press the back button in the navbar that the de-init is called on this action.
Now... I have another method within that child controller which sets the active tab of the tab bar controller. When this action happens, I do not see the print statement indicating the de-init is not being called in that case.
I'm wondering how to solve this? I'm worried that I'm holding onto extra stuff in memory if deinit isn't called.
Update:
In my method for setting the active tab, I would also like to popToRoot on the current active child controller. I tried the following:
self.navigationController!.popViewControllerAnimated(true)
self.tabBarController?.selectedIndex = 2
And I get the following error:
popViewControllerAnimated: called on <UINavigationController 0x15609a200> while an existing transition or presentation is occurring; the navigation stack will not be updated.
This makes sense but I'm wondering, is there any way around this?
Update 2
In order to avoid the issue above, I put popToRoot in my observer and added the tabbar.setSelectedIndex to the viewDidDisappear instead.
func checkForSeg(){
//self.tabBarController?.selectedIndex = 2
self.navigationController!.popViewControllerAnimated(true)
}
override func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(true)
print("view Did Disappear called?")
self.tabBarController?.selectedIndex = 2
}
But I do not understand why the tab bar isn't switching index when I press the nav back bar button(I don't want it to and it isn't but I dont understand why?)
When I explicitly call popToRoot in the checkForSeg func, then the viewDidDisappear executes the line 'tabbar.setSelectedIndex' (as desired) but when I press the nav back bar button it doesn't (also desired). I don't understand why this is? As I have not implemented any custom logic for when it should or shouldn't perform the tab change.
Also, I AM getting the print statement every time.
When you switch tabs in a UITabBarController, you do not release the previous tab's controller from memory.
This is expected behavior.
If you move back to the tab you are referring to you will see that the child VC is still alive and well.
The current active VC of any tab will always stay resident in memory during the execution of the application.
Thus, at a minimum, when using UITabBarControllers, the root viewController on each tab is held in memory and only released on application termination, or termination of the tabBarController.
No issue here.

Completely remove View Controller - Swift

I created a little game (not with Sprite Kit), I have a MenuViewController and a GameViewController.
On my GameViewController, I have a button "MENU" to go to the menu. I had this code to the GameViewController :
print("test")
It is executed every seconds with using a timer.
When I press the "MENU" button, I find myself on the menu but the timer is still running. I think the GameViewController is not completely removed, how can I do this ?
PS: to dismiss the GameViewController, I use this :
func goToMenuViewController() {
let menuViewController = MenuViewController()
menuViewController.modalTransitionStyle = UIModalTransitionStyle.CrossDissolve
self.presentViewController(menuViewController, animated: true, completion: nil)
}
EDIT 1
I add some infos about my app hierarchy :
When the user launch the app, he finds himself on the MenuViewController.
There is a play button on this ViewController which takes to the GameViewController.
On the MenuViewController, there is an other button which takes to the RulesEditorViewController, allowing to modify rules displayed on the GameViewController
you can not see if the GameViewController is removed in this way.
if you use a timer in a GameViewController, it will keep a ref to GameViewController until it stop executing.
By the way, present menu view controller won't make the game view controller be removed
Your try to dismiss GameViewController is wrong. You are create new Menu controller and showing it above the GameViewController. That's why this controller is still in app memory.
You have to use this function self.dismissViewControllerAnimated(true, completion: nil) or self.navigationController?.popToRootViewControllerAnimated(true) depending on your segue type.
For now if you want access RulesEditorViewController you have to stop the game and leave this controller. But if you want modify options while playing then it will be better if you will access RulesEditorViewController directly from your GameViewController like you trying to show Menu now.
Update:
First of all you should understand view controllers hierarchy and ways how to present it. I believe this tutorial on example of storyboards will help you: http://www.raywenderlich.com/50308/storyboards-tutorial-in-ios-7-part-1

Resources