Pushing view onto viewcontroller in tab controller - ios

So I'm trying to convert a single view application into a tabbed application. My use case is this - in one of the view controllers I want to push a new view controller and still have the tab underneath.
I'm currently doing this -
[self.tabBarController setViewControllers:#[self.searchViewController, self.loginViewController]];
[self.searchViewController presentViewController:self.searchViewController.detailController animated:YES completion:nil];
However, this makes the tab across the bottom disappear.
What should I do?

presentViewController is a "modal" presentation - the presented view controller takes over the entire screen. If you want to remain within the tab but move between view controllers, the root view controller in the tab should be a UINavigationController. You can then push/pop view controllers onto that.

There are two primary methods for view navigation, the first is a presentation which displays a view from the bottom that slides up, and the second is a push which displays a view from the right that slides in from the side.
In most cases, the view I am going to display and what action kicked off the navigation determine which method I will use. For example, if I have a table view that has a list of music albums and I want to search for a song by a particular artist, to see the songs within that album I want to PUSH the view controller, i.e. slide to the right. This gives me the built-in (and intuitive) ability to go back via the automatically added back button on the navigation bar in case the song I was looking for wasn't in the album I selected.
If perhaps I wanted to present the user with the ability to edit the album details, such as renaming the album, this is a totally different type of action, and I would want to PRESENT such a view modally, i.e. from the bottom.
The major distinction between the two is where are you going and what are you doing. If the next view you are going to show is something that does one action and you are then returned back to the original view, presenting modally from the bottom is conventional. If you are doing to be potentially navigation further and further into subsections and will be coming back and forth between said subsections, like Artist->Album->Song etc., you are going to want to push the view from the side, just like the default music app in iOS does.
This is an example starter project I created that demonstrates an easy way to make this work the way you likely want. I create instances of the different view controllers I want to be contained in the tabBarController, which are associated with the tabs, and then "wrap" them each with their own navigation controller before adding them to the tabBar via the .items property. This way each view controller has its own navigation hierarchy and within each you'll be able to call [self.navigationController pushViewController:] or [self.navigationController presentViewController] to keep the navigation 'within' the views and separate from the tabBar itself.
#import "AppDelegate.h"
#import "TabBarViewController.h"
#import "InfoViewController.h"
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
InfoViewController *firstVC = [[InfoViewController alloc] init];
firstVC.title = #"First";
firstVC.view.backgroundColor = [UIColor redColor];
UINavigationController *firstNC = [[UINavigationController alloc] initWithRootViewController:firstVC];
InfoViewController *secondVC = [[InfoViewController alloc] init];
secondVC.title = #"Second";
secondVC.view.backgroundColor = [UIColor blueColor];
UINavigationController *secondNC = [[UINavigationController alloc] initWithRootViewController:secondVC];
TabBarViewController *tabBarVC = [[TabBarViewController alloc] init];
tabBarVC.viewControllers = #[firstNC, secondNC];
self.window.rootViewController = tabBarVC;
[self.window makeKeyAndVisible];
return YES;
}
That resulted in the following:
Hope that helps!

Related

can we use table based navigation in single view application?

I have created single view iOS application with some basic functionality. and added the table view in simple view controller now I wanted to use table view with row navigation like(pushcontroller and popcontroller) is that possible and if it is, how we can set that.
If you need full support for push/pop operation, I would use a navigation controller.
On the other hand, you could simply present your detail view controllers when a table row is tapped:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
MyViewController* detailController = <CREATE THE VIEW CONTROLLER>;
[self presentViewController:detailController animated:YES completion:NULL];
}
In this case, your detailController will need to implement a sort of navigation bar or other mechanism to let the user dismiss the controller (through dismissViewControllerAnimated:completion:) and go back to the table view. (Using a navigation controller would instead take care of this in a canonical way.)
One major drawback of the simpler solution based on presenting/dismissing is the fact that all the view controllers presented this way are dismissed at once, so you cannot have multiple levels of navigation.
EDIT:
To add a navigation controller to your app, simply do something like this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
...
MyViewController *myViewController = [[MyViewController alloc] initWithNibName:nil bundle:nil];
...
self.navigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
self.window.rootViewController = self.navigationController;
[self.navigationController pushViewController:myViewController animated:YES];
...
[self.window makeKeyAndVisible];
return YES;
}
At the moment, your didFinishLaunchingWithOptions method should use the addSubview method to make your main view controller (named MyViewController in my example) visible. You can replace that call by the code above to instantiate the navigation controller and push on to it your main view controller.
Alternatively, you could create a new Xcode project using the navigation-based template and move all of your source files over.

