deinit in child view controllers - ios

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.

Related

How to detect back button is pressed in the next viewController in a navigationController

I want to detect if back button is pressed in the next viewController in a navigationController.
Let's say I have VC_A and VC_B viewControllers in a navigationController. I know how to detect if back button is pressed in a current view controller but I do not know how to detect it in a previous viewController.
Edit:
I go from VC_A to VC_B and when I press back button in VC_B then I want to call a function in VC_A.
You could use notification center. This link has a nice tutorial: https://learnappmaking.com/notification-center-how-to-swift/
I want to detect if back button is pressed in the next viewController in a navigationController.
I'm not sure I understand this exactly, but it really doesn't matter much: in essence, you're talking about some view controller (call it controllerA), whose views aren't currently visible, finding out about a change that affects some other view controller (controllerB). The usual reason for needing such a thing is so that controllerA can update some data that it manages.
A better way to handle that is to have both controllers share a common data model. Any application state that's affected by something like a view controller being dismissed is shared data that should be part of the data model. controllerA really shouldn't care about whether controllerB's back button was tapped or not... that event is only the business of controllerB (and arguably the navigation controller that manages it). What controllerA should care about is updating its own views according to whatever changes happened while it was off screen, and those changes should be recorded in the model by controllerB and any other view controllers that might have been active along the way.
I'm suggesting you to do that with Notification Center like AglaiaZ suggested you. But if you're not feeling comfortable with using Notification Center, then try this more basic solution with viewWillAppear delegate method in viewController from which you're tracking are you back from B VC. So, let's go.
Set this variable in your current view controller class where you want to trigger method when the back button is pressed on the specific view controller, let's call that specific view controller B VC.
let isFromBViewController = false
Then in code block where you're triggering the transition to B VC set this variable to true.
func goToBViewController() { // This method is triggering transition from A VC to B VC
isFromBViewController = true }
And then in viewWillAppear delegate method check did current VC from which we triggered the transition to B VC have appeard from B VC.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if isFromBViewController {
// code for doing something when you got back from B VC
isFromBViewController = false
}}
And that's it.
But, again I'm suggesting you to use the notification center as #AglaiaZ suggested, the tutorial is easy, and with that tutorial I've also learned how to use Notification Center and how to create and use custom notifications.
Good luck.
If I understood correctly, you want to do something when the back button in the navigation bar at the current view controller is pressed, and the user is going back from the current B view controller to A view controller.
Put this line of code in the view controller in which you want to track when the user has pressed the back button.
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if isMovingToParent {
//your code when back button is pressed
}
}

Order of viewWillAppear calls

If I have multiple view controllers being presented and dismissed in any order, can I be sure that iOS calls viewWillAppear methods in the right order (i.e. order of appearance)?
I cannot find any specific information about this in the documentation.
I think this is all you need to know about viewWillAppear from the docs:
This method is called before the view controller's view is about to be added to a view hierarchy and before any animations are configured for showing the view. You can override this method to perform custom tasks associated with displaying the view. For example, you might use this method to change the orientation or style of the status bar to coordinate with the orientation or style of the view being presented. If you override this method, you must call super at some point in your implementation.
Only thing that comes to mind that might not be absolutely clear is that this callback is called on the presenting view controller when presented view controller is going to be dismissed (so presenting view controller is going to appear again).
Therefore if A is a root, A.viewWillAppear will be called before it will appear on the screen. Then, if A presents B, just before B becomes visible, B.viewWillAppear will be called. And when B is being dismissed, A.viewWillAppear will get called again, since its view will appear again.
viewWillAppear() is called the first time the view is displayed and it is also called when the view is displayed again, so it can be called multiple times during the life of the view controller object.
It’s called when the view is about to appear as a result of the user tapping the back button, closing dialog, or when the view controller’s tab is selected in a tab bar controller, or a variety of other reasons. Make sure to call super.viewWillAppear() at some point in the implementation

