How to pass data back to the original UINavigationController - ios

I would like to pass some data from the last Table View to the First View. How can I do grab a hold of the First View object? I'm familiar with delegate pattern.
From the First View, I'm using Style Modal to invoke the Table View.

Create an object to manage your data model outside of your controller structure (singleton or owned by the application delegate). Update it when you have new data and read from it when you want to display something. Then, instead of having to make links between controllers, all you need to do is remove the one or ones you don't want and let the one you go back to decide what to show.

to get your rootViewController of your navigation controller, you can try this
NSArray *viewControllers = self.navigationController.viewControllers;
YourRootViewController *rootViewController = (YourRootViewController *)[viewControllers objectAtIndex:viewControllers.count - 2];
and add your data to rootViewController

Related

What is right way to display a detail view?

I'm new to iOS, coming from a PHP/MYSQL background, and am trying to show a detail view of a table.
I have created a simple NSMutableArray to hold some records as we say in web programming. I am able to display these in a table view and have it working so when you touch a row, you go to the detail page.
But I am totally flummoxed by how you get the detail view to display data using the MVC model and have scoured the web to no avail trying to find a simple example how to do this. I have tried numerous tutorials and viewed numerous videos but can't get the hang of it, because I don't understand the principle involved. I want to pull the data for the record clicked on, but don't know to pass this to the detail view.
The NSMutableArray is created in the tableview controller. Trying to follow MVC, I have also created a model class that lists some properties corresponding to the field names held in the array.
What I can't figure out is how to display this in the detail view.
Does it have something to do with Prepare for Segue? However, that would seem to violate MVC as the data for the detail page is actually in the table view page i.e. a different controllers.
I'm desperate for some guidance...
I don't know what code to try as none has worked for me. I just need a basic example of how to do this seemingly simple routine task.
Create a property in your DetailViewController then set the property in your TableViewController's as follows in its 'didSelectRowAtIndexPath'
DetailViewController *dvc = [DetailViewController alloc] init]; //Can be instantiated via the storyboard too.
OR
DetailViewController *dvc = (DetailViewController *)[self.storyboard instantiateViewControllerWithIdentifier:#"DetailViewControllerIdentifier"]; // Do not forget to set the identifier in the storyboard for the viewcontroller.
then,
dvc.recordsArray = self.recordsArray //setting the recordsArray property of the DetailViewController object to point to the TableViewController recordsArray
[self.navigationController pushViewController:DVC animated:YES];
[self.TableViewController deselectRowAtIndexPath:indexPath animated:YES];

How do I Pass An Array Between Two Views Using the SWRevealViewController?

QUICK BACKGROUND:
I have an app that shows a list of songs in a searchable, tabbed interface (a.k.a. "Cheers View Controller"). There are two tabs, one for each category of my cheers. I have a sidebar that allows you to select menu options, one of which is called "My Favorites". It uses the SWReveal Controller (a.k.a. "Sidebar"). I am trying to allow the user to mark favorite cheers before game time, then use the sidebar navigation to quickly reference their list of favorite songs when they're in the dugout.
The initial view controller for my application is a Reveal View Controller.
The front view (sw_front) is the tab bar controller that shows the Cheers View Controller when the app first launches. The data is loaded into an array form a CSV file in the ViewDidLoad of the Cheers View Controller.
The rear controller (sw_rear) is the Sidebar that defines the menu options for my application. One of the menu options on my Sidebar is called My Favorites. It's this sidebar that is conceptually throwing me. See below for a snapshot of the relevant area of my storyboard.
STORYBOARD SNAPSHOT:
It turns out that as a new user I cannot post images. :(
GOAL
I am trying to access the array of favorite cheers in the Cheers View Controller from the My Favorites view controller. There is currently no segue from Cheers View Controller to My Favorites and it seems like defining a segue here is wrong conceptually.
I don't want to use a singleton or some other work around, I would rather structure my app the correct way. One easy solution is to move My Favorites over to another tab of the Tab Bar Controller. While I can change the design and things would probably be easier, it feel like I'm missing a valuable concept here that I can use in later apps.
I realize that there are many posts already on the topic of passing data from one controller to another. I've read what I think is all of them but I have not been able to incorporate the solutions into the SWRevealController implementation successfully yet. So clearly I'm confused either on mechanics or on concepts (or both). Most of those posts I read were involving segues or passing fields between two controllers that had segues or relationships of some sort.
MECHANICS QUESTIONS:
I currently prep the favorites array in the ViewDidLoad of the table view controller in the Main Tabbed Interface. I did this immediately after loading the main array with all the songs from a CSV file. I was thinking that I could somehow pass this to the My Favorites table view controller or access it from the My Favorites Table view controller.
//Copy favorite cheers into array
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF.favorite contains[c] %#",#"Yes"];
NSArray *tempArray = [self.cheersArray filteredArrayUsingPredicate:predicate];
self.myFavoriteCheersArray = [NSMutableArray arrayWithArray:tempArray];
Here's where it gets sticky for me - when the user clicks the button to show the SideBar, then clicks on My Favorites:
the prepareForSegue of the Sidebar will execute with this:
- (void) prepareForSegue: (UIStoryboardSegue *) segue sender: (id) sender
{
// Get the selected row and identify the destination controller you are sending to
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
UINavigationController *destViewController = (UINavigationController*)segue.destinationViewController;
// Set the title of navigation bar by using the menu items
destViewController.title = [[_menuItems objectAtIndex:indexPath.row] capitalizedString];
if ( [segue isKindOfClass: [SWRevealViewControllerSegue class]] ) {
SWRevealViewControllerSegue *swSegue = (SWRevealViewControllerSegue*) segue;
swSegue.performBlock = ^(SWRevealViewControllerSegue* rvc_segue, UIViewController* svc, UIViewController* dvc) {
UINavigationController* navController = (UINavigationController*)self.revealViewController.frontViewController;
[navController setViewControllers: #[dvc] animated: NO ];
[self.revealViewController setFrontViewPosition: FrontViewPositionLeft animated: YES];
};
}
And I was trying to add this to the end of the prepareForSegue to somehow allow My Favorites access to the array...but I'm having trouble identifying the property in the Cheers View Controller from the Sidebar...
if([segue.identifier isEqualToString:#"myFavorites"]) {
//Get the Favorites View Controller (which has a navigation controller)
MyFavoritesViewController *myFavoritesViewController = [[MyFavoritesViewController alloc] init];
myFavoritesViewController = (MyFavoritesViewController*)destViewController.topViewController;
//THIS BELOW PART IS SHAKEY AT BEST
//Get the Cheers View Controller (which is part of a tabbed view controller)
SWRevealViewController *frontViewController = [[SWRevealViewController alloc] init];
CheersViewController *cheersViewController = (CheersViewController*)frontViewController.frontViewController;
//Pass the Favorites Array -- The below code gives me an error that the property I set on the CheersViewController is not found.
rankingViewController.favoriteCheers = CheersViewController.myFavoriteCheersArray;
}
}
CONCEPT QUESTIONS I'D LOVE COMMENTARY ON:
What is the best (by best, I mean a balanced approach between implementation reality and academic purity) approach to make the array available to another view controller when a segue is not defined without butchering OO concepts/designs?
The rear view controller (the Sidebar) does not get loaded on app startup - instead it gets loaded on first display. Is it correct that I am therefore loading the data for the first time in the Cheers View Controller (i.e. I could make the Sidebar load on app startup but this seems conceptually wrong as well since the user may never access the Sidebar)?
Is it best to prep the my favorites array when the app first launches in the Cheers View Controller or only when My Favorites is first viewed? The list is rather small but technically if the user never clicked on the sidebar to view their favorites then I would be loading an extra array unnecessarily - probably a nit since the array is small but I am curious.
Is the proper place to load the data where I have it currently?
It seems to me that a delegate conceptually wouldn't make sense since the Cheers View Controller doesn't really need to be aware of a user viewing his/her favorites. All I'm really doing is trying to query a set of master data. Am I correct in thinking this way?
Here is how these were solved. They work - as for whether or not they are the best, I'll let you know as I learn more. grin
QUESTION 1: Load an initial set of data from a plist, CSV or any other source file in AppDelegate didFinishLaunchingWithOptions. For example:
//Read in from JSON file
NSError *error;
NSData *data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"Cheers" ofType:#"json"]];
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
self.cheersArray = json[#"cheers"];
This array can be immutable (i.e. NSArray) and you can cast this to a view controller who can manipulate it as an NSMutableArray if needed. For example:
//Instantiate initial view controller
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:[NSBundle mainBundle]];
CMViewController *cmViewController = [storyboard instantiateInitialViewController];
//Pass array of data into a table view controller
cmViewController.tableData = (NSArray *)self.tableData;
This assumes your View Controller has a property that can be used to pass the data into.
If you had a JSON file and need it to be a PLIST, there's a great utility via Terminal on your Mac
plutil -convert xml1 InputFileName.json -o OutputFileName.plist
As you learn like I did you may realize plists with a smaller number of rows is preferred over JSON files and a tad bit easier (personal opinion) to deal with when it comes time to saving changes to the data (if your app allows that).
QUESTION 2: It is conceptually wrong from an academic point of view to load the Sidebar on app startup. It should only be loaded on first use.
QUESTION 3:Similar to Question 2, It is best to pass the favorites array to the view controller that needs it on first request (i.e. the user presses on the tab "Favorites". Since technically Favorites is just a specific view of a subset of the master data I am loading, I believe this is correct.
QUESTION 4:I moved it form the first view controller to the App Delegate as shown above. This gave me more flexibility to pass data down (conceptually speaking from a parent to a child view controller) rather than across (form child to child view controller). This simplified my code.
QUESTION 5: I don't believe a delegate is appropriate for the answer to the challenge I was facing.
Feel free to comment!

Using a UINavigationController for a set path without Back options

I am using a UINavigationController within my app (as expected) for a specific path the user takes when taking a turn in my turn based game.
When they move through the turn controllers they do not actually have the option to go Back from the Navigation Controller. This is planned/expected behaviour.
My question is, is it best to keep the other controllers on the UINavigationController stack when they are not going to be used again.
Should they be de-alloced immediately, or wait for the whole turn to be complete and let them go when the navigation controller goes (how it is at the moment). Some of the controllers hold data/images etc as properties so I am wondering if it would be more efficient to get rid of these on the fly?
If it is, what is the best method to load new controllers into the UINavigationController at present I am using self performSegue... or buttons that push to the VC from the storyboard setup.
You can manipulate navigation controller viewControllers using this code
NSMutableArray *viewControllers = self.navController.viewControllers;
//remove or add in the array
[self.navController setViewControllers:viewControllers];

How can I keep a view controller in memory after its popped from the stack?

This is an embarrassingly simple question, but I need to preserve a view controller and my current solution is not a solution.
I have a slide out table view menu that lets me select a new view controller to push to the forefront. When I select a view, it deallocs the old main view controller to push the new one. Since 90% of my functionality revolves around one view controller, I want it to stay in memory so I don't have to constantly spend resources to allocate it and either pull the last data source from core data or make a network request.
I naively tried to set a placeholder temporaryMainViewController and assign it to my center view controller before it is assigned another VC, but assigning the current main view controller to the temporaryMainViewController simply assigns the address of the main view controller- so when it's changed, so is my tempVC.
Trying to copy the view controller causes a crash.
So how can I effectively do self.temporaryMainViewController = self.currentCenterViewController; where the temp controller is assigned by value of object and not value of address?
-- EDIT --
More info:
ECSlidingViewController keeps (in my case) 3 controllers in memory- the top/center/main view controller, a left hidden controller, and a right hidden controller. My left hidden controller is a tableview, LeftMenuTableViewController , where each row, when selected, observes the view controller class I've associated with that indexPath then instantiates an instance of that class and sets it to the topViewController with a simple assignment statement. I want to keep only my initial top view controller (of class PlacesNavigationViewController (which holds PlacesTableViewController)) in memory when a new view controller is assigned to the top view.
My first approach was to declare a placeholder property in LeftMenuTableViewController, since it never leaves memory itself.
#property (nonatomic, strong) UIViewController* temporaryViewController;
then in didSelectRowAtIndexPath:
// make local variables for storyboard and the identifier of the view controller that will be pushed, then..
self.temporaryViewController = self.topViewController;
self.topViewController = [storyboard instantiateViewControllerWithIdentifier:newViewControllerIdentifier];
but this fails because the temporary controller is assigned the memory address of the topViewController- which in the next line is given a new view controller to hold.
So what I need is a way to hold the contents of topViewController so that when topViewController changes, I still have the old VC in memory.
I'm likely forgetting some obvious tenet of Objective-C, but this is giving a good bit of trouble. Let me know if I did not make something clear.
You want to use viewControllers property of UINavigationController class. The documentation is here. Basically, you can just swap 2 view controllers in viewControllers array and then use setViewControllers:animated: to make the transition.
Hope this helps!
Cheers!

UITabBarController seems to open one view before closing another

I have a method that is called when a Settings button is tapped in my root view, that subclasses two UIViewControllers, attaches them to a UITabBarController and pushes the UITabBarController onto a navigation stack:
-(IBAction)onSettings:(id)sender {
// Create the Settings Views
SettingsViewController *vcSettings1 = [[Settings1ViewController alloc] initWithNibName:#"Settings1ViewController" bundle:nil];
Settings2ViewController *vcSettings2 = [[Settings2ViewController alloc] initWithNibName:#"Settings2ViewController" bundle:nil];
// Create the Tab View
UITabBarController *tabController= [[UITabBarController alloc] init];
tabController.viewControllers = #[vcSettings1,vcSettings2];
// Pass the Index of the database on to the views so they can pull the record from the database
vcSettings.recordIndex = recordIndex;
vcSettings2.recordIndex = recordIndex;
// Add the tab bar controller to the navigation stack
[self.navigationController pushViewController:tabController animated:YES];
}
In each of the Settings views, I override the viewWillAppear method to load a row of data from an sqlite database at primary key recordIndex. (Both views pull the same record and display different data from the record, except one field is the same on both.)
I also override viewWillDisappear in each view to save the controls data back to the database.
I can verify that every time I switch views using the tab bar, the viewWillDisappear method is called on one closing view and the viewWillAppear is called on the opening view.
The problem is that when I change data on the first view and switch to the second view, the data is not changed on the second view unless I return to the first view and then back to the second. As best I can tell, here is what seems to be happening:
View 1 is open. I change the data in the field.
I tap on the tab for View 2
viewWillAppear is called for View 2, populating the field in View 2 with the old data in the database.
viewWillDisappear is then called for View 1, saving the changed data to the database.
It seems that the opening view is calling viewWillAppear BEFORE the closing view is calling viewWillDisappear.
I have tried this other ways, such as using a singleton, and simply trying to modify the recordIndex from both views and in all cases it seems that the data is loaded from the opening view before it is saved from the closing view.
Is this a bug in the way UITabBarController works, or am I abusing viewWillAppear and Disappear in a way that I'm not supposed to? Has anyone else run across this behavior?
This is not a good design. There is no guarantee that viewWillAppear of new view should be called after viewWillDisappear from the previous one. Even if that were to work, there would be no guarantee that this would keep working in future iOS versions.
If performance is not impacted, a quick fix could be to save the changes to the database as they occur, you would always have an up to date database that could be accessed from any view in any circumstance.
Best design is to have some model classes, with your model objects accessible through a singleton for example. Those model objects are updated real time as you interact with the UI, they are the ones being accessed by the different views, and they are periodically saved using the method of your choice.
as a cheap solution you could load data in viewDidAppear but in general I agree with JP's answer

Resources