Tab Bar Controller - change data if segue was initiated from tab bar - ios

I'm trying to do something which seems simple, but I am having difficulty. I have a tab bar application with a setting page which is a table view. The user can change the settings by selecting a row, which pushes a new table view with new setting options. When the user selects a new setting, I send the information back to the main table view and populate the table view with the new setting.
The problem : I also have a SAVE button. If the user does not save the new settings I want to discard them. If the user selects a new tab (without saving settings) and then selects the settings tab, the last data the user entered is still in the tableView. I understand this is because viewdidload is not called again after the view has been created.
Basically I want logic like this :
If segue was initiated by clicking a tab bar icon : load the table view using dataModel.
Else if the previous screen was the data entry screen : Load the table view using user selected data.
If I wasn't using a tab bar controller I would do this by loading the dataModel using viewdidload and loading the tableview using delegate methods. The problem is I can't use viewdidload. I also can't use viewwillappear because it is called both when opening the screen from the table view and when popping the entry view controller off the stack.
I tried to set up the delegate method for the Tab Bar Controller in AppDelegate
func tabBarController(tabBarController: UITabBarController, didSelectViewController viewController: UIViewController) {
if (tabBarController.selectedIndex == 2)
{ let navController = viewController as! UINavigationController
let settingsTableViewController = navController.viewControllers[0] as! SettingsTableViewController
settingsTableViewController.loadFromDataModel = true
println("In did select view controller in app delegate")
}
}
And if loadFromDataModel = true then I load from the data Model in the Settings Table View Controller. Here is the problem - this only works if I go back and forth from the tabs twice. So weird. I put in println statements and it seems like execution is happening in the following sequence:
ViewWillAppear is being called in SettingsTableViewController
tabBarController:didSelectViewController is being called from the
AppDelegate (and the variable loadFromDataModel is updated - but it is too late because viewwillappear has already been called)
In Summary : Is this the best way to determine if the segue came from the tab bar? Why is viewwillappear on my SettingsTableViewController being called before the delegate method? Any suggestions on how to load the data from the data model each time the user selects the tab via the tab bar. Am I missing some obvious method? Thanks for any help!

By the time didSelectViewController is called, it's too late to do what you are looking to do. As you can tell by the viewDWillAppear delegate firing. Try moving your code to the shouldSelectViewController delegate of the tabBarController That should be early enough in the process to set your data.

Related

How to detect back button is pressed in the next viewController in a navigationController

I want to detect if back button is pressed in the next viewController in a navigationController.
Let's say I have VC_A and VC_B viewControllers in a navigationController. I know how to detect if back button is pressed in a current view controller but I do not know how to detect it in a previous viewController.
Edit:
I go from VC_A to VC_B and when I press back button in VC_B then I want to call a function in VC_A.
You could use notification center. This link has a nice tutorial: https://learnappmaking.com/notification-center-how-to-swift/
I want to detect if back button is pressed in the next viewController in a navigationController.
I'm not sure I understand this exactly, but it really doesn't matter much: in essence, you're talking about some view controller (call it controllerA), whose views aren't currently visible, finding out about a change that affects some other view controller (controllerB). The usual reason for needing such a thing is so that controllerA can update some data that it manages.
A better way to handle that is to have both controllers share a common data model. Any application state that's affected by something like a view controller being dismissed is shared data that should be part of the data model. controllerA really shouldn't care about whether controllerB's back button was tapped or not... that event is only the business of controllerB (and arguably the navigation controller that manages it). What controllerA should care about is updating its own views according to whatever changes happened while it was off screen, and those changes should be recorded in the model by controllerB and any other view controllers that might have been active along the way.
I'm suggesting you to do that with Notification Center like AglaiaZ suggested you. But if you're not feeling comfortable with using Notification Center, then try this more basic solution with viewWillAppear delegate method in viewController from which you're tracking are you back from B VC. So, let's go.
Set this variable in your current view controller class where you want to trigger method when the back button is pressed on the specific view controller, let's call that specific view controller B VC.
let isFromBViewController = false
Then in code block where you're triggering the transition to B VC set this variable to true.
func goToBViewController() { // This method is triggering transition from A VC to B VC
isFromBViewController = true }
And then in viewWillAppear delegate method check did current VC from which we triggered the transition to B VC have appeard from B VC.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if isFromBViewController {
// code for doing something when you got back from B VC
isFromBViewController = false
}}
And that's it.
But, again I'm suggesting you to use the notification center as #AglaiaZ suggested, the tutorial is easy, and with that tutorial I've also learned how to use Notification Center and how to create and use custom notifications.
Good luck.
If I understood correctly, you want to do something when the back button in the navigation bar at the current view controller is pressed, and the user is going back from the current B view controller to A view controller.
Put this line of code in the view controller in which you want to track when the user has pressed the back button.
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if isMovingToParent {
//your code when back button is pressed
}
}

