UINavigationController popToViewController not popping - ios

Been working with UIKit for years now. It's amazing how issues like this seem to pop-up out of the blue.
I have a simple navigation setup:
UINavigationController
HomeViewController [push]
DetailViewController [push]
ModalViewController [modal]
A root navigation controller with 2 children pushed onto the stack. Then a modal presented from the root nav controller.
For some reason, the following snippet of code isn't working as expected:
extension UINavigationController {
func popToViewController(_ vc: UIViewController, animated: Bool, completion: #escaping ([UIViewController]?)->()) {
let popped = popToViewController(viewController, animated: animated)
if let coordinator = self.transitionCoordinator {
coordinator.animate(alongsideTransition: nil) { _ in
completion(popped)
}
}
else {
completion(popped)
}
}
}
using the extension:
navigationController.popToViewController(
homeViewController,
animated: true
)
No errors, warnings or crashes occur. UI is still fully responsive. But the DetailViewController in the stack is never popped. Inspecting the extension's popped variable, results in an empty array - which makes sense as the DetailViewController is clearly not removed from the stack.
What could prevent a navigation controller from popping a valid vc off of it's stack?
Things I've checked:
homeViewController is in the stack already, and I'm asking it to pop to the same instance. i.e. navigationController.viewControllers.contains(homeViewController) == true
I'm on the main thread. i.e. Thread.isMainThread == true
navigationController.viewControllers returns the same array before & after calling popToViewController(_vc:animated:)
Manually figuring out what vc's need to be popped, and calling setViewControllers(_ vcs:animated:) with the vcs I want to keep (in this case, just the HomeViewController instance). This still has the same issue.
I want to say this has something to do with popping view controllers off the stack from behind a modal presentation. But, as far as I know this is an okay thing to do. Plus, I've done it before and have had no issues in the past.

Related

Attempt to present VC2 on VC1 which is already presenting

Presently at a lost for why I am receiving the message of Attempt to present VC2 on VC1 which is already presenting when the VC1 does both the presenting and dismissing of the VC2. There are a few other questions and a lot of examples and I've not been able to resolve it from looking through them.
VC2 is called RoadwaysViewController and is opened as a modal. The only catch I see is that VC2 is used by 2 other VC. Things work great for awhile and then the problem arises when I've moved back and forth from VC to VC after awhile and in each I've used VC2. VC2 was intended to be used in multiple places since the data is the same for all.
Presenting and dismissing are both done from VC1.
class level variable
var roadwaysViewController = RoadwaysViewController()
Here is how it is presented
func roadwayTapped(){
roadwaysViewController = storyboard?.instantiateViewController(withIdentifier: "RoadwayModal") as! RoadwaysViewController
roadwaysViewController.delegate = self
self.present(self.roadwaysViewController, animated: true)
}
Here is how it is dismissed on VC1 the callback from the delegate.
func sendValue(value: Int, name: String) {
roadwaysViewController.dismiss(animated: true, completion: {
if value == 999 {
return
}
self.groupId = value
self.roadName.text = name
self.setupTableData()
})
}
I've tried checking to see if VC2 is presenting and then did dismiss. Tried this in the function that does the presenting, viewWillAppear and viewDidAppear.
I understand you can't have 2 VC presented at the same time but why it isn't getting properly dismissed I'm struggling to understand why. I don't believe I want to dismiss the parent and then show the child since the child is a modal.
Any help, ideas or suggestions is appreciated.

Trigger events when ViewController covered by a presented ViewController

