De-initialzing a ViewController after dismissal? - swift

I have two viewControllers in my App, the code for the first viewController is as illustrated below:
import UIKit
class firstViewController: UIViewController {
// The below two variables will be passed from the firstViewController to the secondViewController then back again from the secondViewController to the firstViewController:
var selectedRowValue: Int = 0
var selectedSectionValue: Int = 0
let main = UIStoryboard(name: "Main", bundle: nil)
lazy var secondViewController = main.instantiateViewController(withIdentifier: "secondViewController")
override func viewDidLoad() {
super.viewDidLoad()
}
// The below function will be triggered when the user tap on a specific tableView cell detailClosure icon. This is when the needed data get sent from this viewController to the secondViewController:
func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
let secondViewControllerProperties = secondViewController as! secondViewController
secondViewControllerProperties.receivedSelectedSectionValueFromFirstVc = indexPath.section
secondViewControllerProperties.receivedSelectedRowValueFromFirstVc = indexPath.row
// The below is the relevant content of a UILabel inside the tapped tableView cell by the user that get send to the secondViewController for it to be displayed as its NavigationBar title:
secondViewControllerProperties.selectedUniversalBeamSectionDesignation = arrayWithAllDataRelatedToUbsSections.filter({ $0.sectionSerialNumber == "\(arrayWithAllSectionsSerialNumbers[indexPath.section])" }).map({ $0.fullSectionDesignation })[indexPath.row]
self.present(secondViewControllerProperties, animated: true, completion: nil)
}
}
// The below extension inside the firstViewController is used to pass data back from the secondViewController to the firstViewController:
extension firstViewController: ProtocolToPassDataBackwardsFromSecondVcToFirstVc {
func dataToBePassedUsingProtocol(passedSelectedTableSectionNumberFromPreviousVc: Int, passedSelectedTableRowNumberFromPreviousVc: Int) {
self.selectedRowValue = passedSelectedTableRowNumberFromPreviousVc
self. selectedSectionValue = passedSelectedTableSectionNumberFromPreviousVc
}
}
Below is the code inside the second view controller:
import UIKit
class secondViewController: UIViewController {
weak var delegate: ProtocolToPassDataBackwardsFromSecondVcToFirstVc?
// The below variables get their values when the data get passed from the firstViewController to the secondViewController:
var receivedSelectedRowValueFromFirstVc: Int = 0
var receivedSelectedSectionValueFromFirstVc: Int = 0
var selectedUniversalBeamSectionDesignation: String = ""
// Inside the below navigationBar declaration, its labelTitleText will depend on the tapped tableViewCell by the user inside the firstViewController:
lazy var navigationBar = CustomUINavigationBar(navBarLeftButtonTarget: self, navBarLeftButtonSelector: #selector(navigationBarLeftButtonPressed(sender:)), labelTitleText: "UB \(selectedUniversalBeamSectionDesignation)", navBarDelegate: self)
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(navigationBar)
}
// The below gets triggered when the user hit the back button inside the navigationBar of the secondViewController. This is where using the Protocol data get passed back to the firstViewController:
extension secondViewController: UINavigationBarDelegate {
#objc func navigationBarLeftButtonPressed(sender : UIButton) {
if delegate != nil {
delegate?.dataToBePassedUsingProtocol(passedSelectedTableSectionNumberFromPreviousVc: self.selectedTableSectionNumberFromPreviousViewController, passedSelectedTableRowNumberFromPreviousVc: self.selectedTableRowNumberFromPreviousViewController)
}
dismiss(animated: true) {}
}
}
However, what I am noticing is whenever the secondViewController gets dismissed when the user hit on the back button inside the navigationBar of the secondViewController. The secondViewController does not get de-initialized, and therefore, whenever I press on a different cell inside the tableView inside the firstViewController, the navigationBar title that gets displayed inside the secondViewController is still the same as the one displayed when I pressed the first time. Since the secondViewController did not get de-initialzied and thus, I am seeing the same values as the first time it got initialized.
My question is how to de-initialize the secondViewController when it gets dismissed, so that every time I tap on a different cell inside the tableView inside the firstViewController a new secondViewController gets initialized?