UITextView Delegate Issue in Stacked UIViewControllers

I want to stack a UIViewController Board on top of a UIViewController Menu in order to create a Facebook-like side menu. This menu should contain a UITextView.
So far I can drag the Board View side ways and the Menu appears underneath it. Great. But there's an issue with the UITextView inside the menu. When I click it the app crashes with a BAD_EXC... exception. It seems like an issue with the UITextView Delegate.
Here's how I currently set it.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
//Board
board = [[BoardViewController alloc] init];
[self.window setRootViewController:board];
//Menu
MenuViewController* menu = [[MenuViewController alloc]init];
menu.textView.delegate = menu;
[self.window addSubview:menu.view];
//Window
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
Note: when I set the textView delegate to board and implement the delegate methods there it works but that really seems to be the wrong place to implement the menu textview methods to me. The delegate of the menu should be in the menu class itself.
How to set the menu's textView delegate correctly to the menu class?
//Menu
MenuViewController* menu = [[MenuViewController alloc]init];
menu.textView.delegate = menu;
[self.window addSubview:menu.view];
And then, poof: the menu view controller isn't referenced again and is deallocated by ARC. The view is retained by the window so it will look like everything is ok until the text view tries to send its delegate message to an object which was long since deallocated. This is the cause of your EXC_BAD_ACCESS crash.
The simple, slack solution would be to define a property in your app delegate for the menu view controller.
#property (nonatomic, strong) MenuViewController * menu;
and then store the menu there
//MenuViewController* menu = [[MenuViewController alloc]init];
//becomes
self.menu = [[MenuViewController alloc]init];
The proper solution and the one I recommend is that you look up UIViewController containment and implement your own custom view controller container that looks after this special arrangement of view controllers.
Just to briefly outline: You would have a subclass of UIViewController with two properties, one for the board view controller and one for the menu view controller. It would have a scroll view and would be responsible for the sliding action and any communication that needed to be passed from the board to the menu and vis versa. This container would also be responsible for loading the board and the menu view controllers and inserting their views into the correct places in its own view. If the board view controller needs to be swapped out for another board then the container would be responsible for this also.
You would usually set that by dragging the delegate connection from the textField to the view controller in interface builder, or programmatically in viewDidLoad of the menu view controller. You're right in that your app delegate shouldn't have anything to do with this process.

UINavigation pushing a new root controller

