How do I pass an NSString through 3 ViewControllers? - iphone

hey, I'm currently using the iPhone SDK and I'm having trouble passing an NSString through 3 views
I am able to pass an NSString between 2 view controllers but I am unable to pass it through another one. My code is as follows...
`- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)index`Path {
NSString *string1 = nil;
NSDictionary *dictionary = [listOfItems objectAtIndex:indexPath.section];
NSArray *array = [dictionary objectForKey:#"items"];
string1 = [array objectAtIndex:indexPath.row];
//Initialize the detail view controller and display it.
ViewController2 *vc2 = [[ViewController2 alloc] initWithNibName:#"ViewController2" bundle:[NSBundle mainBundle]];
vc2.string1 = string1;
[self.navigationController pushViewController:vc2 animated:YES];
[vc2 release];
vc2 = nil;
}
in the "ViewController 2" implementations I am able use "string1" in the title bar by doing the following....
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationItem.title = string1;
UIBarButtonItem *addButton = [[[UIBarButtonItem alloc]
initWithImage:[UIImage imageNamed:#"icon_time.png"]
style:UIBarButtonItemStylePlain
//style:UIBarButtonItemStyleBordered
target:self
action:#selector(goToThirdView)] autorelease];
self.navigationItem.rightBarButtonItem = addButton;
}
but I also have a NavBar Button on the right side that I would like to push a new view
- (void)goToThirdView
{
ViewController3 *vc3 = [[ViewController3 alloc] initWithNibName:#"ViewController3" bundle:[NSBundle mainBundle]];
[self.navigationController pushViewController:NESW animated:YES];
vc3.string1 = string1 ;
[vc3 release];
vc3 = nil;
}
How do I pass on that same string to the third view? (or fourth)

What you have should work exceptthat you want to set the string in vc3 before you push it onto the stack to ensure it is present when the view and the nav bar draws. That is the way you have it in the vc2 that works.
However, as a matter of application design, it is poor practice to pass values directly between view controllers. Ideally, you want your view controllers to be standalone and able to function regardless of which other controller did or did not precede it. (This becomes really important when you need to resume an app back to the point where it was interrupted.) If make the view controllers interdependent your app will grow tangled and complex as it grows larger.
The best way to exchange data between views is to park the data in a universally accessible place. If it is application state information put it in user defaults or you can put in an attribute of the app delegate. If it is user data, then it should go in a dedicated data model object (which is either a singleton or accessible through the app delegate.)

You may find code sample from a question I asked earlier.

Related

iOS: Why isn't this View Controller getting pushed onto the Navigation Controller's stack?

I have a modal view that is a Navigation Controller. When one of the rows in its UITableView gets tapped, the correct View Controller for that row should be initialized and pushed onto the Navigation Controller's stack (so that the screen now shows that View Controller). But it's not working. I've been trying to debug it for a while, and it appears that the Navigation Controller's retain count is 0 at the time pushViewController is called. I assume that means it has been deallocated, and that this is the root of the problem. But I can't figure out why.
In the following code, AddSportDelegate.m presents the modal view that contains the necessary Navigation Controller (_addItemNavController) initialized with the necessary AddItemTableViewController. Tapping on one of the rows of the Table View managed by AddItemViewController calls the showAddItemDataView: method of AddSportDelegate, which in turn should push the correct ViewController onto the _addItemNavController stack. But, as I note in a comment in the code, the retain count of _addItemNavController at that moment is 0.
Note: I realize this code has memory leaks. I deleted some release lines for the sake of brevity. I also haven't included the code for the view controller that is supposed to be getting pushed, since it doesn't have anything at the moment beyond a UILabel identifying that it is the right View Controller.
AddItemDelegate.m
#synthesize addItemNavController = _addItemNavController;
- (void)showAddItemViewController:(UIViewController *)viewController
{
_parentVC = viewController;
[_parentVC retain];
tc = [[AddItemTableViewController alloc] init];
UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc] initWithTitle:#"Cancel" style:UIBarButtonSystemItemDone target:self action:#selector(cancelAdd)];
tc.navigationItem.leftBarButtonItem = cancelButton;
tc.title = #"Select a Category";
_addItemNavController = [[AddItemNavController alloc] initWithRootViewController:tc];
tc.superViewController = _addItemNavController;
[_parentVC.navigationController presentModalViewController:_addItemNavController animated:YES];
}
- (void)showAddItemDataView:(SportCategory *)category
{
[category retain];
UIViewController *vc;
if (category.name == #"Soccer") {
vc = [[AddSoccerDataViewController alloc] init];
}else{
vc = [[AddBaseballDataViewController alloc] init];
}
//retain count already 0
NSLog(#"retain count: %i", [_addItemNavController retainCount]);
[_addItemNavController.navigationController pushViewController:vc animated:YES];
}
AddItemTableViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
_addItemDelegate = [[AddItemDelegate alloc] init];
SportCategory *soccer = [[SportCategory alloc] initWithCategoryName:#"Soccer"];
SportCategory *baseball = [[SportCategory alloc] initWithCategoryName:#"Baseball"];
_categories = [[NSArray alloc] initWithObjects:soccer,baseball,nil];
[self.tableView reloadData];
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
SportCategory *selectedCategory = [_categories objectAtIndex:[indexPath row]];
[_addItemDelegate showAddItemDataView:selectedCategory];
}
I am going to take a shot at this.
if (category.name == #"Soccer")
I come from a java background, but I know a little objective - c. I thought you can't compare strings with == which would mean your view controller was never created. Maybe try a isEqualToString method.
That is my only thought, I could be wrong. But Best of Luck.
The '==' operator isn't the good way to compare strings, but anyway your code should fall into the else part.
About your question, _addItemNavController must be nil because your NSLog prints 0 for its retain count.
Is the method -(void)showAddItemViewController:(UIViewController *)viewController called somewhere ?
Your view controller doesn't seem to be initialized.
A bit of sleep helped me find the problem. There were actually two:
1) The final line in AddItemDelegate read:
[_addItemNavController.navigationController pushViewController:vc animated:YES];
However, _addItemNavController IS the navigation controller, so the '.navigationController' part needed to be deleted.
2) I also needed to assign tc.addItemDelegate to self in showAddItemViewController.

Right design pattern for tabbed navigation views?

I've been stuck trying to puzzle this out for a couple days now, and I'll admit I need help.
The root view controller of my application is a tab bar controller. I want to have each tab bar a different navigation controller. These navigation controllers have completely different behavior.
So how do I set this up in terms of classes? Per Apple's documentation, I'm not supposed to subclass UINavigationViewController. So where do I put the code that drives each of these navigation controllers? Does it all get thrown in App Delegate? That would create an impossible mess.
This app should run on iOS 4.0 or later. (Realistically, I can probably require iOS 4.2.)
This is taken from one of my applications. As you say, you are not supposed to subclass UINavigationController, instead you use them as they are and you add viewcontroller on the UINavigationController's. Then after setting the root viewcontroller in each UINavigationController, you add the UINavigationController to the UITabBarController (phew!).
So each tab will "point" to a UINavigationController which has a regular viewcontroller as root viewcontroller, and it is the root viewcontroller (the one you add) that will be shown when a tab is pressed with a (optional) navigationbar at top.
UITabBarController *tvc = [[UITabBarController alloc] init];
self.tabBarController = tvc;
[tvc release];
// Instantiates three view-controllers which will be attached to the tabbar.
// Each view-controller is attached as rootviewcontroller in a navigationcontroller.
MainScreenViewController *vc1 = [[MainScreenViewController alloc] init];
PracticalMainViewController *vc2 = [[PracticalMainViewController alloc] init];
ExerciseViewController *vc3 = [[ExerciseViewController alloc] init];
UINavigationController *nvc1 = [[UINavigationController alloc] initWithRootViewController:vc1];
UINavigationController *nvc2 = [[UINavigationController alloc] initWithRootViewController:vc2];
UINavigationController *nvc3 = [[UINavigationController alloc] initWithRootViewController:vc3];
[vc1 release];
[vc2 release];
[vc3 release];
nvc1.navigationBar.barStyle = UIBarStyleBlack;
nvc2.navigationBar.barStyle = UIBarStyleBlack;
nvc3.navigationBar.barStyle = UIBarStyleBlack;
NSArray *controllers = [[NSArray alloc] initWithObjects:nvc1, nvc2, nvc3, nil];
[nvc1 release];
[nvc2 release];
[nvc3 release];
self.tabBarController.viewControllers = controllers;
[controllers release];
This is how I go from one viewcontroller to another one (this is done by tapping a cell in a tableview but as you see the pushViewController method can be used wherever you want).
(this is taken from another part of the app)
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (self.detailedAnswerViewController == nil) {
TestAnsweredViewController *vc = [[TestAnsweredViewController alloc] init];
self.detailedAnswerViewController = vc;
[vc release];
}
[self.navigationController pushViewController:self.detailedAnswerViewController animated:YES];
}
The self.navigationcontroller property is of course set on each viewcontroller which are pushed on the UINavigationController hierachy.

application:didFinishLaunchingWithOptions loading before viewDidLoad

So I am writing an app that will read a JSON feed. In my application:didFinishLaunchingWithOptions, I am writing some code to download the JSON string and store it into a local NSString variable. I will then pass that string into ListingsViewController (which is the Root VC of the NavigationController). When I print out the JSON data in ListingsViewController, it is showing me (null) which is making me think that viewDidLoad is loading before - which seems illogical?
So here is my application:didFinishLaunchingWithOptions
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Grab the feeds
NSURL *jsonURL = [NSURL URLWithString:#"http://www.shoofeetv.com/iphonexml/view/all_channels.json"];
NSString *jsonData = [[NSString alloc] initWithContentsOfURL:jsonURL];
// Pass jsonData to the ListingsViewController
ListingsViewController *listingsViewController = [[ListingsViewController alloc] initWithNibName:#"ListingsViewController" bundle:nil];
listingsViewController.jsonData = jsonData;
[listingsViewController release];
// Display the navigation controller
self.window.rootViewController = self.navigationController;
[self.window makeKeyAndVisible];
return YES;
}
My viewDidLoad method is as follows:
- (void)viewDidLoad {
self.navigationItem.title = #"Listings";
UIBarButtonItem *checkinButton = [[UIBarButtonItem alloc]
initWithTitle:#"Check In"
style:UIBarButtonItemStylePlain
target:self
action:#selector(switchView)];
self.navigationItem.rightBarButtonItem = checkinButton;
[checkinButton release];
NSLog(#"%#", self.jsonData);
[super viewDidLoad];
}
Please note that a common solution is to make sure that the App Delegate in MainWindow.xib must be connected to the File's Owner. Mine already is connected.
I will appreciate any help!
Thank you everyone.
well you are setting up a view controller with your code, but it is never shown within the navigation controller. you just set up a view controller, assign its jsonData a string and destroy the view controller immediately. I#m pretty sure the output you get is from a different view controller that you create in your main XIB.
what you want to do is create an empty navigation controller in your XIB and then do the following:
// Pass jsonData to the ListingsViewController
ListingsViewController *listingsViewController = [[ListingsViewController alloc] initWithNibName:#"ListingsViewController" bundle:nil];
listingsViewController.jsonData = jsonData;
[self.navigationController pushViewController:listingsViewController animated:NO];
[listingsViewController release];
// Display the navigation controller
self.window.rootViewController = self.navigationController;
[self.window makeKeyAndVisible];
this will actually display the view controller you created.
Also remember that when you deploy your application, you need to load your json-data asynchronously and handle network errors (apple will test your app under various network conditions)

How to disable a DetailViewController UIBarButtonItem from RootViewController?

i have a main table view. and a DetailView. when cell is clicked, DetailView of that cell comes which shows details of that cell. DetailView has two buttons next and previous. I wanna know how to disable a detail view button from RootViewcontroller.m. code looks like this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
DetailViewController *nextController = [[DetailViewController alloc] init];
int storyIndex = [indexPath indexAtPosition:[indexPath length] -1];
nextController = [nextController initWithObjectAtIndex:storyIndex inArray:stories];
NSString *storyTitle = [[stories objectAtIndex:storyIndex] objectForKey:#"title"];
nextController.title = #"Details";
UIBarButtonItem *tempButtonItem = [[[UIBarButtonItem alloc] init] autorelease];
tempButtonItem.title = #"Back";
self.navigationItem.backBarButtonItem = tempButtonItem ;
nextController.sTitle = storyTitle;
[self.navigationController pushViewController:nextController animated:YES];
[nextController release];
}
i have already tried nextController.next.enabled=NO and [nextController.next setEnabled:NO] after this line:
[self.navigationController pushViewController:nextController animated:YES];
where next is the UIBarButtonItem name which is in DetailViewController.
can anybody tell me how to disable that button.
thanx in advance
Viewcontrollers and views are not loaded at the same time. What this means is that when you instantiate an object of DetailViewController in your case, the views are not drawn(and if you are using Nib's) loaded, this is part of the lazy loading concept.
So the first time you send the message setEnabled = NO, the object will be nil(sending messages to objects that are nil is allowed in Objective C).
Example:
[nextController setEnabled:NO] is equal to [nil setEnabled:NO] and this is surely not what you want.
The next time, unless a memory warning and the views are unloaded, the views will be in memory and the reference to the button will no longer be nil so the second time you invoke it, it will work.
And add the code line above the pushViewController:animate
If you want to initialize the button to be disable you may put this code in the viewDidLoad/viewWillAppear depending on the context of your application.
This is only one possible solution.
Edited answer to request in comment:
In your initializer method in the DetailviewController add this:
self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc] initWithTitle:#"Back" style:UIBarButtonItemStyleBordered target:self action:#selector(backToRoot)] autorelease];
- (void)backToRoot {
[self.navigationController popToRootViewControllerAnimated:YES];
}
and also add the method-signature to your headerfile.
Reference to the UINavigationController: http://developer.apple.com/library/ios/#documentation/uikit/reference/UINavigationController_Class/Reference/Reference.html

How to pushviewcontroller to a viewcontroller stored in a tabbaritem?

First of all I know this is a long question. REST ASSURED I have tried to figure it out on my own (see: StackOverflow #2609318). This is driving me BATTY!
After trying and failing to implement my own EDIT feature in the standard moreNavigationController, I have decided to re-implement my own MORE feature.
I did the following:
Add a HOME view controller which I init with: initWithRootViewController
Add 3 other default tabs with:
ResortsListViewController *resortsListViewController;
resortsListViewController = [[ResortsListViewController alloc] initWithNibName:#"ResortsListView" bundle:nil];
resortsListViewController.title = [categoriesDictionary objectForKey:#"category_name"];
resortsListViewController.tabBarItem.image = [UIImage imageNamed:#"whatever.png"];
resortsListViewController.navigationItem.title=#"whatever title";
localNavigationController = [[UINavigationController alloc] initWithRootViewController:resortsListViewController];
localNavigationController.navigationBar.barStyle = UIBarStyleBlack;
[localControllersArray addObject:localNavigationController];
[localNavigationController release];
[resortsListViewController release];
Those work when i add them to the tabbar. (ie: click on them and it goes to the view controller)
Then I add my own MORE view controller to the tabbar:
MoreViewController *moreViewController;
moreViewController = [[MoreViewController alloc] initWithNibName:#"MoreView" bundle:nil];
moreViewController.title = #"More";
moreViewController.tabBarItem.image = [UIImage imageNamed:#"more.png"];
moreViewController.navigationItem.title=#"More Categories";
localNavigationController = [[UINavigationController alloc] initWithRootViewController:moreViewController];
localNavigationController.navigationBar.barStyle = UIBarStyleBlack;
[localControllersArray addObject:localNavigationController];
[localNavigationController release];
[moreViewController release];
Then
tabBarController.viewControllers = localControllersArray;
tabBarController.moreNavigationController.navigationBar.barStyle = UIBarStyleBlack;
tabBarController.customizableViewControllers = [NSArray arrayWithObjects:nil];
tabBarController.delegate = self;
That creates the necessary linkages. Okay, so far all is well. I get a HOME tab, 3 category tabs and a customized MORE tab -- which all work.
in the MORE tab view controller I implement a simple table view that displays all the other tabs I have in rows. SINCE I want to be able to switch them in and out of the tabbar I created them JUST like i did the resortslistviewcontroller above (ie: as view controllers in an array). When I pull them out to display the title in the tableview (so the user can go to that "view") i simply do the following:
// [myGizmoClass CategoryArray] holds the array of view controller tab bar items that are NOT shown on the main screen.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
... etc...
UIViewController *Uivc = [[myGizmoClass plusCategoryArray] objectAtIndex:indexPath.row];
cell.textLabel.text = [Uivc title];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
THIS is where it falls through:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
MyGizmoClass *myGizmoClass= [MyGizmoClass sharedManager];
UIViewController *tbi = [[myGizmoClass plusCategoryArray] objectAtIndex:indexPath.row];
NSLog(#"%#\n",[[tbi navigationItem ]title]);
[self.navigationController pushViewController:tbi animated:YES];
}
This is the error i get ("ATMs" is the title for the clicked tableview cell) so i know the Uivc title is pulling the correct title and therefore the correct "objectatindex":
2010-04-09 11:25:48.222
MouseAddict[47485:207] ATMs 2010-04-09
11:25:48.222 MouseAddict[47485:207]
*** Terminating app due to uncaught exception
'NSInvalidArgumentException', reason:
'Pushing a navigation controller is
not supported'
BIG QUESTION: How do i make the associated VIEW of the UIViewController *tbi show and get pushed into view?
I am GUESSING that the UIViewController is the correct class for this tbl .. i am not sure. BUT i just wanna get the view so i can push it onto the stack.
Can someone plz help?
To answer kovpas's question below: myGizmoClass is a singleton (apple's singleton myGizmo class. The array of viewcontrollers is stored in that just like it is in [localControllersArray addObject:localNavigationController]; (in the first code snippet above). AND it does put it in and pull it out correctly as evidenced by the fact that when i NSLOG the [Uivc title] the log prints ATMs. This means the plusCategoryArray is correctly storing and retrieving the viewController (if, indeed, that is what is being stored).
Pushing a navigation controller is not supported is really bothering me. Why would a viewController return a navigationController and is it possible to coerce the navigationController to get the "pushable" view out of it... or does the navigationController have some element in it that is the view?
From the error, it looks as if your Gizmo class has an array of UINavigationControllers, not UIViewControllers. So instead push with:
[self.navigationController pushViewController:[[tbi viewControllers] lastObject] animated:YES];
If the array is the same array as you called localControllers above, then this should work better. Or you could just create the array without the UINavigationControllers, they aren't needed if you are going to push them onto your more controller navigation controller.
I'm not sure, but it looks like this error appears when you are trying to push UINavigationController into another UINavigationController. Could you please provide an implementation of MyGizmoClass?