Swift - Keep instantiated view controllers when switching between them via side menu - swift

I have a side menu in my iOS app with several entries. Once I click on one of them, I want to segue to the corresponding view controller via pushing them onto the navigation stack. This is currently done in the following way:
// Called on click event on table cell
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.tableView.deselectRow(at: indexPath, animated: true)
// navigate to the corresponding view controller
switch(indexPath.row){
case 0:
let launchScreenNC = self.storyboard?.instantiateViewController(withIdentifier: "LaunchScreenNC") as? UINavigationController
self.navigationController?.pushViewController((self.launchScreenNC?.viewControllers.first!)!, animated: true)
break
case 1:
let connectionNC = self.storyboard?.instantiateViewController(withIdentifier: "ConnectNC") as? UINavigationController
self.navigationController?.pushViewController((self.connectionNC?.viewControllers.first!)!, animated: true)
break
case 2:
let syncNC = self.storyboard?.instantiateViewController(withIdentifier: "SyncNC") as? UINavigationController
self.navigationController?.pushViewController((self.syncNC?.viewControllers.first!)!, animated: true)
break
default:
// nothing to do
break
}
}
It works fine for switching the first time to a new view controller. However, once initiated I want to keep those instances alive to keep a state of variables associated with each view controller.
What is the best way to do this?
I tried with
self.navigationController?.popToViewController((self.syncNC?.viewControllers.first!)!, animated: true)
or saving them as instance variables within the menu view controller, for example. But none worked so far.
I appreciate any advice.

What is the best way to do this?
The best way is don’t. Once a view controller is popped, let it go out of existence. Preserve its state (the values of its instance properties) and use that to reconstruct the interface the next time; don’t preserve the view controller instance itself.

Related

How to navigate between tabs "TabBarController" and transfer data between them?

this code works almost as it should, but it opens a second window on top of the main one, and not separately. How do I programmatically navigate to another view controller?
let tableVC = self.storyboard?.instantiateViewController(withIdentifier: "SaveTableViewController") as! SaveTableViewController
tableVC.titleText = alert.textFields![0].text ?? "Test"
tableVC.tabBarController?.selectedIndex = 1
//self.tabBarController?.showDetailViewController(tableVC, sender: self)
//self.present(tableVC, animated: true, completion: nil)
Your tabBarController have a viewControllers array. If you want to show a specific ViewController programmatically you could set the selectedViewController attribute of your tabBarController to an element out of the viewControllers array...
For more detail: https://developer.apple.com/documentation/uikit/uitabbarcontroller/1621172-selectedviewcontroller

NavigationController nested in TabController animation bug

I try my best to explain what is happening.
I have updated the XCode to Version 10.1 (10B61)
And the iOS on my iPhone and Simulator is v12.1
My app has a TabController with 5 tabs.
First: Posts
Fifth: Profile Posts
These are embedded into a navigation controller (In case someone
clicks on the comments button)
So. I've noticed that if I run my app and I click the comments, it pushes that vc in a weird way to the screen, then clicking back just "bumps" back. Also slide back isn't working.
However, if I switch tabs first then everything works fine.
VIDEO:
https://www.youtube.com/watch?v=fgS3j21L8Js
As you see in the video everything is fine after switching to Profile Posts + back .
UPDATE 1:
So if I start my app, switch to another Tab, then back to the original, it works fine.
Requested code:
func commentsButtonTapped(sender: UIButton) {
let touchPoint:CGPoint = sender.convert(CGPoint.zero, to:self.tableView)
if let indexPath = tableView.indexPathForRow(at: touchPoint) {
openDetails(indexPath: indexPath, shouldShowKeyboard: false)
}
}
func openDetails(indexPath: IndexPath, shouldShowKeyboard : Bool) {
if (self.tableView.cellForRow(at: indexPath) as? WorldMessageCell) != nil {
let storyboard = UIStoryboard(name: "Additional", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "DetailsViewController") as! DetailsViewController
vc.postId = PostIds.shared.nearby.ids[safe: indexPath.row]
vc.shouldShowKeyboard = shouldShowKeyboard
self.navigationController?.pushViewController(vc, animated: true)
}
}
UPDATE 2:
Solved the problem by forcing the TabController to switch between tabs..
override func viewDidAppear(_ animated: Bool) {
self.selectedIndex = 1
self.selectedIndex = 0
}
But that's not how it should work..
UPDATE 3:
I have tested it, if I make the navigation controller->vc the initial vc (so no tab controller) everything works fine.
But as soon as the navigationcontroller is nested inside the tab, it happens.
I made a new project to test if this is a version specific bug but no, everything works fine there. So the issue must be with my app.
What could generate issue like that (in the video)?
Ohh.. I have found the problem & bug:
So if you have navigation controllers nested into a tab controller that calls it's viewDidLoad() function, then the navigation controller will have problems.
The code I had to remove totally:
override func viewDidAppear(_ animated: Bool) {
// here i had some code ... /
}
Now everything works..

