Hiding a UINavigationController's UIToolbar during viewWillDisappear:
I've got an iPhone application with a UITableView
menu. When a row in the table is selected, the appropriate view controller is pushed onto the application's UINavigationController
stack.
My issue is that the MenuViewController
does not need a toolbar, but the UIViewControllers
which are pushed onto the stack do. Each UIViewController
that gets pushed calls setToolbarHidden:animated:
in viewDidAppear:
. To hide the toolbar, I call setToolbarHidden:animated:
in viewWillDisappear:
.
Showing the toolbar works, such that when the pushed view appears the toolbar slides up and the view resizes correctly. However, when the back button is pressed the toolbar slides down but the view does not resize. This means that there's a black strip along the bottom of the view as the other view transitions i开发者_JS百科n. I've tried adding the toolbar's height to the height of the view prior to hiding the toolbar, but this causes the view to be animated during the transition so that there's still a black bar.
I realise I can manage my own UIToolbar, but I'd like to use UINavigationControllers
built in UIToolbar for convenience.
This forum post mentions the same issue, but no workaround is mentioned.
I too have experienced this problem. In my case, the only way I found to successfully hide the toolbar without showing the background of the window is to call [self.navigationController setToolbarHidden:YES animated:animated]
in your view controller’s -viewDidAppear:
method.
I wasn't satisfied with the answers on this question so I posted my own: Reference to source view controller and destination view controller at the same time
The answer I got fixed my problem. It may work for yours too (though this question is pretty old, I figured this might help someone like me who read this post a half dozen times looking for a hint).
Here's what I did. I don't know if marker protocols are idiomatic objective-c or not, but I liken them to attributes like I'd use in c# so I have this marker protocol:
@protocol HidesNavigationItem
@end
I added UINavigationControllerDelegate to my AppDelegate. I'm not sure yet whether or not that's a good thing. I thought about keeping that implementation in another object, but for now, this is where I put it. Here's the implementation:
#pragma mark Navigation Controller Delegate
-(void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
[navigationController setNavigationBarHidden:[viewController conformsToProtocol:@protocol(HidesNavigationItem)] animated:animated];
}
This way, I can just set my marker protocol on my UIViewController implementation like so:
@interface MyViewController : UIViewController <HidesNavigationItem>
If I don't have that interface, it puts it back.
Finally, in my appDelegate's application:didFinishLaunchingWithOptions: method, I wire up the delegate like this:
if ([self.window.rootViewController isMemberOfClass:[UINavigationController class]])
((UINavigationController*)self.window.rootViewController).delegate = self;
Now I get no black boxes and no Cheshire Cat. My solution was with regard to the navigation bar of course, but I'm sure it works the same for the toolbar. This is very similar to Danra's answer except that I get the black box without the "animated:animated."
For the UIViewController that does not need a toolbar when pushed you can consider using either
Implementing the hidesBottomBarWhenPushed method for that UIViewController:
// method to be added to the UIViewController that has no toolbar
- (BOOL) hidesBottomBarWhenPushed {
return YES;
}
Or prior to pushing in the UIViewController, set the value of hidesBottomBarWhenPushed:
viewControllerWithNoToolBar.hidesBottomBarWhenPushed = YES
[self.navigationController pushViewController:viewControllerWithNoToolBar animated:YES];
Try implementing a UINavigationControllerDelegate and setting it to your navigation controller's delegate property. This achieved for me what you are describing in your post, with no visible artifacts.
The following code assumes that secondController is pushed into the navigation view by an action performed in the firstController.
MyNavigationControllerDelegate.h
@interface MyNavigationControllerDelegate : NSObject<UINavigationControllerDelegate> {
}
-(void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated;
-(void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated;
@end
MyNavigationControllerDelegate.m
#import "MyNavigationControllerDelegate.h"
#import "AppDelegate_Shared.h"
@implementation MyNavigationControllerDelegate
-(void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
if ([AppDelegate_Shared sharedDelegate].firstController == viewController ) {
[navigationController setNavigationBarHidden:TRUE];
[navigationController setToolbarHidden:FALSE];
}
}
-(void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
if ([AppDelegate_Shared sharedDelegate].secondController == viewController ) {
[navigationController setNavigationBarHidden:FALSE];
[navigationController setToolbarHidden:TRUE];
}
}
@end
sharedDelegate is just a helper method:
AppDelegate_Shared.m
+ (AppDelegate_Shared*)sharedDelegate {
return (AppDelegate_Shared*)[[UIApplication sharedApplication] delegate];
}
To show toolbar in new view controller just add this:
- (void)viewWillAppear:(BOOL)animated
{
[self.navigationController setToolbarHidden:NO animated:animated];
[super viewWillAppear:animated];
}
To hide toolbar:
- (void)viewWillAppear:(BOOL)animated
{
[self.navigationController setToolbarHidden:YES animated:animated];
[super viewWillAppear:animated];
}
When traveling between screens push new view controller with following code:
SettingsRecordingViewController *vc = [[SettingsRecordingViewController alloc] initWithNibName:@"SettingsRecordingViewController" bundle:[NSBundle mainBundle]];
[self.navigationController pushViewController:vc animated:YES];
[vc release];
and if it has different state of toolbar (hidden/shown) then nice animation of hiding/showing toolbar will be shown.
The issue here is that the UITableView
's frame is set so that it does not overlap with the UIToolbar
. That is, it sits just above the UIToolbar
. When you push the next UIViewController
to the UINavigationController
stack, while removing the UIToolbar
, there is nothing to show but the UIWindow
behind it, unless you put something there in its place.
One workaround without the awkward animation after transition is to place your UITableView
in a UIView
"container" that shares the same frame as your regular view, but underlaps the UIToolbar
with the desired colour that you wish to see during the transition (e.g. white).
To enact the underlap, you could set your UIViewController
to wantsFullScreenLayout = YES
. You would then ensure that your UITableView
has the same frame as it would have before the use of the container. i.e. it sits below the navigation bar, and above the toolbar.
This can be made more elegant by writing a custom UIViewController
and use that instead of the UITableViewController
, or by being sneaky and inserting the new UIView
"container" beneath the UITableView
in your existing UITableViewController
.
I use a custom background image for the toolbar background, and a custom image for the table background. I was having the same issue with the black bar at the bottom as the view transitioned back and forth from another table view. However I set the
self.navigationController.toolbar.barStyle = UIBarStyleBlackTranslucent;
in viewDidLoad.
This sets the background image to the full potential size of the background, which if you had a transparent toolbar would make sense. It's probably not a good workaround when you're using the standard opaque toolbar, but for those of you customizing the toolbar anyway, you can get the best of both worlds.
I am in agreement with Jeff's answer. But there is a UI glitch if I hide the toolbar in -viewDidAppear method of the viewController through which different viewControllers are pushed.
To avoid this, I was experimenting and found out that calling -setToolbarHidden in the -viewWillAppear call indeed hides the toolbar, but as the question states, the view though expanded would not be occupied by the tableview rows.
To fix this, I have changed to following code and now it works without the glitch:
- (void)viewDidLoad
{
[super viewDidLoad];
.
.
.
[self reframeRowHeight];
[self.menuItemTableView addObserver:self
forKeyPath:@"frame"
options:NSKeyValueObservingOptionNew
context:nil];
[self.menuItemTableView setBounces:NO];
.
.
.
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:@"frame"])
{
[self reframeRowHeight];
}
}
-(void)reframeRowHeight
{
[self.menuItemTableView setRowHeight:self.menuItemTableView.frame.size.height/self.menuItems.count];
[self.menuItemTableView reloadData];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
.
.
.
// Bad Apple! - http://stackoverflow.com/questions/2339721/hiding-a-uinavigationcontrollers-uitoolbar-during-viewwilldisappear
[self.navigationController setToolbarHidden:YES animated:YES];
.
.
.
}
Managing the toolbar state (i.e. which VC needs/doesn't need the toolbar) gets tricky quickly.
I've had success with following this rule:
For each view controller, in viewWillAppear()
, decide if it needs a toolbar or doesn't, then call navigationController?.setToolbarHidden(true or false, animated: animated)
respectively.
This way, each view controller starts out with the correct toolbar state and you don't have to worry about "restoring" the toolbar state when the view controller is dismissed.
This is just a wild stab in the dark but maybe you should let the runloop run once after hiding the toolbar:
[viewController setToolbarHidden:YES animated:YES];
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.0]];
I had the same problem and here is the solution that worked for me. Say you're pushing SomeUIViewController
onto your navigation stack.
Define this (private) ivar in the interface of SomeUIViewController
:
// keep a reference to the navigation controller for use in viewDidDisappear:(BOOL)animated method
UINavigationController * _navigationController;
Implement the following methods of SomeUIViewController
:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
// here, your controller still has a reference to self.navigationController
_navigationController = [self.navigationController retain];
}
- (void)viewDidDisappear:(BOOL)animated {
// at this point, self.navigationController = 0x0, so
// use your retained reference to the navigation controller to perform any last minute operations, then release
[_navigationController setToolbarHidden:YES];
[_navigationController release];
[super viewDidDisappear:animated];
}
The idea is that you want to hide the toolbar owned by the navigation controller after SomeUIViewController
's view has disappeared. This way, you avoid any unwanted display artifacts.
DISCLAIMER
This is merely an answer that shows a workaround solution for the stated question. It is meant solely to point out a detail of the inner workings of the framework. It is also meant as an example of what not to submit to the Apple AppStore.
精彩评论