I have several view controllers (UIViewController). Each view controller has its own storyboard. And I want the app to call deinit when it transitions from one view controller to another. But it won't.
The app starts with HomeViewController. And it will transition to SelectViewController when the user taps a button (UIButton).
class HomeViewController: BasicViewController {
#IBAction func selectTapped(_ sender: UIButton) {
let storyboard = UIStoryboard(name:"Select",bundle:nil)
let controller = storyboard.instantiateViewController(withIdentifier: "SelectView") as! UINavigationController
let viewController = controller.topViewController as! SelectViewController
self.navigationController?.pushViewController(viewController, animated: true)
}
deinit {
print("HOME HAS BEEN REMOVED FROM MEMORY")
}
}
The app never calls deinit when it leaves HomeViewController. I wonder if that's because it's pushing a view controller? If I set the view controller as follows, the app will crash. What am I doing wrong? I have never done it with different storyboards. Thanks.
class HomeViewController: BasicViewController {
#IBAction func selectTapped(_ sender: UIButton) {
let storyboard = UIStoryboard(name:"Select",bundle:nil)
let viewController = storyboard.instantiateViewController(withIdentifier: "SelectView") as! SelectViewController
self.navigationController?.setViewControllers([viewController], animated: true)
}
}
UPDATE 1
I guess it's just the following.
#IBAction func selectTapped(_ sender: UIButton) {
let storyboard = UIStoryboard(name:"Select",bundle:nil)
let controller = storyboard.instantiateViewController(withIdentifier: "SelectView") as! UINavigationController
self.present(controller, animated: true, completion: nil)
}
UPDATE 2
I also clear the view controller stack when SelectViewController appears as follows.
override func viewWillAppear(_ animated:Bool) {
super.viewWillAppear(animated)
var navigationArray = self.navigationController?.viewControllers
navigationArray?.removeAll()
}
Actually, It is not about storyboards. As per the Apple's documentation, "deinit" method gets automatically called when an object is deallocated from memory, not when a view controller is pushed from current one.
When you push "SelectViewController" from "HomeViewController", instance of "HomeViewController" does not deallocated from memory, hence deinit method does not called.
Here is a link to Apple's Documentation for Deinitialization. Alternatively, you can use "viewDidDisappear" method of the view controller to perform an operation if it satisfies your need.
From your code snippet, I understand that you expect deinit is called after pushViewController. But, by calling pushViewController, your navigation controller pushes your HomeViewContoller to navigation stack, which means navigation controller holds strong reference to your HomeViewController object. So, deinit is not called. If you don't need HomeViewController, you have to manually remove it from navigation stack w/in SelectViewController.
See this how. How to remove a specific view controller from uinavigationcontroller stack?
In SelectViewController,
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let allControllers = NSMutableArray(array: navigationController!.viewControllers)
allControllers.removeObject(at: allControllers.count - 2)
self.navigationController!.setViewControllers(allControllers as [AnyObject] as! [UIViewController], animated: false)
}
Related
My scenario, I am having Tabbar with three viewcontroller. Here, tabbar first viewcontroller I am showing tableview. If I click the tableview cell it will show one popup present model viewcontroller. In this present popup viewcontroller I am maintaining two bar button cancel and done. If I click done It will dismiss and show tabbar main view controller. While dismiss time I need to pass some values with button flag from present popup view controller to tabbar main viewcontroller.
Here, below my dismiss popup pass viewcontroller code (VC 2)
#IBAction func apply_click(_ sender: Any) {
print("Dimiss Filter")
dismiss(animated: true, completion: {
if let navView = self.tabBar?.viewControllers?[0] as? UINavigationController {
if let secondTab = navView.viewControllers[0] as? HomeViewController {
secondTab.selectedIndexFromFirstTab = self.selectedIndex
//secondTab.item = self.item
secondTab.tfData = "YES"
}
}
self.tabBar?.selectedIndex = 0
})
}
Here, Tabbar main view controller code (receiving values) (VC 1)
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("SELECTED INDEX:\(selectedIndexFromFirstTab)")
print("RESPONSE:\(tfData)")
}
I am not receiving values, how to solve this issue.
From my previous answer you need to make few changes to existing code if you want to pass values from your child view to main tab bar controller.
For that first of all you need to declare a method into your main TabBarViewController
func tabPressedWithIndex(index: Int, valueToPass: String) {
print("selected Index: \(index)")
print("Value from user: \(valueToPass)")
}
Now you need to pass that tab bar controller to your detail view with didSelectRowAt method and it will look like:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let vc = self.storyboard?.instantiateViewController(withIdentifier: "DetailViewController") as! DetailViewController
vc.selectedIndex = indexPath.row
vc.tabBar = self.tabBarController as? TabBarViewController // Here you need to assign tab bar controller.
self.present(vc, animated: true, completion: nil)
}
Now next thing is when you click on dismiss button from detail view controller you need to add one line below self.tabBar?.selectedIndex = 1:
self.tabBar?.tabPressedWithIndex(index: 1, valueToPass: self.userTF.text!)
Now this will pass the values to main tab bar controller and method tabPressedWithIndex will call and print your data in your main tab.
Check out demo project for more info.
You can achieve it multiple ways. Using blocks/closures, protocols or if you are using RxSwift than using controlled property or using controlled events. Because I can't demonstrate everything here am gonna write protocol
Using Protocol
Step 1:
Declare a protocol in your modal view controller
#objc protocol ModalViewControllerProtocol {
func dismiss(with data: String)
}
Step 2:
ViewController that presents this ModalViewController make it to confirm the protocol
extension HomeViewController: ModalViewControllerProtocol {
func dismiss(with data: String) {
//use the data here
self.presentedViewController?.dismiss(animated: true, completion: nil)
}
}
Step 3:
Declare a variable to hold delegate reference in ModalViewController
weak var delegate: ModalViewControllerProtocol? = nil
Step 4:
In your ViewCOntroller that presents the modalViewController pass self as a delegate to ModalViewController before presenting
let modalVC = //...
modalVC.delegate = self
self.present(modalVC, animated: true, completion: nil)
Finally in IBAction of ModalViewController simply call
#IBAction func apply_click(_ sender: Any) {
self.delegate?.dismiss(with: "your_data_here")
}
Using Block/Closure
Step 1:
In your modal ViewController declare a property that accepts a block/closure
var completionBlock: (((String) -> ()))? = nil
Step 2:
In your ViewController that presents this ModalViewController, pass a block before presenting it
let modalVC = //...
modalVC.completionBlock = {(data) in
debugPrint(data)
self.presentedViewController?.dismiss(animated: true, completion: nil)
}
self.present(modalVC, animated: true, completion: nil)
Step 3:
Finally in your ModalViewController IBAction simply execute the block passed
if let block = completionBlock {
block("your data here")
}
Hope it helps
This is the solution
self.dismiss(animated: true) {
if let tabController = self.presentingViewController as? UITabBarController {
if let navController = tabController.selectedViewController as? UINavigationController {
if let secondTab = navController.viewControllers.first as? HomeViewController {
secondTab.tfData = "YES"
}
} else {
if let secondTab = tabController.selectedViewController as? HomeViewController {
secondTab.tfData = "YES"
}
}
}
}
I have added a child viewcontroller to VC1. On tapping a button in child viewcontroller , I am pushing to another viewcontroller , VC2. On tapping back button in VC2 , I need to remove the child viewcontroller but I m unable to do it.Can u pls help me ?
override func viewDidDisappear(_ animated: Bool) {
let controller = storyboard!.instantiateViewController(withIdentifier: "PopupViewController") as! PopupViewController
controller.willMove(toParentViewController: nil)
controller.view.removeFromSuperview()
controller.removeFromParentViewController()
}
I added the following in VC1 and it solved my problem
override func viewWillDisappear(_ animated: Bool) {
for controllers in self.childViewControllers
{
controllers.willMove(toParentViewController: nil)
controllers.view.removeFromSuperview()
controllers.removeFromParentViewController()
}
}
I have a view controller as my initial view controller.
there's a button in it(GO button) which when the user taps, it should go to another view controller(let's call it Destination view controller with label 'This is where i wanna go'). Meanwhile i want to pass it through a Tabbar controller. The reason is i want to have tabbar in my navigation stack and when users presses back on Destination view controller, it must go to tabbar controller. Picture shows what i want. what can I do to skip tabbar while having it in navigation stack?
You can do that easily inside the IBAction of GO button:
#IBAction func goTapped(_ sender: UIButton) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc1 = storyboard.instantiateViewController(withIdentifier: "myTabBarViewController")
let vc2 = storyboard.instantiateViewController(withIdentifier: "myGoalViewController")
let controllers = [vc1, vc2]
self.navigationController!.setViewControllers(self.navigationController!.viewControllers + controllers, animated: true)
}
Good luck!
Going to DestinationViewController could be manually:
if let destinationViewController = self.storyboard?.instantiateViewController(withIdentifier: "Storyboard ID of DestinationViewController") {
self.navigationController?.pushViewController(destinationViewController, animated: true)
}
(Alternatively, you could make a segue from FirstViewController to the DestinationViewController directly in Storyboard)
And in your DestinationViewController, insert the TabbarController to the Navigation sequence manually after view did appear, then you are able to go back to the TabbarController:
class DestinationViewController: UIViewController {
//......
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if self.isBeingPresented || self.isMovingToParentViewController {
var viewControllers = self.navigationController?.viewControllers
if let index = viewControllers?.endIndex.advanced(by: -1),
let tabBarController = self.storyboard?.instantiateViewController(withIdentifier: "Storyboard ID of TabBarController") {
viewControllers?.insert(tabBarController, at: index)
self.navigationController?.viewControllers = viewControllers!
}
}
}
//......
}
I have a viewcontroller that presents from a .xib.
Inside the .xib there is a button; I want the button to pushviewcontroller to an other viewcontroller (ViewControllerPeopleNew).
I use an action button:
#IBAction func _hitPeople(sender: AnyObject) {
let mapViewControllerObejct = self.storyboard?.instantiateViewControllerWithIdentifier("peoplenew") as? ViewControllerPeopleNew
self.navigationController?.pushViewController(mapViewControllerObejct!, animated: false)
}
But got an error:
Signal SIGABRT
unrecognized selector sent to instance
I already checked all about name identifier viewcontroller and all is right.
From view controller present with XIB you can not get storyboad use below code
#IBAction func _hitPeople(sender: AnyObject) {
let storyboard = UIStoryboard(name: "storyboadname", bundle: nil)
let mapViewControllerObejct = storyboard.instantiateViewControllerWithIdentifier("peoplenew") as? ViewControllerPeopleNew
self.navigationController?.pushViewController(mapViewControllerObejct!, animated: false)
}
Always remember that you can only use self.storyBoard to instantiate a view controller when both the view controllers (To and From view controllers) belong to the same storyboard. If not then you need to first get a reference to the storyboard your To view controller belongs to. So you need to do this:
#IBAction func _hitPeople(sender: AnyObject) {
let stotyboard = UIStoryboard(name: "YourStoryboard", bundle: nil)
let mapViewControllerObejct = storyboard?.instantiateViewControllerWithIdentifier("peoplenew") as? ViewControllerPeopleNew
self.navigationController?.pushViewController(mapViewControllerObejct!, animated: false)
}
PS: Just be sure that your From view controller is in the navigation stack i.e. self.navigationController is not nil.
I have a view controller which is triggered from a UITabBarController (which is the root of my app) if a parse session doesn't exist.
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.initialiseLogin()
}
func initialiseLogin()
{
if (PFUser.currentUser() == nil) {
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let vc: UIViewController = storyboard.instantiateViewControllerWithIdentifier("LoginView") as! UIViewController
self.presentViewController(vc, animated: false, completion: nil)
}
}
Which works great. However the problem i'm having is how do i trigger this code when a logout is called from a child view controller in the tab bar controller
#IBAction func logoutAction(sender: AnyObject)
{
PFUser.logOut()
// ... what should i call here...
}
Protocols and delegates might be what you're looking for:
The Swift Programming Language - Protocols
Essentially, you can declare your UIViewControllers to conform to a protocol. Then set the delegates in your root view controller (or wherever you do your initialisation)
Then, you can do something like this:
#IBAction func logoutAction(sender: AnyObject)
{
PFUser.logOut()
delegate?.loggedOut()
}