Suppress moreNavigationController in custom UITabBarController - ios

I have implemented a custom UITabBar solution for a project. Essentially, if there are more than 5 items, I use a scrollView that will allow the user to scroll through the additional tab items and suppresses the more button. A similar look and feel can be seen in the Weather Channel app.
Each tab bar item corresponds to a UINavigationController that manages the stack of views for each tab. The issue I'm having is when I have more than 5 tab items, from tab 5 onward does not correctly maintain the navigation stack. It seems that the moreNavigationController kills the navigation stack each time you return to that tab and you are brought to the initial page again.
I've overridden the setSelectedViewController method as follows:
- (void) setSelectedViewController:(UIViewController *)selectedViewController {
[super setSelectedViewController:selectedViewController];
if ([self.moreNavigationController.viewControllers count] > 1) {
self.moreNavigationController.viewControllers = [[NSArray alloc] initWithObjects:self.moreNavigationController.visibleViewController, nil];
}
}
This code will remove the More functionality on the left nav button but it doesn't solve the issue of maintaining the navigation stack. All other tabs work fine. I can traverse down several views and the stack is maintained after I leave and return to that tab. I understand that this is a complicated issue so please let me know if there are areas where I can provide clarity. Thanks!

This is how I ended up fixing this:
- (void) setSelectedViewController:(UIViewController *) selectedViewController {
self.viewControllers = [NSArray arrayWithObject:selectedViewController];
[super setSelectedViewController:selectedViewController];
}
Basically any tab from 5 on gets its navigation controller replaced by the moreNavigationController when you intiially set the viewControllers on UITabBarController. Therefore, I dynamically set viewControllers to just contain the tab I'm clicking. There never ends up being more than 1 in this case so the moreNavigationController doesn't come into play.
When I init my custom controller, I just supply the first tab as the viewControllers so the application can load.
- (id) init {
self = [super init];
if (self) {
self.delegate = self;
[self populateTabs];
}
return self;
}
- (void) populateTabs {
NSArray *viewControllers = [self.manager createViewsForApplication];
self.viewControllers = [NSArray arrayWithObject:[viewControllers objectAtIndex:0]];
self.tabBar.hidden = YES;
MyScrollingTabBar *tabBar = [[MyScrollingTabBar alloc] initWithViews:viewControllers];
tabBar.delegate = self;
[self.view addSubview:tabBar];
}
For clarity, the tabBar delegate is set to this class so that it can respond to tab clicks. The delegate method is as follows:
- (void) tabBar:(id) bar clickedTab:(MyScrollingTabBarItem *) tab {
 if (self.selectedViewController == tab.associatedViewController) {
[(UINavigationController *) tab.associatedViewController popToRootViewControllerAnimated:YES];
} else {
self.selectedViewController = tab.associatedViewController;
}
// keep nav label consistent for tab
self.navigationController.title = tab.label.text;
}

You may also override the moreNavigationController var of UITabBarController and return your own custom NavigaitonContorller like so:
override var moreNavigationController: UINavigationController {
return MyCustomNavController()
}
worked in my case where I needed a NavigaitonController that allowed hiding the NavigationBar when scrolling.
As for hiding the Back Button of the MoreNavigationController you can set the leftBarButtonItem of each NavigationItem to anything (even an empty UIBarButtonItem) and the back butotn of the more NavigaitonController will disappear.

Related

How to have navigation pane bar button item show up in detail view controller in portrait mode?