Swift - How to pass UInavigationController and also pass variables

What is the best way to pass a UInavigationController and also pass variables to a new viewController. I know how to do one or the other but not both at the same time. Thank you in advance
this is my current code
func(){
let vc = storyboard?.instantiateViewControllerWithIdentifier("messagesViewController") as! UINavigationController
let posts = self.postList[indexPath.row]
//this is the var that i want to past
//vc.previousViewMessageId = posts.postKey
self.presentViewController(vc, animated: true, completion: nil)
}
If I understand you correctly, you have a view controller that can present a second VC. And this VC is embedded in a UINavigationController. What you don't know how to do, is to pass data from the first VC, to the navigation controller, then to the second VC.
Here is a brute force solution. It's not beautiful, but it works anyway.
Make your own UINavigationController subclass:
class DataPasserController: UINavigationController {
var previousViewMessageId: SomeType?
override func viewDidLoad() {
if let vc = self.topViewController as? YourSecondViewController {
vc.previousViewMessageId = self.previousViewMessageId
}
}
}
Now you can add a navigation controller in the storyboard, set its class to DataPasserController, and connect the second VC to it as its root view controller.
Now suppose you have got an instance of DataPasserController by calling instantiateViewControllerWithIdentifier, you can do this:
yourDataPasserControllerInstance.previousViewMessageId = posts.postKey
And present the instance!
To pass a value to your Navigation Controller's Root View Controller, you access viewControllers[0] and cast it to the class of your Messages View Controller (the controller that has the previousViewMessageId property):
func () {
let messagesNC = storyboard?.instantiateViewControllerWithIdentifier("messagesViewController") as! UINavigationController
let messagesVC = messagesNC.viewControllers.first as! MessagesViewController
messagesVC.previousViewMessageId = postList[indexPath.row].postKey
presentViewController(messagesNC, animated: true, completion: nil)
}
What you have there is simply presenting a view controller... You are skipping the navigation controller.
What you need to do is present the new view controller inside the navigation controller. Once you have done that, it will show correctly. You can also pass the variables after you've created the vc variable.
This presents the new viewController (vc) within the navigation controller...
self.navigationController?.pushViewController(vc, animated: false)
This sets the variable in the new viewController (vc) (you are correct)
vc.previousViewMessageId = posts.postKey
So complete:
func(){
let vc = storyboard?.instantiateViewControllerWithIdentifier("messagesViewController") as! MessagesViewController
let posts = self.postList[indexPath.row]
//this is the var that i want to past
vc.previousViewMessageId = posts.postKey
navigationController?.pushViewController(vc, animated: false)
}
PS. While not part of the question, I feel I should still mention... Use of the word self should be left to necessity only. In other words, don't use it when it isn't needed. for example self.postList[indexPath.row] :)
https://github.com/raywenderlich/swift-style-guide#use-of-self

Using UINavigationBarDelegate method shouldPopItem in Swift

