I'm getting a little lost trying to use both a UITabBarController and UINavigationController in MonoTouch.
I can create a navigation based project, and navigate using only a navigationController, fine. Or I can add a tabBarController and navigating to a couple of main screens, fine.
However, I can't seem to navigate to another without using the TabBarController if one is present. E.g, I'm doing an app that deals with "foo", so I am two views on my tabbar, FooHome, and FooSettings. How do I navigate to a new view if the user click something like "Add Foo" on the FooSettings view.
The NavigationController.PushToView doesn't seem to have any effect, and I don't want to add the view to the tabController since its nice and simple with only two items.
Should I be using this.View.AddSubView? The idea sort of sounds like a dialog box, I'm just not sure how to do it with monoTouch...
I was wrestling with this for hours and this post helped. Thank you so much. For those still in the dark a little, here's my code:
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
// create a new window instance based on the screen size
window = new UIWindow (UIScreen.MainScreen.Bounds);
var vc1 = new VideosVC ();
var vc2 = new ScheduleVC ();
var vc3 = new TheCastVC ();
var vc4 = new MerchandiseVC ();
UINavigationController uinc1 = new UINavigationController(vc1);
UINavigationController uinc2 = new UINavigationController(vc2);
UINavigationController uinc3 = new UINavigationController(vc3);
UINavigationController uinc4 = new UINavigationController(vc4);
tabBarController = new UITabBarController ();
tabBarController.ViewControllers = new UIViewController [] {
uinc1,
uinc2,
uinc3,
uinc4,
};
window.RootViewController = tabBarController;
window.MakeKeyAndVisible ();
return true;
}
Add your FooHome and FooSettings controllers to UINavigationControllers and set those navigation controllers to your tab controller.
So for example, the first tab will contain a navigation controller whose root controller is FooHome and the second tab will contain a navigation controller whose root controller is FooSettings.
When you tap on Add Foo in FooSettings, you will push the new controller inside the second tab.
Another approach would be to use the storyboard to create your screens. It's very easy to use a UITabBarController with UINavigationController. You just create segues between them. What has taken you hours could have been done in seconds. Also, by using the storyboard you can very quickly rearrange views & tabs without writing code; making it much more maintainable.
Related
I use in my application, the side bar menu like in facebook, so I have different cells in this menu, what I want is to when click on a cell push me to another view controller. I face a problem here which is: the menu is a table view which I don't have it in storyboard, I use some classes from this site:github
and I've stucked here, in my application I use a storyboard, but this menu is programmed with code, and doesn't have a view in stroyboard,
in the didSelectRowAtIndexPath method: I use like this
for(int j=0; j< 9 ; j++)
{
if(indexPath.row == j)
{
DetailsSidebarViewController *essayView = [[DetailsSidebarViewController alloc] init];
essayView.detailItem = [jsonResults objectAtIndex:j];
NSLog(#"%#=%d",essayView.detailItem,j);
}
}
and I create a DetailsSidebarViewController as the new view controller when I push from menu item. in this class, I create a method to configure the view, and just I echo the result:
- (void)configureView
{
// Update the user interface for the detail item.
if (self.detailItem) {
NSLog(#" %# ", self.detailItem);
}
}
the result is true like I want, but I want to push to another view controller, in fact nothing is happened when I click on an item into the menu.
How can I create the new view controller in storyboard? if the menu has not a storyboard, and how can I connect them with segues?
In fact, I am blue with it, please help!!
You should make an instance of the App Delegate class in the Side Menu view controller, and push it from there. You must have a reference to the Side Menu View and a reference to the Center View! Import your App Delegate into the Side Menu controller, and in the didSelectRowAtIndexPath, use:
DetailsSidebarViewController *essayView = [[DetailsSidebarViewController alloc] init];
essayView.detailItem = [jsonResults objectAtIndex:indexPath.row]; // note that you don't need the for loop to know what object you need
AppDelegate* myAppDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
[myAppDelegate.sideMenuViewController toggleSideMenu]; // the idea here is that you have to close the side menu, i.e. it must dissapear
[myAppDelegate.centerViewController.navigationController pushViewController:essayView animated:YES];
This is all the code you need.
If I may, as a piece of advice, use Mike Frederik's MFSideMenu instead of the library you're using. In my opinion, it has a more simple and a more straightforward implementation, just look into the README file, everything is explained pretty simple, and it has exactly the code you need!
Hope this helps, good luck!
I've got a SVC setup which works well. The master and detail views of the SPC are both UINavigationControllers, with separate root view controllers each.
My application works perfectly. I can start it up in either portrait or landscape mode and everything works as it aught to. The 'master' popup button is visible in portrait mode but hidden in landscape mode. Both master and detail planes animate and rotate properly, there is no popping or weird animation artifacts present, and everything stretches properly. 100% satisfaction per spec.
Issues arise when I replace the detail view controller. Since we cannot replace the root node of a uinavi controller, what I do is create a brand new UINavigationController, assigning it the new view I want in the detail view. I do this because the views in the detail view can do 1-2-3 levels deep, but I need to be able to assign unique 'root' views (eg I need to be able to completely replace the root UINavigationController).
The code looks like this:
self.detailViewController = [[SomeNewController1of3 alloc] initWithNibName:#"SomeNewController1of3" bundle:nil];
self.splitViewController.delegate = self.detailViewController;
UINavigationController *newNC = [[UINavigationController alloc] initWithRootViewController:self.detailViewController];
self.splitViewController.viewControllers = #[navigationController, newN];
First, I create the new viewcontroller, whatever it may be. I then set the splitviewcontroller's delegate to = the newly created viewcontroller (so that willHideViewController, etc fires). Then I create the a navicontroller to house the newly created viewcontroller, so that it can push and pop from it. And finally, I replace the second (detail) view of the split view controller with my new navi controller.
Again, this plan works flawlessly when I start in landscape.
When I start in portrait, these are the anomalies I've noticed:
All popups (alert boxes, the master view, etc) have weird rotations and flip out from an awkward spot when they are shown. It's almost as if it rotates these popups right as they're displayed, instead of having them simply 'slide' out of their correct locations.
When the master view is displayed, after doing the weird rotation describes above, it disappears completely for an instant (leaving a black, empty region) then reanimates to it's original location.
Until I complete a device rotation, the 'master' button on the detail view controller which displays the popup for the master view is not shown. After confirming with breakpoints, I've indeed concluded that willHideViewController is Not being called by my above method.
What I don't get is why everything behaves flawlessly when I start in landscape, but all of these errors arise in portrait? All of my views actually have a default orientation set to portrait in IB, and are scale to fit. Also it's not like they're crazy interfaces, they are all just stock tableviews.
Have any of you encountered this issue or found a method around it?
Firstly, the UISplitViewController delegate should be a top level object like your App Delegate, not a view controller the split controller is showing.
Secondly, to answer your question, to replace a showing detail view controller (i.e. pushed on the master nav) in portrait you can make use of a split controller delegate method:
AppDelegate.m
- (BOOL)splitViewController:(UISplitViewController *)splitViewController showDetailViewController:(UIViewController *)vc sender:(id)sender{
if(!splitViewController.isCollapsed){
return NO;
}
UINavigationController *masterNav = splitViewController.viewControllers.firstObject;
if(![masterNav.topViewController isKindOfClass:UINavigationController.class]){
return NO;
}
UINavigationController *existingDetailNav = (UINavigationController *)masterNav.topViewController;
UINavigationController *newDetailNav = (UINavigationController *)vc;
existingDetailNav.viewControllers = #[newDetailNav.viewControllers.firstObject];
return YES;
}
You might be wondering why we don't just replace the master nav stack. Well the problem is the split view controller preserves the detail view controller from the last time showDetail that went through its default behavior. So if we were in portrait, tapped on a cell to show detail, then replaced the detail, then navigate back then upon rotating to landscape the old detail will show on the right and not the new one. By replacing the detail nav stack with the new detail controller as above this solves this because although the split is showing the preserved detail nav it now contains the new detail.
I find it is easier to subclass the SplitViewController. From there, you can easily swap out the the detail adding a navController for those that need it.
Subclass UISplitViewController and set your root splitViewController to that class. Then add this method to your UISplitViewController subclass:
-(void)setDetailControllerTo:(UIViewController *)detailController withNavControllerTitle:(NSString *)title {
[detailController view]; // this line forces the viewDidLoad method to be called
if (title) {
UINavigationController *navController = [[UINavigationController alloc] init];
[navController pushViewController:detailController animated:YES];
detailController.title = title;
NSArray *viewControllers=#[self.mainController.viewControllers[0],navController];
self.mainController.viewControllers = viewControllers;
} else {
NSArray *viewControllers=#[self.mainController.viewControllers[0],detailController];
self.mainController.viewControllers = viewControllers;
}
}
To call this method do something like this from the master view controller in the tableView:didSelectRowAtIndexPath: method
FixedSplitViewController *splitController = (FixedSplitViewController*) self.splitViewController;
CurrentEventViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"CurrentEventViewController"];
// add any setup code here
[splitController setDetailControllerTo:controller withNavControllerTitle:#"Current Event"];
If you wish to keep the master view visible in portrait rotation, add this method to the SplitViewController subclass:
-(BOOL)splitViewController:(UISplitViewController *)svc shouldHideViewController:(UIViewController *)vc inOrientation:(UIInterfaceOrientation)orientation {
return NO;
}
I have a UITabBar with 5 tabs. I only wish to enable autorotation for a UIViewController that gets pushed onto the stack deep inside tab #3. So to be clear: tap UITabBar item 3, and you get tabbar item #3's root UIView, which should not autorotate. Tap and get another UIViewController pushed onto the stack (via a UINavigationController). Tap again, and get another UIViewController pushed onto the stack. Only here should this UIView autorotate.
The other 4 tabs should not rotate at all--not the root view of the tabs, nor any of the child views of the tabs.
Can someone tell me what approach I should use? I read that every single tab needs to respond "YES" to willAutorotateToInterfaceOrientation.
In each view's shouldAutorotate..., you could call a method in the root view controller that checks what is currently being displayed. If the deep-level view for tab 3 is on display, it will return YES, otherwise NO, and the views will, in turn, return the same.
Edit -- more detail per user798719's request:
Your root view controller knows which view is on display. You add a method to the root view controller - (BOOL) isDeepLevelTab3Displayed;. The method checks whether the deep-level view for tab 3 is on display and, if so, returns YES, otherwise returns NO.
Each sub view controller’s shouldAutorotate… method will get a ref to the root controller so that it can call isDeepLevelTab3Displayed.
If you’re using a navigation-style controller, you can get the ref like this (self is the sub controller):
NSArray *arrayOfControllers = [self viewControllers];
UIViewController *rootController = [arrayOfControllers objectAtIndex:0]; // per UIViewController class ref, root controller is at index 0
Or you could get anything in your project like this:
YourProjectAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
UIViewController *rootController = appDelegate.rootController; // or appDelegate.intermediateClass1.intermClass2.rootController — however you set up your project
So every sub controller would do this:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
UIViewController *rootController = [[self viewControllers] objectAtIndex:0];
return [rootController isDeepLevelTab3Displayed];
}
Therefore, every subcontroller would return YES when autorotation should happen, fulfilling the requirement you mention at the end of your question.
However, if all your subcontrollers are instances of UINavigationController, you could determine which view is currently on display directly, by calling visibleViewController. Then you just need a way of checking its identity.
You could check the controller’s nibName or title, for example, against a constant, or add an integer property intControllerIdentity to all your controllers and set them in the controller’s initWithNibName…
The integer-property scheme might be best, because it won’t be affected should you later change the nibName or title.
You’d add constants to some class whose h file is imported by all the controllers (or, if all the controllers are instances of the same class, put these constants in that class’s h file):
#define kFooController 1
#define kBarController 2
#define kRotatableController 3
And you’d set it like this:
self.intControllerIdentity = kRotatableController;
And check it like this:
if (self.intControllerIdentity == kRotatableController)
Hope that helps. But evaluate this added detail with a critical eye; I have worked with autorotation but not yet with navigation controllers.
I have a game with menu - level select - game vc
I'd like to dump the user out to the menu after the game, but I'm doing presentModalViewController's and if I keep a reference to level select VC in my game VC I can dismiss it, but then we see the level select screen for a split second before going back to the menu... ( no animations )
Is there another way to do this? All I know is having a top bar ( navigationController? ) which I don't want showing and this presentModal way I'm currently using to switch view controllers.
How I dismiss the game view controller:
- (void)dismissMe {
NSLog(#"dismissMe");
[mParentVC dismissModalViewControllerAnimated:NO];
// [self dismissModalViewControllerAnimated:NO];
}
You can set the rootViewController on the UIWindow object.
[[UIApplication] sharedApplication].keyWindow.rootViewController = theVCYouWantDisplayed
You can use UINavigationController with navigationBarHidden property set to YES
Okay, here is my issue: My app has a display of categories in the tab bar at the bottom of the iPhoneOS screen. This only allows 5 categories before it presents the MORE button. I have over 25 (please do not answer this by saying: "Rethink your application...etc" -- that was rudely said before. They are food, drink, etc categories and cannot be changed). I want to allow the user to put their favorites on the home page. The Apple moreNavigationController editing system only allows 20 tab bar items to be rearranged due to space constraints on the editing page. This is not enough so i need to implement my own Editing screen. I set the rightBarButtonItem to nil and created my own. Using NSLog, i can see the "click" happens when clicking the EDIT button, but I cannot push using pushViewController. Nothing happens. I think it has something to do with the navigationController I am addressing...but i am not sure. ps: This all happens in my App Delegate which DOES act as both UITabBarControllerDelegate & UINavigationControllerDelegate.
I tried to do the following:
- ( void )navigationController:( UINavigationController * )navigationController_local willShowViewController:( UIViewController * )viewController_local animated:( BOOL )animated
{
UIViewController * currentController = navigationController_local.visibleViewController;
UIViewController * nextController = viewController_local;
// Do whatever here.
NSLog(#"Nav contoller willShowViewController fired\n'%#'\n'%#'\nThere are currently: %d views on the stack\n",currentController,nextController,[self.navigationController.viewControllers count]);
if ( [nextController isKindOfClass:NSClassFromString(#"UIMoreListController")])
{
UINavigationBar *morenavbar = navigationController_local.navigationBar;
UINavigationItem *morenavitem = morenavbar.topItem;
morenavitem.rightBarButtonItem = nil;
NSLog(#"Is a UIMoreListController\n");
UIBarButtonItem *editTabBarButton = [[UIBarButtonItem alloc]
initWithTitle:#"Edit"
style:UIBarButtonItemStylePlain
target:self
action:#selector(editTabBar:)];
morenavitem.rightBarButtonItem = editTabBarButton;
[editTabBarButton release];
}
}
This works to place an EDIT button at the top right of the screen -- mimicking Apple's look and feel... but when that button is clicked, you cannot exit the darn moreNavigationController.
I have tried many things. UIAlerts work, etc...but pushing (or popping -- even popping to root view) a view controller on the stack does not.
- (void) editTabBar:(id)sender {
NSLog(#"clicked edit tabbar\n");
NSLog(#"Total count of controllers: %d\n",[self.navigationController.viewControllers count]);
TabBarViewController *tabBarViewController2 = [[TabBarViewController alloc] initWithNibName:#"TabBarView" bundle:nil];
tabBarViewController2.navigationItem.title=#"Edit Tab Bar";
[self.navigationController pushViewController:tabBarViewController2 animated:YES];
[tabBarViewController2 release];
NSLog(#"finished edit tabbar\n");
}
If you click the edit button on the moreNavigationController's display page, you get the log entries like expected AND (this is strange) the views on the stack climbs -- but no page change occurs. I marked it down to not using the correct navigation controller...but I am lost on how to find which one TO use.
this is a weird one too. In the edit function if i just do this:
- (void) editTabBar:(id)sender {
self.tabBarController.selectedIndex = 0;
}
It DOES take me home (to tabbarcontroller 0)
BUT doing this:
- (void) editTabBar:(id)sender {
[self.navigationController popToRootViewControllerAnimated:YES];
}
does not work.
Does the moreNavigationController have some special quality that screws with the rest of the system?
I would try reimplementing the whole "More" functionality from scratch. In other words, store the four home tabs in your user defaults and add a dummy fifth tab that switches to your own complete reimplementation of the more view controller stack.
You could even write a lightweight subclass of UITabBarController that handled this for you.
UITabBarController is evil, so I wouldn't be at all surprised if MoreController had some special properties, too.
I have had success intercepting the More Controller in shouldSelectViewController to change the data source; you may be able to find some workaround there.
PS I am inclined to agree that you could consider redesigning your app so that you didn't need an unlimited number of viewControllers attached to the tab bar just to select categories; you might have better luck using a tool bar with a single, scrollable, custom view in it. If that's really the best way of picking categories for your app, of course.