I would like to process code when a ViewController is no longer visible due to presenting a new ViewController.
I cannot use ViewWillDisappear etc since the controller is not technically ever dismissed from the stack - you just can't see it.
What process can I use so that code runs when the controller is no longer visible (i.e. topmost) and when it becomes visible again?
EDIT:
Seems some confusion here - not sure why.
I have a viewcontroller.
I use the following code to present another controller
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let navController = storyboard.instantiateViewControllerWithIdentifier("NavController") as! UINavigationController
let thisController = navController.viewControllers[0] as! MyController
self.presentViewController(navController, animated: true, completion: nil)
This controller does not trigger a viewWillDisappear on the previous controller since the previous view is not removed - just hidden.
I need to process code when this view is hidden (i.e. not visible) and, more importantly, process code when it becomes visible again.
When presenting a UIViewController if the presentation style has been set to UIModalPresentationOverCurrentContext it doesn't call the viewWillDisappear and related methods as the view never disappears or gets hidden.
A simple test to check if thats the case would be to set the NavController that you are using to have a clear background color. If you do this and present the NavController and you can still view the first UIViewController below your NavController content. Then you are using UIModalPresentationOverCurrentContext and that is why the viewDidDisappear isn't called.
Have a look at the answer referenced by Serghei Catraniuc (https://stackoverflow.com/a/30787112/4539192).
EDIT: This is in Swift 3, you can adjust your method accordingly if you're using an older version of Swift
If you won't be able to figure out why viewDidAppear and viewDidDisappear are not called, here's a workaround
protocol MyControllerDelegate {
func myControllerWillDismiss()
}
class MyController: UIViewController {
var delegate: MyControllerDelegate?
// your controller logic here
func dismiss() { // call this method when you want to dismiss your view controller
// inform delegate on dismiss that you're about to dismiss
delegate?.myControllerWillDismiss()
dismiss(animated: true, completion: nil)
}
}
class PresentingController: UIViewController, MyControllerDelegate {
func functionInWhichYouPresentMyController() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let navController = storyboard.instantiateViewController(withIdentifier: "NavController") as! UINavigationController
let thisController = navController.viewControllers[0] as! MyController
thisController.delegate = self // assign self as delegate
present(navController, animated: true, completion: {
// place your code that you want executed when it disappears here
})
}
func myControllerWillDismiss() {
// this method will be called now when MyController will dismiss
// place your code that you want executed when it re-appears here
}
}
Firstly, thanks to Serghei for his time in helping work through this.
To clarify, both my potential presented controllers were set to Full Screen presentation style in the storyboard, however one was being set to Custom via a piece of pasted code dealing with the presentation. I can't find the error with the other.
However, if I force a presentation style of Full Screen as part of the presenting process then all is ok.
Hopefully my frustrating afternoon can help to save someone else's - always try to understand the implications and processes involved in pasted snippets.

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.

UINavigationController deinit never called

From some ViewController of my UINavigationController stack I present another ViewController and will never come back, but the problem is that deinit{} is not called. How should I remove each ViewController from the stack before navigation? Or should I use some other method? Now my code looks like
let destinationVC = storyboard?.instantiateViewControllerWithIdentifier("revealViewController") as! SWRevealViewController
self.presentViewController(destinationVC, animated: true, completion: nil)
First of all, when you call presentViewController:animated:completion: you will present the new viewController modally, outside of the navigationController's hierarchy.
If you wish to present it within the navigationController hierarchy use:
self.navigationController!.pushViewController(destinationVC, animated: true)
And if you want to change the view hierarchy, the navigationController has a property viewControllers which can be set with or without animation.
self.navigationController!.setViewControllers([destinationVC],
animated: true)
See the iOS Developer Library for more information.

Removing a view controller from memory when instantiating a new view controller

In my app, I am instantiating new view controllers instead of using segues because it looks better in animations as a result, my views keep running in the background. This causes large memory leaks.
My code to go back to the main screen is:
let mainStoryboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
let vc : UIViewController = mainStoryboard.instantiateViewControllerWithIdentifier("MainScreen") as UIViewController
self.presentViewController(vc, animated: false, completion: nil)
This view controller is still active in the background and therefore shouldn't be instantiated again. How do I do this.
When I close my view controller using the above code, it also does not unload it, it keeps running in the background. How do I make it unload as soon as the screen disappears.
I have tried doing
override func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(animated)
view.removeFromSuperview()
view = nil
}
However this does not work properly. How do I properly destroy a view controller from memory when exiting a view controller in this manner.
You need only to use:
EDIT Swift 4.2
self.dismiss(animated:true, completion: nil)
The rest of work is doing by ARC
To help you during your debug you can add also this code:
if let app = UIApplication.shared.delegate as? AppDelegate, let window = app.window {
if let viewControllers = window.rootViewController?.children {
for viewController in viewControllers {
print(viewController.debugDescription)
}
}
}
An important reason for this problem is related to the memory management!
if you have 'strong reference' or 'delegate' or 'closure' or other things like this, and you didn't managed these objects, your view controller has strong reference and never be closed.
you should get 'deinit' callback in view controller after than viewDidDisappear called. if 'deinit' not called so your view controller still is alive and it has strong reference.

Resources