I've been trying to figure out how to use the UINavigationBarDelegate method shouldPopItem with a popover in Swift. I've done lots o' digging around and trying this and that, with no success. I'm hopeful someone can point me in the right direction.
I start with a UIViewController that has a number of buttons the user can click. As an example, there is a button that calls back to this method:
#IBAction func manageVerbListsButtonPressed(sender: UIButton) {
vc = ManageListsViewController(nibName: "ManageListsView", bundle: nil)
vc.preferredContentSize = CGSizeMake(600, 600)
vc.modalPresentationStyle = .Popover
let popoverController = vc.popoverPresentationController!
popoverController.sourceView = sender
popoverController.sourceRect = sender.bounds
popoverController.permittedArrowDirections = .Left
popoverController.delegate = self // could you set the popover delegate to the vc, so the vc could control the dismiss?
presentViewController(vc, animated: true, completion: nil)
}
This correctly opens a popover with ManageListsView as the presenting controller. That controller has a view with a table. When the user clicks on a table row, the didSelectRowAtIndexPath method fires:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let vc = EditOneListViewController()
let vl = Lists[indexPath.row] // this is an array of list names
vl.isNew = false
vc.List = vl // give the next controller the list to edit
navigationController?.pushViewController(vc, animated: true)
}
Again, this correctly pushes the EditOneListViewController.
What I want to do, but haven't figured out how, is to use the UINavigationBarDelegate method shouldPopItem to determine whether the navController should pop back to the table view, depending on whether the user has done something on the EditOneListViewController view. That is, if the user has edited something and not saved it, I want to use the shouldPopItem to put up an Alert indicating that and return to the view so the user can save the edits. (The list has an isDirty boolean that I can test for whether it's been saved.) If the user has saved edits, I want the navController to pop back a level.
I have done this in Obj C in earlier iOS's, but I'm darned if I can figure out how to do it with iOS 9 and Swift and presentationControllers. Any help at all will be greatly appreciated.

Passing Core Data objects from UITableViewCell to another View Controller

Currently I have the application loading up the data in a tableview presented to the user that I want them to select the object they wish to view further details about. From their I want to be able to read out the data in the object to the user in various forms either labels or a tableview dependent on the data.
I have been reading around and trying to understand this stuff but it is all still new to me! I had another guy on here help me out with saving the data and passing the data around to store but reading it out is a seemingly different thing. Any examples in swift language would lead me down the right path I know that I need to use the didselectrow method and call a segue and also that I need a prepare for segue but I am not quite sure how it should look.
I have read multiple posts and some do actually attempt to pass objects but not in the manner in which I am trying to..Are you able to pass whole objects after they have been have been selected in a tableview to another view controller to present all data related to that object or are you only able to pass information from that object to the next viewcontroller? I have examples of prepareforsegue and perform segue before not sure what else I am missing but I am currently not able to pass any information between the tableview and the viewcontroller.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if (segue.identifier == "decktoRecord") {
var detailVC: StatWidgetViewController = segue.destinationViewController as! StatWidgetViewController
detailVC.deckRecord = decktoPass
}
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let indexPath = tableView.indexPathForSelectedRow()
let selectedCell = tableView.cellForRowAtIndexPath(indexPath!) as UITableViewCell?
self.performSegueWithIdentifier("decktoRecord", sender: indexPath);
}
decktoPass is just a variable that contains the type of data entity that is the primary entity of the object.
A little confused by what you're asking but from what I understand, once the user clicks on a cell you want to segue to another view controller where they can edit the the details?
To add an exception breakpoint, open up your left panel with all your files/viewcontrollers, at the very top of it should be a small panel with a few icons, the first one is a folder, click on the second last one (the one that looks like a tag)
click on the plus in the bottom right and click add exception breakpoint, this should let you know where the problem in your code is occurring
Okay to edit the details in another View controller start by preparing the segue from the original view controller
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showTaskDetail" {
let detailVC: TaskDetailViewController = segue.destinationViewController as! TaskDetailViewController
let indexPath = self.tableView.indexPathForSelectedRow()
let thisTask = fetchedResultsController.objectAtIndexPath(indexPath!) as! TaskModel
detailVC.detailTaskModel = thisTask
detailVC.delegate = self
}
else if segue.identifier == "showTaskAdd" {
let addTaskVC:AddTaskViewController = segue.destinationViewController as! AddTaskViewController
addTaskVC.delegate = self
}
}
okay so like the code is showing above, I have a segue called showTaskDetail which shows the details of whatever it is, in my case its a simple task. You said you want to edit this information in another view controller when a user clicks on the row, well you need to be able to get this information in the other view controller.
So create a variable in the other viewcontroller that will hold these values, for me i called it detailTaskModel
var detailTaskModel: TaskModel!
Incase you're confused about what TaskModel is, I'm using CoreData to store my data, and TaskModel is a NSMangedObject class.
let detailVC: TaskDetailViewController = segue.destinationViewController as! TaskDetailViewController
this line you're just specifying what your other view controller is, replace TaskDetailViewController with your swift class.
let detailVC: TaskDetailViewController = segue.destinationViewController as! TaskDetailViewController
this line fetches the data from the row selected
Now you should be able to pass your information into the other view controller and edit it using the detailTaskModel, do you need help with that as well?