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!
Related
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];
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
Question: How do I perform custom initializations when using StoryBoards?
I have read several questions with the same keywords that are in my title, but could not find any answer. I beg your pardon if this question has already been asked or it is too basic: I have just started developing iOS apps.
Currently I have an application whose:
AppDelegate reads from a source (currently a remote URL) two URLs that need to be displayed in instances of UIWebView;
main controller is a TabBarController with two tabs, that are instances of UIWebView;
the two UIWebView have methods (init and setter) for receiving the URL of the file they need to display. These two UIWebView display the URLs read by the AppDelegate.
I am trying to perform the same operations using a StoryBoard but do not know how to pass the URL of the content to the two UIWebView. I have read the answers concerning the initWithCoder method, but cannot figure out how to pass the parameters.
Is there anyone who figured out what I meant in my (incredibly confused) question who is also able to help me?
I think the right place to do this is your ViewController. You should create an outlet of the UIWebView within your ViewController in order to be able to access them. Then to pass the URL to them you can use:
- (void)loadRequest:(NSURLRequest *)request
I don't understand why you read remotely the URLs in your AppDelegate but you can access your AppDelegate from your ViewController if needed as follows:
[[UIApplication sharedApplication] delegate]
On the general question of accessing references to view controllers in a tab bar controller...
A tab bar controller has a public property called viewControllers, which is an array of view controllers which the tab bar controller contains.
You can access elements of this array just as you would any other array, but in this case, they're references to all your view controllers.
[tabBarController.viewControllers firstObject];
[tabBarController.viewControllers objectAtIndex:3];
for(UIViewController *vc in tabBarController.viewControllers) {
if([#"MyFirstVC" isEqualToString:vc.title]) {
// do stuff
}
}
etc. etc. etc.
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];
I have a table view cell with a button that, when pressed, needs to call the method of a different view controller. I''ve followed this tutorial, which shows one way to get at another view controller in story boards, but it involves hardcoding the position of the view controllers. Anytime I'd change the order of my view controllers, I'd need to update my code, which I know I will mess up.
Here is their method:
UITabBarController *tabBarController = (UITabBarController *)
self.window.rootViewController;
UINavigationController *navigationController =
[[tabBarController viewControllers] objectAtIndex:0];
PlayersViewController *playersViewController =
[[navigationController viewControllers] objectAtIndex:0];
playersViewController.players = players;
Yikes, what is that?! We want to assign the players array to the
players property of PlayersViewController so it can use this array for
its data source. But the app delegate doesn’t know anything about
PlayersViewController yet, so it will have to dig through the
storyboard to find it. This is one of the limitations of storyboards
that I find annoying. With Interface Builder you always had a
reference to the App Delegate in your MainWindow.xib and you could
make connections from your top-level view controllers to outlets on
the App Delegate. That is currently not possible with storyboards. You
cannot make references to the app delegate from your top-level view
controllers. That’s unfortunate, but we can always get those
references programmatically.
Does anybody know a cleaner way?
I tried creating an IBOutlet in my table view cell to the other view controller, but I can't ctrl-click and drag to the other view controller for some reason.
I also tried setting the IBAction of my button in my other view controller, but I need to know what row was clicked, and the only information i can get from -(IBAction) addButtonClicked:(id)seder is the sender information, which is just RectButton.
Thanks!
As a workaround, you could set the tag property of the sender in InterfaceBuilder to something, and then just compare [sender tag] in the addButtonClicked: method. I believe tags are ints.