View Controllers that are popped from the navigation stack seem to still exist in the back

I have three different view controllers in my project, all with their own overridden viewWillAppear functions, with print statements to identify which view controller's function is being called when the application is running. These controllers are all under a navigation controller, and for simplicity, I'll refer to them as View Controllers A, B, and C.
However, the problem is that when I navigate from View Controller A to View Controllers B and C, then use the "back" button on the navigation bar to return to viewController A, it seems like viewControllers B and C aren't dismissed completely. I checked the navigation stack count using self.navigationController.viewControllers.count and it's properly returning count = 1 (meaning that the only view controller on the stack is A), yet if I minimize the app and bring it back to the foreground again, the viewWillAppear functions are still being called by the B and C view controllers, even though they should have been popped off the stack. Furthermore, if I go back and forth between A and B multiple times, stay at A, then minimize the app and open it up again, the viewWillAppear method will be also be called multiple times, the number of occurrences being equal to however many times I moved between the two controllers.
Note: I am using a NotificationCenter Observer to tell the controllers to call viewWillAppear/viewDidDisappear when the app comes into the foreground or goes into the background.
Here is how I'm using the observer:
override func viewWillAppear(_ animated: Bool) {
NSLog("View Controller A: viewWillAppear called")
/*
Some code here
*/
}
func willEnterForeground(){
viewWillAppear(false)
}
NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: .UIApplicationWillEnterForeground, object: nil)
Sorry for the wall of text, I didn't know how to better describe this issue. This seems like a somewhat generalized problem, so I didn't post any code. If anyone has any clue on what the problem may be here, or would like me to post some more code, please let me know.
Thanks!
Update: RESOLVED. For anyone who stumbles upon this problem and happens to see this post, I left my solution in the comments section below.

Determine when leaving a view controller that it's not a segue

I'm trying to save some data upon user leaving this specific view controller (either by hitting nav back button or different tab bar button or exiting the app). However, there is one segue going forward, in this case I don't want to save the data yet, it will get saved on the next view controller with additional data.
How can I determine in viewWillDisappear: (or anywhere else) that user is leaving the view controller, but differentiate that it is not through the segue? I've looked at self.isBeingDismissed() and self.isMovingFromParentViewController() and I can't seem figure out a good solution. Any insight is greatly appreciated, thanks!
You might want to try this method instead of viewWillDisappear-
fun willMoveToParentViewController(parent : UIViewController?)
{
super.willMoveToParentViewController(parent);
if parent == nil {
//This means the current controller is getting popped out of the nav stack
}
}
Documentation:
Called just before the view controller is added or removed from a
container view controller.
Since navigation controller is a container controller, when it removes the top most controller, it should call this method with parent value as nil.
HTH

Popping view controller immediately after pushing it

I want the view controller to check something, and if it's true it would allow the user to use the view controller, and if it's false it would show an alert to the user and send the user back where he/she came from.
So I tried adding this to an if-statement at the end of viewDidLoad:
[self.navigationController popViewControllerAnimated:YES]
However, I got the error:
2014-08-09 20:12:59.731 ProjectName[1978:60b] nested pop animation can
result in corrupted navigation bar
2014-08-09 20:13:00.118 ProjectName[1978:60b] Finishing up a
navigation transition in an unexpected state. Navigation Bar subview
tree might get corrupted.
I learned from this StackOverflow question that calling pushViewController or popViewController before viewDidLoad finishes is unsafe. Are there any ways around this?
TLDR: I'm trying to push a view controller onto the stack, show an alert, then pop the view controller off of the stack.
EDIT: Added code.
This should be fairly easy provided you want to show the alert.
1 - Call [UIAlertView show] method from within viewDidAppear. It would be advisable to use viewDidAppear as it would be last in the view life cycle, when the view controller is fully pushed on the stack. So popping will be harmless.
2 - In clickedButtonAtIndex delegate method, call popViewControllerAnimated.

Resources