Your code generates secondViewController once and reuses it (it's a property).
lazy var secondViewController = main.instantiateViewController(withIdentifier: "secondViewController")
It means it will live until the first view controller is destroyed, and of course - will be reused.
Instead, you should create it as needed.
func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
// Create the second view controller
let secondViewController = main.instantiateViewController(withIdentifier: "secondViewController")
let secondViewControllerProperties = secondViewController as! secondViewController
secondViewControllerProperties.receivedSelectedSectionValueFromFirstVc = indexPath.section
secondViewControllerProperties.receivedSelectedRowValueFromFirstVc = indexPath.row
// The below is the relevant content of a UILabel inside the tapped tableView cell by the user that get send to the secondViewController for it to be displayed as its NavigationBar title:
secondViewControllerProperties.selectedUniversalBeamSectionDesignation = arrayWithAllDataRelatedToUbsSections.filter({ $0.sectionSerialNumber == "\(arrayWithAllSectionsSerialNumbers[indexPath.section])" }).map({ $0.fullSectionDesignation })[indexPath.row]
self.present(secondViewControllerProperties, animated: true, completion: nil)
}
}
Remove the lazy var of course, it is no longer needed.
Also, you could just do:
let secondViewController = main.instantiateViewController(withIdentifier: "secondViewController") as! SecondViewController instead of casting it later, it's a bit cleaner.

Related

Xcode Cannot go back to TableViewController in navigation stack

I have controller1 -> TableViewController2 -> TableViewController3 in my storyboard. When I press a button in controller1, I want to jump to TableViewController3 and from there when I select a row, I want to go back to TableViewController2 and get some data and then go back to controller1.
In controller1 instantiate TableViewController3:
if segue.identifier == "MySegue" {
let viewController:TableViewController3 = segue.destination as! TableViewController3
viewController.addLocationToMap = true
}
In TableViewController3 instantiate TableViewController2 like this:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if addLocationToMap == true {
let navc:UINavigationController = self.navigationController!
let tvc:UITableViewController = TableViewController2()
let rootView = navc.viewControllers[0]
navc.setViewControllers([rootView, tvc], animated: true)
return
}
In TableViewController2 viewDidLoad, it fails in ViewDidLoad when I try to set a text field value because the textfield is nil. It cannot be because Textfield is already in the view. Looks like the TableViewController2 view never got loaded.
in TableViewController2
override func viewDidLoad() {
super.viewDidLoad()
locationPurposeTextField.text = "sometext"
}
Fails when setting text value because locationPurposeTextField is nil.
How to fix this?
EDITS:
On pressing a button In controller1:
let navc:UINavigationController = self.navigationController!
let alTvc = self.storyboard!.instantiateViewController(withIdentifier: "AddLocationID") as! UITableViewController
navc.pushViewController(alTvc, animated: false)
let cListTvc = self.storyboard!.instantiateViewController(withIdentifier: "ContactListID") as! UITableViewController
navc.pushViewController(cListTvc, animated: true)
The code takes me to TableViewController3 with storyboard ID: ContactListID as desired.
Next, in TableViewController3
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.myDelegate?.userSelectedContact(contactIdentifier: self.contactId, contactFullName: fullName, contactSelected: true)
let navc:UINavigationController = self.navigationController!
let tvc:UITableViewController = self.storyboard!.instantiateViewController(withIdentifier: "AddLocationID") as! UITableViewController
let rootView = navc.viewControllers[0]
navc.setViewControllers([rootView, tvc], animated: true)
}
Takes me to TableViewController2 with storyboard ID: AddLocationID
Based on the delegate sent TableViewController3, in ViewDidappear method of TableViewController2, I set some text in the view and call tableview.realoadData(). It does not load the view.
However if I select a button in TableViewController2 and load TableViewController3 and then comeback to TableViewController2 upon execution of the very same method didSelectRow it reloads the view in TableViewController2
How to fix it, please let me know?
There are a couple of things wrong with your approach.
If you want to go from controller1 to TableViewController3 but have your navigation stack contain controller1 -> TableViewController2 -> TableViewController3, you will have to have the button in controller1 first push TableViewController2 without animation, and then push TableViewController3 with animation. (You won't be able to have your button trigger a segue.)
Second problem: You can't create a copy of your TableViewController2 by just invoking it's initializer (TableViewController2()). When you do that it doesn't load its views from the storyboard. Instead, you should assign a storyboard identifier to it and use the UIStoryboard method instantiateViewController(withIdentifier:) to create a new instance of it.

Unexpectedly found nil when sending data with Delegates and protocols pattern

I have 2 viewcontrollers, firstVC and secondVC. I want to send data from the first one to update the secondVC's variable and UI.
I want to send tableView's indexPath.row with delegate when it is tapped in didSelectRowAt.
This is what I try, but when selecting a row the app crashes with error:
Unexpectedly found nil while implicitly unwrapping an Optional value
I tried to debug and saw that the delegate is nil, even if I put a method to make the delegate firstVC.
SecondVC:
func setupDelegate() {
let FirstVC = storyboard?.instantiateViewController(identifier: "FirstVC") as! FirstVC
FirstVC.selectionDelegate = self
}
}
extension SecondVC: setSelectionDelegate {
//this is never executed
func didChoose(index: Int) {
lbl.text = String(index)
}
}
FirstVC:
protocol setSelectionDelegate {
func didChoose(index: Int)
}
var selectionDelegate: setSelectionDelegate!
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let secondVC = UIStoryboard.init(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier: "secondVC") as? secondVC
secondVC?.setupDelegate()
self.selectionDelegate.didChoose(index: indexPath.row)
}
Each time you call instantiateViewController explicitly, you get a new instance of the view controller and this is NOT the same instance that you are seeing in your device/simulator.
With storyboard segues ( embed or present modally... ) you have override prepare(...) function to get a reference to desired view controller instance.