Saving UIViewController state

I have a MainController with a button
Click button will pushViewcontroller to UsersViewController
In UsersViewController, I will load an table view of users with an selected cell to be highlighted
Ask: How could I save the state of tableview or state of the ViewController after hitting the back button from navigationcontroller, And the next time when a initiate the UsersViewController again, the table view will be at last state.
while traversing from the page, save the details in a Manager object, and next time, whenever you appear on the page, check whether the details are there in Manager object, if true, show the details from the object itself, otherwise, show them like you do as now.
You can try to avoid using a pushViewController / Navigation Controller by using a TabBarController for example but that might change the behavior of your app.
Or avoid your UsersViewController being deallocated and just initialize it once.

How to use Storyboard view controllers without reloading each time they show

I have several UIViewControllers in a Storyboard.
I'm not using a Navigation controller, just simple Segues to show them and to unwind.
However each time show/unwind a View, it completely reloads.
I would like it to "load" only the first time it's shown, and then
hide/show as I move to other View Controllers.
How can this be done using Storyboards and Swift 2?
Assuming that you'd always have a base (menu or something) UIViewController, I'd use UINavigationController and just keep optional references to created UIViewControllers in this menu controller, like :
var firstViewController : VC1?
var secondViewController : VC2?
You should create a function to get those view controllers like:
func getVC1Instance() {
return firstViewController ?? firstViewController = self.storyboard.instantiateViewControllerWithIdentifier("VC1Identifier")
}
Then, when you click a button that should take you to VC1 for example, you should do
self.navigationController?.pushViewController(getVC1Instance())
I don't know what your use case is, but I really don't recommend retaining UIViewControllers that aren't shown on the screen. If you need to persist their state, just use NSUserDefaults / Singleton pattern to store the data like text typed into some textfields and just use them on viewDidLoad.

XCTest Reference to View Controller not Assessable via RootViewController

I have a application with UIViewController "hierarchy" of this form:
Tab Bar Controller
- View Controller 1
- View Controller 2
- View Controller 3
* View Controller 4
I am using the XCTest Framework to write test methods for my iOS application. View controller 3 is a UITableViewController. When the user selects a row in the table, view controller 4 is shown.
I make a call to the application's window to get the root view controller, the tab bar controller for the application:
// get reference to the tab bar controller
let tabBarController = UIApplication.sharedApplication().delegate!.window!!.rootViewController! as! UITabBarController
But that only gets me access to the three view controllers corresponding to each tab. I checked the number of child view controllers for the root view controller and the number is 3. Makes sense. I checked the number of child view controllers for the 3rd view controller...zero. The view controllers are all setup via IB. I still need to somehow get access to the 4th view controller for my test. Is there a programmatic way to reach the 4th view controller?
A good night's grinding got me to the answer:
In my test code, I was making a call on the UITableView using this:
selectRowAtIndexPath:animated:scrollPosition:
But that method call does not trigger the UITableViewSelectionDidChangeNotification notification to get posted. Since my application's code does use the UITableView delegate function that depends on that notification's posting, I instead just did this:
// get reference to the table view
let tableView = tableViewController.view.viewWithTag(300)! as! UITableView
tableView.delegate!.tableView!(tableView, didSelectRowAtIndexPath: indexPath)
Doing the row selection this way triggered my table view's delegate method to be called and thus the
performSegueWithIdentifier:sender:
inside of that method was called.
The view controller responsible for displaying the site details was loaded and I was able to get a reference to it using the presenting table view controller's method:
let detailViewController = tableViewController.presentedViewController!
This seems to work and I am satisfied thus far...

SWIFT UINavigationController embedded in a containerView

I have a UIContainerView inside of a ViewController which will be presented as a popoverViewController.
There is a UINavigationViewController embedded inside the containerView.
there is tableViewController embedded in the navigationView which performs a segue on cell selection.
like so
when a user clicks on My sessions from the Settings table view controller a delegate method on the (Main profile View Controller)get called to update the preferredContentSize of the popoverViewController.
and when the custom backButton (<) on the UITableviewcontroller the popoverView will change size again. i managed to get it all to work via delegation
however with that being said it only works once... the delegate method from the settings table view controller doesn't get called anymore.
i think after the setting view controller performs a segue on its own the delegate to the main profile view controller is lost and doesn't get called again.
this is how it looks in run time
now the size changes on click of my sessions and returns to original size after user taps on backbutton
but afterwards the delegate method doesn't get called and the view doesn't change size like so
i am using SWIFT 2.0 and Xcode 7.01 any solution on how to retain the delegate method would be really appreciated.
Ok so i solved it by not using the delegates method at all.
When the view controller that is supposed to change the screen size is presented i use
func viewWillAppear(animated: Bool) {
super.viewWillAppear(true)
presentingViewController?.preferedContentSize.width = 500
}
and i reverse it in viewWillDisappear
i hope it helps

Resources