I am trying to push a new root controller to a navigation stack, but using a side reveal menu.
My app delegate has the following:
welcomeViewController = [[MyWelcomeViewController alloc] initWithNibName:#"MyWelcomeViewController" bundle:nil];
navController = [[UINavigationController alloc] initWithRootViewController:welcomeViewController];
navController.navigationBarHidden = YES;
// Then we setup the reveal side view controller with the root view controller as the navigation controller
self.revealSideViewController = [[PPRevealSideViewController alloc] initWithRootViewController:navController];
[self.revealSideViewController setDirectionsToShowBounce:PPRevealSideDirectionNone];
[self.revealSideViewController setPanInteractionsWhenClosed:PPRevealSideInteractionContentView | PPRevealSideInteractionNavigationBar];
// Then we make the window root view controller the reveal side view controller
self.window.rootViewController = self.revealSideViewController;
Once the welcome view controller is displayed, the user logs in. Once logged in the following process runs again from the App Delegate.
self.navController.navigationBarHidden = NO;
[self.navController setTitle:#"Home"];
[self.navController pushViewController:homeViewController animated:NO];
I then have a side view controller setup which is a table view with custom cells setup.
When a row is selected I need to push a new root controller onto the navigation controller. I try this by using the following in the table view for the cell selected.
MyAccountViewController *accountViewController = [[MyAccountViewController alloc] init];
[self.navigationController setViewControllers:[NSArray arrayWithObject:accountViewController] animated:NO];
Unfortunately this does not do anything. If I add the code to the App Delegate and then call the method from the table view controller then it works, however not from the .m file for the table view itself. Adding a log I can see the above is run, just does not do anything.
I am unsure if I need to do anything different on the above. For example, completely pop the views currently shown, then create the navigation controller and PPRevealSideViewController all over again. If I am supposed to, I am unsure how to pop all the current views to then push the new to the window, not from the AppDelegate.
The reason I do not want this in the App Delegate is because it is the incorrect way to approach this, and I would then need a separate method for each new root controller I would like to push from the menu, so the App Delegate would become very large.
Check UINavigationController.h:
#interface UIViewController (UINavigationControllerItem)
#property(nonatomic,readonly,retain) UINavigationController *navigationController; // If this view controller has been pushed onto a navigation controller, return it.
It means when you do myViewController.navigationController you will either get nil if myViewController is not pushed to any navController or the navController reference myViewController is pushed into.
As I understand your tableViewController is not pushed into the navController stack, that means you can't get the navController with tableViewController.navigationController. Instead you'll need to use anyViewControllerInTheStack.navigationController or if the navController is the rootViewController of your keyWindow, by
((UINavigationController*)[[UIApplication sharedApplication] keyWindow].rootViewController)
Add something like this to your AppDelegate.h:
#define XAppDelegate ((AppDelegate *)[[UIApplication sharedApplication] delegate])
Now you can access any iVar of AppDelegate from any .m file in your project.
MyAccountViewController *accountViewController = [[MyAccountViewController alloc] init];
[XAppDelegate.navController pushViewController:accountViewController animated:NO];
Make sure you add the correct imports.
One more thing: It's good to pop the login window from your navcontroller once you are done Logging in.
Hope this helps.

UINavigationController pushing UITabBarController only displays one tab

I have an application that has a UITabBarController created in IB. That tbc loads 3 views which work fine up to now.
I decided to INSERT a UINavController as the starting VC and have a UITableViewController display 4 menu items in cells. Each of the 4 items will in essence load the UITabBarController put pass in a different xml file to process in order to display data in those 3 tabs.
I essentially did this at the end of the applicationDidFinishLoading:
MainMenu *rootViewController = [[MainMenu alloc] init];
navController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
self.window.rootViewController = navController;
[window makeKeyAndVisible];
return YES;
I then created the MainMenu as the rootViewController subclassing UITableViewController and added a hardcoded array for now which displays the 4 items I want. I had the didSelectRowAtIndexPath run this code:
tabBarController = [[UITabBarController alloc] init];
[self.navigationController pushViewController:tabBarController animated:YES];
[tabBarController release];
It turns out that when I run it, the navcontroller pushes the tab controller but only the first tab shows up. Here is a pic.
You should never push a UITabBarController from a UINavigationController. Apple clearly says this:
An app that uses a tab bar controller can also use navigation controllers in one or more tabs. When combining these two types of view controller in the same user interface, the tab bar controller always acts as the wrapper for the navigation controllers.`
That essentially means tab bar should be the parent of all other view controllers that get called. Not the other way round. You should probably change the way you present your app.
More information here.

Navigation Controller is null

I have a split-view app that allows a user to select and display a thumbnail of a chosen image. I have placed a UIButton in the detailViewController using Interface Builder. When this button is pressed, I would like to have it change to a full screen view of the image. I have set up a new View Controller, called FullViewController and thought I had everything connected. The problem is that the navigation controller is null. I adjusted the AppDelegate.m to the following:
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after app launch.
// Set the split view controller as the window's root view controller and display.
self.window.rootViewController = self.splitViewController;
UINavigationController *nvcontrol =[[UINavigationController alloc] initWithRootViewController:fullViewController];
[window addSubview:nvcontrol.view];
[self.window makeKeyAndVisible];
return YES;
}
This is the function in the DetailViewController.m which is called when the button is pressed. The navigation controller comes up null in here.
//Function called when button is pressed - should bring up full screen view
- (IBAction) pressFullViewButtonFunction: (id) sender{
//viewLabel.text = #"Full View";
if (fullViewController == nil){
FullViewController *fullViewController = [[FullViewController alloc] initWithNibName:#"FullViewController" bundle:[NSBundle mainBundle]];
NSLog(#"fullViewController is %#", fullViewController);
self.fullViewController = fullViewController;
}
NSLog(#"self.navigationController is %#",self.navigationController);//this is null
[self.navigationController pushViewController:self.fullViewController animated:YES];
}
I'm not sure how to fix this. I've tried adding in the couple lines in the AppDelegate, but when it runs, the table in the root view doesn't show up and it no longer properly switches between portrait and landscape views.
I have the rest of the code readily available if that would help clarify. Just let me know!
Thanks.
From the code you post it is not possible to identify the problem, but two common reasons for self.navigationController to be nil are:
you did not push the object behind self on to the navigation controller in the first place; indeed it seems so, since the navigation controller is added as a subview of the split view controller; possibly you mean the opposite... not sure...
(sub-case of 1) you showed the object behind self using presentViewControllerModally.
When I say "the object behind self" I mean the instance of the class where pressFullViewButtonFunction is defined.
If you need more help, post the code where you push your controllers on to the navigation controller...
On a side note, if you do:
UINavigationController *nvcontrol =[[UINavigationController alloc] initWithRootViewController:fullViewController];
and nvcontrol is not an ivar, then you have a leak.
Hope this helps...

Resources