Swift - How to use pushViewController in NSObject

I have a slide in/out side menu which is called when leftBarButtonItem in nab bar is tapped.
And I coded the slide menu with NSObject and I know NSObject doesn't have the pushViewController method.
navigationController?.pushViewController
I have a menu in UITableView in the NSObject and I want push viewController.
How can I make this work? Thank you.
import UIKit
class SlideMenuLauncher: NSObject, UITableViewDelegate,
UITableViewDataSource {
...
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let destinationVC: UIViewController
switch indexPath.row {
case 0:
destinationVC = ControllerA()
case 1:
destinationVC = ControllerB()
default:
destinationVC = HomeController()
}
self.navigationController?.pushViewController(destinationVC, animated: false)
}
...
1.
You can make a #property of your parentVC in SlideMenuLauncher class.
var parentVC: UIViewController?
and then you can use this instead of self.
parentVC!.navigationController?.pushViewController(destinationVC, animated: false)
2.
You can post a notification instead of pushing from SlideMenuLauncher class and pass the destinationVC as object in notification. Observe this notification in your parentVC then fetch the destinationVC from notification object and push the controller.
3.
You can make a block or delegate of didSelectRowAt event.
Block Example:
/// Declare a block in `SideMenuLauncher`
typealias TableEventBlock = (_ controller: UIViewController) -> Void
var tableEventBlock: TableEventBlock?
/// In table did select method
if tableEventBlock != nil {
tableEventBlock!(destinationVC)
}
You need to define its call back in parentVC (You can define it anywhere, Do it in viewDidLoad:) and will have to use SideMenuLAuncher's instance.
sideMenuLauncherInstance.tableEventBlock = { controller in
self.navigationController?.pushViewController(controller, animated: false)
}

pass data to previous view without segue,#IBaction swift

