TL;DR:
How can I fully recreate/reset a view controller that I'm navigating back to from the automatically placed UINavigationController back button?
OldVC <- CurrentVC
Ensure OldVC is completely reset.
Hi guys,
I'm using UINavigation and the automatically generated back button. When a user presses the back button, I want the previous view controller in the navigation stack to be fully reinstantiated - similar to how you would create the view controller then push to it.
I've tried the following but have been unable to find a solution:
on view will load of the target VC, self.view.setNeedsDisplay() - doesn't recreate
manually dropping the old VC from the stack and inserting a fresh one in it's place
dismissing the VC - when leaving the main VC to the new VC, calling self.dismissViewControllerAnimated(true, completion: nil)
doesn't drop the old VC from the stack
I have a temporary solution in place now using the following code, however from a UX perspective it is not right, as it relies on a custom button and an illogical animation from the push that makes the user think they are going further into the app rather than coming out, the code is below and recreates the VC fully (as you can see from the instantiation).
private func goToStoryBoard(name: String) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier(name)
self.navigationController?.pushViewController(vc, animated: true)
}
UPDATE
It looks like the issue is with the way I am creating custom components - I initialise them once like below, but by doing it this way they will not update:
class HomeMenuButton: UIButton {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setTitleColor(InterfaceColors.MainFont, forState: .Normal)
self.titleLabel?.font = InterfaceFonts.ButtonFont
}
}
Don't make a whole new view controller. Just implement viewWillAppear: to completely initialize the view. This will work the same both when the view controller's view initially appears and when it appears because the pushed view controller is being popped to reveal it.
(If any situations later arise where you don't want to reinitialize the view on viewWillAppear:, just raise a Bool flag on those occasions.)
Related
So in my attached image I show my subclass I'm creating for my HOME button. I have many VCs with a HOME button and I want to connect them to this class to make them all send the user HOME
So far, I made my button to take this class, as shown in the image. My issue is I'm not able to connect my IBAction here and not sure why.... would appreciate any tips anyone can tell me about why I'm not able to connect my IBAction function to the button right now....
Another confusing thing is that although I haven't given any of the other buttons in this stackview of buttons a class, I'm actually able to connect any of my other buttons in this stackview to my IBAction... which I find odd.
class HomeButton: UIButton {
#IBAction func showHomeVC(sender: AnyObject) {
var sb: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
var vc: UINavigationController = sb.instantiateViewController(withIdentifier: "HomeNC-ID") as! UINavigationController
self.present(vc, animated: false, completion: nil)
}
}
I'm aware that I can simply go to each VC where there's a HOME button and create an IBAction from each and just paste this code in there and it will work, but I would like to do this another way where I don't have to have so much of the same code being repeated.... I would like to know what changes need to be made to make this happen.
The error that you are are getting is because you are using the class of UIButton which doesn't have the property to display a view controller. You should present it either on a UIViewController, or one of the other types.
It is linked up but you just can't present a view controller using self (UIButton)
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.
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.
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.
I have just made a small transition so my project loads a different storyboard as its main (did it in the info.plist).
I have my new storyboard to keep my viewController that are responsible for login screen etc. Just to make it more clear.
After the login button is tapped I want to initiate a navigationController from another storyboard:
func instantiateViewController(fromStoryboard storyboard: String, withIdentifier identifier: String) -> UIViewController! {
let storyboard = UIStoryboard(name: storyboard, bundle: nil)
let viewController = storyboard.instantiateViewControllerWithIdentifier(identifier)
return viewController
}
#IBAction func loginButtonTapped(sender: UIButton) {
let viewController = instantiateViewController(fromStoryboard: "Main", withIdentifier: "MainNavigationController")
presentViewController(viewController, animated: true, completion: nil)
}
Everything works correctly but one thing is driving me nuts.
After presenting the MainNavigationController from Main.storyboard its view hierarchy is not maintained.
What I mean is, the labels and buttons which supposed to be on top of another, full screen UIView (but are not its child and so they should remain) are now behind it.
What might be causing this and what is the simplest way to make them appear on top (as they do whey I open main.storyboard)
EDIT
I added a line of code in the rootView of the MainNavigationController in its viewDidLoad method:
self.view.sendSubviewToBack(wholeScreenView)
and it solved the problem.
However, does anybody know why do I have to code it myself and the views are not like in the Main.storyboard?
The storyboard is not configured the way you think it is. Your wholeScreenView is in fact in front of the other views, in the storyboard. The other views (the labels and buttons) are not subviews of wholeScreenView; they are subviews of the main view, and so is wholeScreenView. It is a later subview, so it is in front of them.