Initially, I set up a UISplitViewController by hand in Storyboards. I embedded both the master and detail view controller with a navigation controller.
My only problem is having the navigation pane bar button item show up in portrait mode so that the user can summon the left pane tableview.
I resorted to using Apple's Multiple View Detail code, doing pretty much what the fellow in this link did: UISplitView with Multiple Detail Views (with Storyboard)
In Storyboard, I set the master and detail view controllers explicitly. I was also able to set the delegate of the splitViewController using Interface Builder using an NSObject. But the delegate (below) never gets its method called below:
// "DetailViewManager.h"
-(void)setDetailViewController:(UIViewController<SubstitutableDetailViewController> *)detailViewController
{
self.detailViewController.navigationPaneBarButtonItem = nil;
self.detailViewController = detailViewController;
self.detailViewController.navigationPaneBarButtonItem = self.navigationPaneButtonItem;
UIViewController *navigationViewController = [self.splitViewController.viewControllers objectAtIndex:0];
NSArray *viewControllers = [NSArray arrayWithObjects:navigationViewController, self.detailViewController, nil];
self.splitViewController.viewControllers = viewControllers;
if (self.navigationPopoverController) {
[self.navigationPopoverController dismissPopoverAnimated:YES];
}
}
The initial detail view controller never gets its method to set the navigation bar button item called either:
// "DetailViewController.h"
-(void)setNavigationPaneBarButtonItem:(UIBarButtonItem *)navigationPaneBarButtonItem
{
if (navigationPaneBarButtonItem != self.navigationPaneBarButtonItem) {
if (navigationPaneBarButtonItem) {
[self.navigationController.toolbar setItems:[NSArray arrayWithObject:navigationPaneBarButtonItem] animated:NO];
} else {
[self.navigationController.toolbar setItems:nil];
}
}
}
Whenever I try to set the detail view controller programmatically when I first set the splitViewController as rootViewController, I get into an infinite loop with the first delegate method I just mentioned above:
// "LogVC.h"
- (IBAction)loginButtonPressed:(id)sender {
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
UISplitViewController *splitViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"splitViewController"];
DetailViewManager *detailViewManager = (DetailViewManager *)splitViewController.delegate;
detailViewManager.detailViewController = splitViewController.viewControllers.lastObject;
UIViewAnimationOptions transitionAnimation;
if (UIDeviceOrientationIsLandscape([UIDevice currentDevice].orientation)) {
transitionAnimation = UIViewAnimationOptionTransitionFlipFromLeft;
} else {
transitionAnimation = UIViewAnimationOptionTransitionFlipFromTop;
}
[UIView transitionWithView:appDelegate.window duration:0.75 options:transitionAnimation animations:^{
appDelegate.window.rootViewController = splitViewController;
} completion:nil];
}
I guess what I couldn't wrap my head around in Apple's provided code is when the delegate method is ever called, because I don't remember them configuring the detail view controller, whereas I set mine up in Storyboard, and then programmatically.
UPDATE:
I've been able to pass the instantiated view controller within Storyboard as the detail view controller to my detail view manager with the code below:
// "LogVC.h"
UIViewController <SubstitutableDetailViewController> *detailViewController = nil;
detailViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"identifier"];
DetailViewManager *detailViewManager = (DetailViewManager *)splitViewController.delegate;
detailViewManager.detailViewController = detailViewController;
The detail view gets set up correctly, and is displayed. But I'm still missing the navigation pane bar button item at the top left corner. In fact, I'm missing the entire navigation bar at the top, is this normal?
This is the code I used in the detail view controller to set up the navigation pane bar button item:
// "DetailViewController.h"
-(void)setNavigationPaneBarButtonItem:(UIBarButtonItem *)navigationPaneBarButtonItem
{
if (navigationPaneBarButtonItem != _navigationPaneBarButtonItem) {
if (navigationPaneBarButtonItem) {
[self.navigationController.toolbar setItems:[NSArray arrayWithObject:navigationPaneBarButtonItem] animated:NO];
} else {
[self.navigationController.toolbar setItems:nil];
}
_navigationPaneBarButtonItem = navigationPaneBarButtonItem;
}
}
I have the feeling that I can't simply add the navigationPaneBarButtonItem to self.navigationController.toolbar this way, that I must create my own toolbar. Is this correct?

iOS: Call actionsheet from tabbar