I have a View1 when i click on textbox i am going to view2(table view) to pick a value. I want to send the picked value to view1 for that textbox.
The controls are created programmatically so i am not using segue,IBActions.
I am trying to use the protocol methods still no success. Here is what i have tried.
class DynamicSuperView: UIViewController,UITableViewDelegate,UITableViewDataSource,dropdownDelegate,UITextFieldDelegate
{
func setValue(value:AnyObject)
{
print("dynamic view delegate method executed")
self.labelText = value as! String
//return selectedValue;
}
override func viewDidLoad() {
}
}
The second class is here with delegate method..
protocol dropdownDelegate {
func setValue(value: AnyObject);
}
class testClass: UIViewController,UITableViewDataSource,UITableViewDelegate,UISearchBarDelegate {
var delegate:dropdownDelegate! = nil
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let vcName = names[indexPath.row]
print ("Table view cell clicked and value passed: \(vcName)")
if let cell = tableView.cellForRow(at: indexPath) {
cell.accessoryType = .checkmark
}
self.navigationController?.popViewController(animated: true)
delegate.setValue(value: "Testing delegate")
}
}
Once i select the value in the tableview i am calling the delegate method and trying to pass that value but no success.
I don't want to create the new instance of the previous view controller because i will lose the data already entered by the user, so i am popping the current view controller and going to the previous view controller.
Please suggest if my approach i correct or not?
ERROR:
fatal error: unexpectedly found nil while unwrapping an Optional value
Thank you in advance
Try this:
view func ButtonPressed() {
print("Button Pressed!!") // This will create the new instance of the view controller.
let vc = UIStoryboard(name:"Main", bundle:nil).instantiateViewController(withIdentifier: "Storage") as! testClass
vc.delegate = self
self.navigationController?.pushViewController(vc, animated:true)
}
You are not setting the delegate to first view controller.

Passing data thru a Swift segue works every other time

I have a tableview which reads and RSS feed of episodic radio shows. I want the playlist for the selected show to pass to a second controller for viewing in a textview when a cell is selected. I am using a segue and it works when I select the same cell twice (every other time). I have searched everywhere without success and its driving me nuts! Please help. Here is my code
// Only grab the data at the selected cell
//
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
// Load the variable to hold whats in the row
currentList = feeds.objectAtIndex(indexPath.row).objectForKey("itunes:summary") as NSString
// Load the row number
myRow = (indexPath.row)
}
// Pass the data thru the segue
override func prepareForSegue(segue: (UIStoryboardSegue!), sender: AnyObject!) {
if (segue.identifier == "mySegue") {
// var vc = segue.destinationViewController as secondViewController
// vc.toPass = currentList
// println(vc.toPass)
let vc = segue.destinationViewController as secondViewController
let indexPath = self.tableView.indexPathForSelectedRow
vc.toPass = currentList
}
}
}
Here is the code from my second view controller
import UIKit
class secondViewController: UIViewController {
// Create a property to accept the data
#IBOutlet weak var textPlayList: UITextView!
// Create a variable to store the data
var toPass:String!
override func viewDidLoad() {
super.viewDidLoad()
textPlayList.text = toPass
textPlayList.textColor = UIColor .whiteColor()
textPlayList.font = UIFont .boldSystemFontOfSize(10)
}
}
The problem is that prepareForSegue happens before didSelectRowAtIndexPath so your currentList variable is set up too late for be useful in prepareForSegue.
To fix this, move this code:
// Load the variable to hold whats in the row
currentList = feeds.objectAtIndex(indexPath.row).objectForKey("itunes:summary") as NSString
to prepareForSegue:
override func prepareForSegue(segue: (UIStoryboardSegue!), sender: AnyObject!) {
if (segue.identifier == "mySegue") {
let vc = segue.destinationViewController as secondViewController
if let indexPath = self.tableView.indexPathForSelectedRow() {
let currentList = feeds.objectAtIndex(indexPath.row).objectForKey("itunes:summary") as NSString
vc.toPass = currentList
}
}
}
In general, you don't need didSelectRowAtIndexPath if you are using segues because prepareForSegue is where you set up the transition.