UINavigationController for table inside of UITabController - objective-c

my app is built with a UITabController and works as imagined. However, for one of the views within my UITabBar, I would like to add a table that when something is pressed will take me somewhere. And I would like to do this just within this one view.
I know how to build a table and populate and get it to go somewhere but my issue is I can't seem to get my app to run with the table. I feel like my connections are off and specifically with appDelegates. I already had two appDelegate files (.h & .m) before adding the UINavigationController so from here I really don't know what to do. I took apple's simpleTableView tutorial files and copied them over to mine. It still crashes. I even copied there appDelegate files (so now I have 4) but the same deal. This is the error I am getting but in general I just feel lost with the delegates and connections.
2011-12-12 12:08:50.302 TabbedCalculation[68713:207] * Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key delegate.'
* Call stack at first throw:
If anyone can offer any help, it would be much appreciated.
Thanks!
P.S. I have changed within the mainWindow.xib of the UITabController to point one of the tabs to the appropriate class and xib file so that is not the issue but I have noticed that many tutorials want within the app delegate this line to the navController:
[window addSubview:[navigationController view]];
but I have already set it to the tabBarController.

Your app can only use one set of app delegate files. So copying over a the example's app delegate files does not mean they are being utilized. You need a navigation controller inside the specific tab you want to contain the tableview. Here is an example of a navigation controller inside a tab bar controller, by modifying didFinishLaunchingWithOptions in the app delegate:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
// Override point for customization after application launch.
AllTaskViewController *view1 = [[AllTaskViewController alloc] initWithNibName:#"AllTaskView" bundle:nil];
view1.title = #"All Tasks";
TodayTaskViewController *view2 = [[TodayTaskViewController alloc] initWithNibName:#"TodayTaskView" bundle:nil];
view2.title = #"Today's Tasks";
HistoryViewController *view3 = [[HistoryViewController alloc] initWithNibName:#"HistoryView" bundle:nil];
view3.title = #"History";
SettingsTableViewController *view4 = [[SettingsTableViewController alloc] initWithNibName:#"SettingsTableView" bundle:nil];
view4.title = #"Settings";
UINavigationController *nav1 = [[UINavigationController alloc] initWithRootViewController:view1];
UINavigationController *nav2 = [[UINavigationController alloc] initWithRootViewController:view2];
UINavigationController *nav3 = [[UINavigationController alloc] initWithRootViewController:view3];
UINavigationController *nav4 = [[UINavigationController alloc] initWithRootViewController:view4];
[view1 release];
[view2 release];
[view3 release];
[view4 release];
self.tabBarController = [[[UITabBarController alloc] init] autorelease];
self.tabBarController.viewControllers = [NSArray arrayWithObjects:nav1, nav2, nav3, nav4, nil];
[nav1 release];
[nav2 release];
[nav3 release];
[nav4 release];
self.window.rootViewController = self.tabBarController;
[self.window makeKeyAndVisible];
return YES;
}
Note: All 4 view controllers have an individual navigation controller, and each one is a custom view controller. I used nibs here, but you don't necessarily have to. And the app itself has no main window, you would need to modify this slightly if you are using a main window for the app.

Related

Objective-c Nested VIewControllers

i would like to create an App whit a structure similar to the native application "Phone" of the iPhone. I will be more precise, the phone application have a tabBar that contains:"Favorites" , "Recents", "Contact", "Keypad" and "Voice Mail".
When we enter in the tab contacts we can see a navigation bar and a tableView.
I would like to have a similar structure but i'm questioning myself about which is the best and most correct way to do it?
I was thinking to start a single view application than use the view controller that is create automatically as a TabbedViewController then i would create another subclass of another viewController and i used it as my NavViewController.
I would have something like
[myTabBar.view addSubview:myNavController];
but how can i set those instance? once i have the automatically created ViewController and i create a SecondviewController how can i set them as the TabViewController and my NavViewController ?
If you're using storyboards, just add a tab bar controller to your storyboard. Then select one of the tab bar's child scenes and then choose "Embed In" - "Navigation Controller" from the Xcode "Editor" menu. If you repeat that process for whichever tabs you want to have navigation controllers. In this screen snapshot, I've added a navigation controller to the first and third tabs, but not the second.
Hopefully this illustrates the idea.
If you're determined to do this with NIBs, the easiest way to get started is create a new project with the Tabbed Application template (and obviously, at the next screen, uncheck "Use Storyboards"):
Then open up the app delegate .m file and replace the default didFinishLaunchingWithOptions that looks like:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
UIViewController *viewController1 = [[FirstViewController alloc] initWithNibName:#"FirstViewController" bundle:nil];
UIViewController *viewController2 = [[SecondViewController alloc] initWithNibName:#"SecondViewController" bundle:nil];
self.tabBarController = [[UITabBarController alloc] init];
self.tabBarController.viewControllers = #[viewController1, viewController2];
self.window.rootViewController = self.tabBarController;
[self.window makeKeyAndVisible];
return YES;
}
with one that creates a separate navigation controller for each tab for which you want a navigation bar (in this case, I'm adding it to the first one only, but it illustrates the idea):
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
UIViewController *viewController1 = [[FirstViewController alloc] initWithNibName:#"FirstViewController" bundle:nil];
UINavigationController *navigationController1 = [[UINavigationController alloc] initWithRootViewController:viewController1];
UIViewController *viewController2 = [[SecondViewController alloc] initWithNibName:#"SecondViewController" bundle:nil];
self.tabBarController = [[UITabBarController alloc] init];
self.tabBarController.viewControllers = #[navigationController1, viewController2]; // was #[viewController1, viewController2];
self.window.rootViewController = self.tabBarController;
[self.window makeKeyAndVisible];
return YES;
}
This a relatively easy way to start a NIB-based tabbed application. You can then customize from there.
Personally, I struggle to imagine why someone would use NIBs rather than storyboards (unless you're trying to support iOS 4), but hopefully this illustrates both techniques.

Application didreceiveRemoteNotification and jumping to a specific view

I have been trying everything to work this out. I get a notification when the app is closed with 2 custom items, a type and an id. The type is supposed to tell me which view to load, and the id is supposed to tell the app which row to get from the database. I am going through hell trying to figure this out.
I need to click on the notification and have it take me to the relevant record. So far I have been almost successful with two different methods that I'll outline below.
I should also point out that I know the payload is working correctly from APNS as I've debugged it to death :)
The first thing I tried was as follows:
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
NSString *itemType = [[userInfo objectForKey:#"T"] description];
NSString *itemId = [[userInfo objectForKey:#"ID"] description];
self.window=[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// type 1 = call, type 2 = contact
if ([itemType isEqual: #"1"]) {
Leads_CallsDetailViewController *callView = [[Leads_CallsDetailViewController alloc] init];
[callView displayItem:itemId];
[self.window addSubview:callView.view];
[self.window makeKeyAndVisible];
} else if([itemType isEqual: #"2"]) {
Leads_ContactsDetailViewController *contactView = [[Leads_ContactsDetailViewController alloc] init];
[contactView displayItem:itemId];
[self.window addSubview:contactView.view];
[self.window makeKeyAndVisible];
}
}
With this one, I have a method on the detail views called displayItem that I was going to use to get the data from the api and then display it. This did something, but it looked like the view never really loaded. I have a scrollview and various buttons on the page, but all that ever got loaded from addSubview was a background image. Nothing ever really happened to fully load the view. I wasn't sure how to handle that.
The second thing I tried was to go directly to the view like this:
NSString *storyboardId = #"Leads_Calls_SB";
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
UIViewController *initViewController = [storyboard instantiateViewControllerWithIdentifier:storyboardId];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.rootViewController = initViewController;
[self.window makeKeyAndVisible];
This one seems to load the view functioning and nice looking with two major caveats. 1. I'm not sure how to pass data to it, and 2. It didn't like it when I tried to pop back and it also got angry when I tried to segue pushes from there, almost as if there was no navigation controller for the view, even though the entire application is embedded in a navigation controller.
Thanks so much for your help. If anyone can help me figure this out I'll be indebted to you.
Normally for this requirement I would do this..
Use NSNotificationCenter and post a notification from didReceiveRemoteNotification.
[[NSNotificationCenter defaultCenter] postNotificationName:#"notificationReceived" object:self userInfo:userInfo];
Subscribe to it from the VC from where you can open your details view to show the message.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(notificationReceived:) name:#"notificationReceived" object:nil];
If you are instantiating the VC yourself and not using segue. you can do this..
UIStoryboard* storyBoard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
detailVC = [storyBoard instantiateViewControllerWithIdentifier:#"detailVC"];
detailVC.delegate = self;
detailVC.userInfo = #"YOUR DATA";
[self presentViewController:detailVC animated:YES completion:nil];
To return you can do this in your detail VC..
[self dismissViewControllerAnimated:YES completion:nil];

UIViewController loading a UISplitViewController

I have a UINavigationController as my rootViewController, which contains a UIViewController (which I will call projects for the sake of this discussion). In Projects, I have a button, which when clicked, I want to load a UISplitViewController - preferably sliding up from the bottom, although this is just a nice-to-have feature.
In the UISplitViewController, I have a "Close" button which I want to remove the UISplitViewController re-showing Projects.
From what I have read, UISPlitViewControllers must be the rootViewControllers. With that in mind, my code so far is as follows.
AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
...
// LOAD THE PROJECTS PANEL ROOT VIEW CONTROLLER INTO THE WINDOW
ProjectsListViewController *projects = [[ProjectsListViewController alloc] init];
navigationController = [[UINavigationController alloc] initWithRootViewController:projects];
[window setRootViewController:navigationController];
[window makeKeyAndVisible];
return YES;
}
ProjectsViewController
-(IBAction)loadDetails
{
ProjectNavigationController *projectNavPanel = [[ProjectNavigationController alloc] init];
ProjectDetailController *projectDetailPanel = [[ProjectDetailController alloc] init];
ProjectSplitViewController *splitRootController = [[ProjectSplitViewController alloc] init];
[splitRootController setViewControllers:[NSArray arrayWithObjects:projectNavPanel, projectDetailPanel, nil]];
[[self view] removeFromSuperview];
[[appDelegate window] setRootViewController:splitRootController];
}
UISplitViewController Naviagaion
- (void)loadProjects
{
// LOAD THE PROJECTS LIST BACK INTO VIEW
ProjectsListViewController *projectsList = [[ProjectsListViewController alloc] init];
[[[self parentViewController] view] removeFromSuperview];
[[appDelegate window] setRootViewController:projectsList];
}
Now, I know this is wrong, and unsurprisingly it is having adverse effects on other methods. In fact, as I type this, I noticed that the Projects page is being loaded in a navigationController on launch, but placed directly on the window when the splitViewController is closed. Can anyone help me by explaining the correct method of achieving this?
Thanks
Apple say that a UISplitViewController must be the topmost view controller in your app, and that it must be there for the entire lifetime of your app. As you've noticed, if you ignore this, everything can break.
There are some alternatives out there that don't break this way, e.g. MGSplitViewController. Google around. If you have the time, you could even cook your own implementation of a split view controller and be in complete control.
If you really want to use Apple's UIsplitViewController in "crazy" ways, then you can install it as root VC (as Apple demand), and have it at the root all the time, but then show other UIs modally over the top of it. Then hide the modal UI to make the split view controller appear. This is nasty and hacky though.
A while back I asked a related question that may be of interest:
Best way to switch between UISplitViewController and other view controllers?

UITabbarcontroller in IOS5 throws UIViewControllerHierarchyInconsistency exception

I have the following code for the UITabbarcontroller:
NSMutableArray *arr = [[NSMutableArray alloc] init];
tabBarController = [[UITabBarController alloc] init];
FirstViewController *firstview = [[FirstViewController alloc] init];
[tabBarControllerViews addObject:firstview];
[firstview release];
SecondViewController *secondview = [[SecondViewController alloc] init];
[tabBarControllerViews addObject:secondview];
[secondview release];
[tabBarController setViewControllers:arr animated:YES];
[arr release];
self.view = tabBarController.view;
This code runs fine on IOS4. I tried it on IOS5 beta and get the following error when tapping on a UITabbarItem:
*** Terminating app due to uncaught exception 'UIViewControllerHierarchyInconsistency',
reason: 'child view controller:<FirstViewController: 0x6e03be0> should have parent view
controller:<MainViewController: 0x6816d20> but actual parent is:<UITabBarController: 0x6b0c110>'
replace:
self.view = tabBarController.view;
with:
[self.view addSubview:tabBarController.view];
This will also be backwards compatible with IOS3&4.
Had the same pattern (and problem) in my code. Joe's solution didn't work for me. Looking at the snippet, I'm guessing that you derive a class from UIViewController to allow you to customize something.
The thing to do here, and it is quite simple, is to derive from UITabBarController rather than UIViewController, don't create tabBarController, and anywhere you reference tabBarController, substitute self.
5 minutes and you're no longer throwing the inconsistency exception and you remain backwards compatible with iOS 4. You can still do all of your customization in your derived class (monkeying with the nav controller, etc).
If you have built a complex derivation of UIViewController you need to use, this could be more work.
One small gotcha - if you override LoadView, you'll find that it gets called during the init for the UITabBarController. Makes it hard to set members prior to LoadView, so you may need to split up your initialization.
Good luck!
You cannot push or present a UITabbarViewController. Is your First View Controller a UITabBarController ?
I struggled with the same problem.
When you create a new Master-Detail Application(without story board), you can see this codes below from AppDelegate.m.
MasterViewController *masterViewController = [[MasterViewController alloc] initWithNibName:#"MasterViewController" bundle:nil];
self.navigationController = [[UINavigationController alloc] initWithRootViewController:masterViewController];
self.window.rootViewController = self.navigationController;
[self.window makeKeyAndVisible];
"BE NOT DEPENDENT ON MainWindow"
Just start from your own ViewController and set it to delegate.
And don't forget to unlink view from MainWindow.xib else the view will called 2 times.

ios4 sdk ipad - Can we put two VIEWS inside window?

I want to place two views side by side like they did in the MAIL app for the ipad. Is this possible with UINavigationController?
I would like to place two UINavigationController side by side
Dont worry about the syntax I just want to know if it is possible
UINavigationConroller *left;
UINavigationController *right;
[WIndow addSubView: left];
[WIndow addSubView:right];
UIWindow inherits directly from UIView, so it is possible to add multiple objects to it, though if you add multiple view controllers then only one of them will receive rotation events.
The correct means to do a Mail-type presentation is to use a UISplitViewController, which automatically handles the transition between two view and one view mode. The Xcode template for a 'Split View-based Application' will even set you up so that you have an button to view the left tableview as a UIPopoverController if you're in portrait.
EDIT: example code, with a split view controller:
// a tiny little method to vend a new navigation controller; following Cocoa patterns, because it
// has 'new' in the name it vends an owning reference (ie, not autoreleased, retain count +1)
- (UINavigationController *)newController
{
UITabBarController *tabBar = [[UITabBarController alloc] init];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:tabBar];
[tabBar release];
return navController;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
// generate a split view controller
UISplitViewController *splitView = [[UISplitViewController alloc] init];
// grab a couple of navigation controllers
UINavigationController *navController1 = [self newController];
UINavigationController *navController2 = [self newController];
// add the navigation controllers to the split view controller
splitView.viewControllers = [NSArray arrayWithObjects:navController1, navController2, nil];
[navController1 release];
[navController2 release];
// and put the whole thing on screen
[window addSubview:splitView.view];
[window makeKeyAndVisible];
return YES;
}