I've looked at all other answers for this topic on Stackoverflow but don't get really further. I've set up my Tabbar controller in Storyboard. I've defined the icons for the tabbar items also in Storyboard, the titles however I've set via code in their respective view controllers since my app offers multi-language support.
Now I want one single tabbar button which doesn't segue to another view but just calls an actionsheet. No matter on which other tabbar I am. So my questions are:
Where do I add this tab bar button? Because all other buttons I can only define after creating the segue to the new view controller
Where do I place the code for the action sheet?!
I'm not very clued up on the tabbar, however i would do the following:
Assuming you are using a TabBarController View or a Central View With a TabBar in it.
In the -viewDidLoad
NSMutableArray *tmp = [[NSMutableArray alloc] initWithArray:self.tabBarController.viewControllers];
UIViewController *sheet= [[UIViewContoller alloc] init];
sheet.title = #"Sheet";
sheet.tabBarItem.image = [UIImage....];
[tmp addObject:sheet];
[self.tabBarController setviewControllers:tmp];
self.tabBarController.delegate = self;
Then place the following
-(BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
{
if([viewController.Title isEqualToString:#"Sheet"])
{
//ActionSheet create code here
retrun NO;
}
else
{
return YES;
}
}

How to hide Tabbar in UITabbar Controller first 3 view Controller in ios7 [duplicate]

I have views with a navigation bar and a tab bar. What I would like to happen is to hide the tab bar on a certain view and show the tab bar again when the user changes views.
I saw a snippet of code for hiding the tab bar:
-(void)makeTabBarHidden:(BOOL)hide
{
// Custom code to hide TabBar
if ( [tabBarController.view.subviews count] < 2 ) {
return;
}
UIView *contentView;
if ( [[tabBarController.view.subviews objectAtIndex:0] isKindOfClass:[UITabBar class]] ) {
contentView = [tabBarController.view.subviews objectAtIndex:1];
} else {
contentView = [tabBarController.view.subviews objectAtIndex:0];
}
if (hide) {
contentView.frame = tabBarController.view.bounds;
}
else {
contentView.frame = CGRectMake(tabBarController.view.bounds.origin.x,
tabBarController.view.bounds.origin.y,
tabBarController.view.bounds.size.width,
tabBarController.view.bounds.size.height - tabBarController.tabBar.frame.size.height);
}
tabBarController.tabBar.hidden = hide;
}
from: http://nickwaynik.com/iphone/hide-tabbar-in-an-ios-app/
I call this on the view wherein I want the tab bar hidden
[self makeTabBarHidden:YES];
it works fine when i show/hide it on that view but when I navigate back to the previous view, the tab bar there is also hidden. I tried calling that function in the view's viewDidUnload, viewWillDisappear, viewDidDisappear functions but nothing happens. The same is true when the function is called in the previous view's viewDidLoad, viewWillAppear, viewDidAppear functions.
You can set the UIViewController.hidesBottomBarWhenPushed instead:
DetailViewController *detailViewController = [[DetailViewController alloc] init];
detailViewController.hidesBottomBarWhenPushed = YES;
[[self navigationController] pushViewController:detailViewController animated:YES];
[detailViewController release];
You can also do this in the Interface Builder for a storyboard. Select the View Controller that you want to hide the Tab Bar for and then select "Hide Bottom Bar on Push".
I just created a category on UITabBarController that allows you to hide the TabBar, optionally with an animation:
https://github.com/idevsoftware/Cocoa-Touch-Additions/tree/master/UITabBarController_setHidden
It adds the tabBarHidden property (with isTabBarHidden as its getter) and the - (void)setTabBarHidden:(BOOL)hidden animated:(BOOL)animated method.
Swift 3:
Set tab bar to hide in viewWillAppear or viewDidAppear
self.tabBarController?.tabBar.isHidden = true
Try this for hide / show:
- (void)viewWillDisappear:(BOOL)animated {
self.hidesBottomBarWhenPushed = NO;
}
- (void)viewWillAppear:(BOOL)animated {
self.hidesBottomBarWhenPushed = YES;
}
self.navigationController.hidesBottomBarWhenPushed=YES;
Add this line to your viewDidLoad or viewWillAppear; this will hide you tab from bottom.
The same property is available on the attributes inspector when you click on your view controller on your Xib or storyboard file.
you can use below code but tabBar remains hidden when you navigate back.
//hide tabbar
//self.tabBarController?.tabBar.isHidden = true
better way is to do through main.storyboard
check "Hide Bottom Bar on Push" as I've done.

Back button in standalone UINavigationBar with UINavigationController

I am trying to implement a push-up UINavigationBar, where the position of the navigation bar is attached to the contentOffset of the UIScrollView (similar to how safari works in ios7).
In order to get the dynamic movement working I am using a UINavigationBar created programatically and added as a subview of the UIViewController's view (it is accessible as self.navbar).
The UIViewController is within a UINavigationController hierarchy, so I am hiding the built-in self.navigationController.navigationBar at the top of -viewWillAppear:.
The problem I am trying to solve is to add a back button to this new standalone navbar. I would preferably like to simply copy the buttons or even the navigationItems from the navigationController and its hidden built-in navbar, but this doesnt seem to work
Is my only solution to set leftBarButtonItem on my standalone navbar to be a fake back button (when there is a backItem in the navController's navbar)? This seems a bit hacky, and I'd rather use the built backButton functionality.
Another way to do that, once you have your own UINavigationBar set, is to push two UINavigationItems on your navigationBar, causing back button to appear. You can then customize what happens when the back button is pressed.
Here's how I did that
1 - Some UINavigationItem subclass, to define extra-behavior / customization parameters
#interface MyNavigationItem : UINavigationItem
//example : some custom back action when 'back' is pressed
#property (nonatomic, copy) void (^onBackClickedAction)(void);
#end
2 - Then wire that into your UINavigationBarDelegate :
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
if ([item isKindOfClass:[MyNavigationItem class]]) {
MyNavigationItem *navItem = (MyNavigationItem *)item;
//custom action
if (navItem.backAction) {
navItem.backAction();
}
return YES;// return NO if you don't want your bar to animate to previous item
} else {
return YES;
}
}
You could adapt that scheme, calling your UINavigationController pop method on back action.
This is still hacky
Vinzzz' answer was a good solution. Here is my implementation, as the context was slightly different.
In the UIViewController's viewDidLoad method I setup my navbar's navigation items like this:
NSMutableArray* navItems = [#[] mutableCopy];
if (self.navigationController.viewControllers.count > 1)
{
NSInteger penultimateIndex = (NSInteger)self.navigationController.viewControllers.count - 2;
UIViewController* prevVC = (penultimateIndex >= 0) ? self.navigationController.viewControllers[penultimateIndex] : nil;
UINavigationItem* prevNavItem = [[UINavigationItem alloc] init];
prevNavItem.title = prevVC.title;
[navItems addObject:prevNavItem];
}
UINavigationItem* currNavItem = [[UINavigationItem alloc] init];
... <Add any other left/right buttons to the currNavItem> ...
[navItems addObject:currNavItem];
[self.navbar setItems:navItems];
...where self.navbar is my floating stand-alone UINavigationBar.
I also assign the current view controller to be self.navbar's delegate, and then listen for the -navigationBar:shouldPopItem: event that is triggered when the back button is pressed:
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
if (navigationBar == self.navbar)
{
[self.navigationController popViewControllerAnimated:YES];
return NO;
}
return YES;
}
(If you return YES, it will crash when a swipe gesture is used in ios7).

Prevent UINavigationBar popViewController animation

I have the following problem: I have overridden popViewControllerAnimated:(BOOL)animated of UINavigationController because I would like to have a custom animation. The code is as follows:
- (UIViewController *)popViewControllerAnimated:(BOOL)animated
{
UIViewController *poppedCtrl = [super popViewControllerAnimated:NO];
[((customViewController *) self.topViewController) doCustomAnimation];
return poppedCtrl;
}
Unfortunately the UINavigationBar seems to ignore that I explicitly disable the built in animation and it is still animated.
What do I have to do to also prevent the animation of the navigation bar?
After some reading and also some experimentation I finally found out what needs to be done to achieve the desired behavior.
To prevent the navigation bar from being animated it is not sufficient to override (UIViewController *)popViewControllerAnimated:(BOOL)animated.
It is also necessary to create a custom navigation bar and override (UINavigationItem *)popNavigationItemAnimated:(BOOL)animated:
- (UINavigationItem *)popNavigationItemAnimated:(BOOL)animated {
return [super popNavigationItemAnimated:NO];
}
Of course this custom navigation bar must also be the one which is used (I just replaced the navigation bar which is used by my navigation controller in the interface builder).
If anyones looking to disable push animation - this works for me, by overrideing this method on UINavigationBar:
- (void)pushNavigationItem:(UINavigationItem *)item {
NSMutableArray* items = [[self items] mutableCopy];
[items addObject:item];
self.items = items;
}

Resources