In order to customize the aspect of the navigationBar of my UINavigationController, I've been told to subclass the UINavigationBar class.
I need to change the height of the bar, placing an image instead of the title.. etc. I can't do it just using UINavigationBar properties.
Now, my issue is, how to assign to the UINavigationController, my CustomNavigationBar instance ? I can't use UINavigationController.navigationBar because it is read-only.
The only way seems to load a xib, but subclassing UINavigationController is not reccomended by Apple, so I'm a bit confused.
Depending exactly on how custom you want the nav bar, the following may help you:
1) An image in the bar, replacing the title text. (self is a UIViewController)
UIImageView *titleImg = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"image.png"]];
self.navigationItem.titleView = titleImg;
[titleImg release];
2) A totally custom nav bar
Subview UIView (CORRECTION: UINavigationController) with a nib, using (for example, of iPhone portrait orientation) a imageview as the background (could have a gradient) and then custom buttons on the left and right side, all hooked up to your relevant responder functions. As long as you add this nav bar view as a subview to each view controller etc each time a new one is pushed to the stack, you would have overriden Apple's standard nav bar. (remember to set theirs to hidden in the app delegate)
Edit:
NavigationBar.h
#interface NavigationBar: UIViewController
{
IBOutlet UIImageView* navigationBarImgView; // remember to set these 4 IBOutlets as properties with (nonatomic, retain) and synthesize them.
IBOutlet UILabel* controllerHeader;
IBOutlet UIButton* rightButton;
IBOutlet UIButton* leftButton;
eController controller;
int screenWidth;
}
NavigationBar.m
-(id)initNavigationBar: (NSString*)name bundle:(NSBundle*)bundle: (eController)controllerName: (int)inScreenWidth
{
[super initWithNibName:name bundle:bundle];
controller = controllerName;
screenWidth = isScreenWidth;
return self;
}
You can see my init here only passes in a few things - an enum which was defined in my app delegate which contains the names of all possible view controllers. We interpret this enum in my custom Navigation Bar class and switch between all the possible controllers, and then setup their buttons and titles and the actions that get fired off using that information. The screen width is important - although you may not need this if you create the bar in IB, set the size of it to fill up the allocated space for both orientations.
Keep me posted :)
Related
I make use of ICViewPager to create tabs of contents. However, the layout looks weird as there are strange spaces at the top & bottom of ICViewPager's content view.
As you can see below, I have a UINavigationBar at the top of the screen, which is generated by the embedding UINavigationController. Then, the UINavigationController is made to be one of the tabs in a UITabbar Controller. Here is the structure:
UITabbarController --> UINavigationController --> TabVC (which contains ICViewPager) --> Content views: Content1VC, Content2VC, Content3VC
Here are the codes in TabVC (which configs to have <ViewPagerDataSource, ViewPagerDelegate>):
// in viewDidLoad
self.dataSource = self;
self.delegate = self;
self.edgesForExtendedLayout = UIRectEdgeNone;
and for the delegate methods:
#pragma mark - ViewPagerDataSource
- (NSUInteger)numberOfTabsForViewPager:(ViewPagerController *)viewPager {
return tabsContents.count;
}
- (UIView *)viewPager:(ViewPagerController *)viewPager viewForTabAtIndex:(NSUInteger)index {
UILabel *label = [UILabel new];
label.text = [tabsContents objectAtIndex:index];
label.textColor = [UIColor colorWithRed:136/255.0 green:136/255.0 blue:136/255.0 alpha:1.0f];
[label sizeToFit];
return label;
}
- (UIViewController *)viewPager:(ViewPagerController *)viewPager contentViewControllerForTabAtIndex:(NSUInteger)index {
UIViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:[tabsVC objectAtIndex:index]];
return vc;
}
The functions look okay, but the layout does not span through the whole spaces as it expects to do so. The red spaces (I made the TabVC view's background color to red to illustrate the issue) are not expected to appear. How do I make the ICViewPager occupy the red spaces?
Note: This appears only after the view is popped back from a pushed view controller, or changing tabs in UITabbarController
I think it is a conflict between automaticallyAdjustsScrollViewInsets and edgesForExtendedLayout.
From this answer :
edgesForExtendedLayout
Basically, with this property you set which sides of your view can be extended to cover the whole screen. Imagine that you push a UIViewController into a UINavigationController, when the view of that view controller is laid out, it will start where the navigation bar ends, but this property will set which sides of the view (top, left, bottom, right) can be extended to fill the whole screen.
and
automaticallyAdjustsScrollViewInsets
This property is used when your view is a UIScrollView or similar, like a UITableView. You want your table to start where the navigation bar ends, because you wont see the whole content if not, but at the same time you want your table to cover the whole screen when scrolling. In that case, setting edgesForExtendedLayout to None won't work because your table will start scrolling where the navigation bar ends and it wont go behind it.
So, automaticallyAdjustsScrollViewInsets defaults to YES and thus inserts a positive inset at the top equal to the height of the nav bar. now when you apply self.edgesForExtendedLayout = UIRectEdgeNone, that inset creeps out from under the nav bar causing said issue.
The following does not work
UILabel *titleLabel = (UILabel *)self.navigationController.navigationItem.titleView;
OR
UILabel *titleLabel = (UILabel *)self.navigationItem.titleView;
Both return nil.
Yes, the title in question is in the navigation bar. The objective is simple: I want the UILabel. Can that be done?
UPDATE
One of the reasons I needed the UILabel is so that I could grab its width after I fill it with text, which would essentially tell me the max width of the label (basically a device agnostic way of measuring: whether iPad, iPhone, etc).
The short answer: You can't. UINavigationBar 's internals handle the display of a UIViewController's title. The titleView property of UINavigationItem is used for other purposes.
The long answer
Look at UINavigationBar.h and get a rough understanding of how they both work together. - You'll quickly see why the titleView has nothing to do with a viewController's title.
UINavigationItem is the dataSource for a UINavigationBar.
It holds the barButtonItems, the title and the titleView for each UIViewController.
Look at the UIViewController (UINavigationControllerItem) category defined in UINavigationController.h there is a good explanation in the comments there.
The UINavigationBar on the other hand is 'simply' displaying the content of UINavigationItems.
When you push a viewController onto the stack of a navigationController, the navigationController implicitly calls UINavigationBar: pushNavigationItem:animated: on its navigationBar, sending the UINavigationItem of the viewController you are pushing.
I see you have the same misconception that i had when i started digging into UINavigationControllers:
The fact that you can write things like UILabel *titleLabel = (UILabel *)self.navigationController.navigationItem.titleView; without any warning is very misleading
UINavigationController does not have a UINavigationItem. It will never have.
This is because a navigationController will never be contained inside another navigationController. (If you try to build this containment in IB it will give you a warning i think !?) The reason you can call self.navigationController.navigationItem is that the category i mentioned above is giving EVERY UIViewController a UINavigationItem property. UINavigationController in turn IS a UIViewController and thus receives the property from the category within its own implementation. - That's very confusing!
I assume you want to grab a reference to the titleLabel (even though you can't !), because you want to set the font or the textcolor of the title. If so, you can do these things through UINavigationBar: setTitleTextAttributes:.
If you want to use a complete custom titleView, you are better off creating a UILabel and assigning it to the titleView property of the UINavigationItem
this works for me:
- (UILabel *)getNavBarTitleLabel
{
UINavigationBar *navBar = self.navigationController.navigationBar;
if (!navBar) { return nil; }
for (UIView *subview in navBar.subviews) {
for (UIView *subSubview in subview.subviews) {
if ([subSubview isMemberOfClass:[UILabel class]])
{
// consider adding more checks to verify that this UILabel is indeed the title label before returning it
// for example, if you know what the text should be you can compare the strings
return (UILabel*)subSubview;
}
}
}
return nil;
}
I have UITabbarController with UINavigationController in it. I have a subclass of UIView that I assign as the view of UIViewController in the navController. This is pretty standard stuff, right? This is how I do it
_productCategoryView = [[ProductCategoryView alloc] initWithFrame:self.view.frame];
self.view = _productCategoryView;
This view has a UITableView as subView
_productCategoryTableView = [[UITableView alloc] initWithFrame:self.frame style:UITableViewStylePlain];
_productCategoryTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
_productCategoryTableView.backgroundColor = [UIColor clearColor];
[self addSubview:_productCategoryTableView];
For the sake of debugging I am setting self.backgroundColor = [UIColor blueColor] on the view.
From the above initialization of tableView one might think that the view's and table's frame is same. However when I run in iOS 7, the view's origin is set behind the UINavigationBar. This is understandable because I am setting self.navigationBar.translucent = YES; in my subclass of UINavigationController. But what I don't understand is how come the table is sitting just below the navBar? Shouldn't it also start from (0, 0) which is behind the navBar? See screenshot Scenario 1 below. Notice the blue hue behind navBar
Now, I push another viewController on the navigation stack, simply by using [self.navigationController pushViewController.....]. Again I have a custom UIView with a tableView in it. However I also have a UILabel above this table, and again for debugging, I gave it a redColor. This time I am setting the label's origin to be almost same as the view's
CGRect boundsInset = UIEdgeInsetsInsetRect(self.bounds, UIEdgeInsetsMake(10, 10, 10, 10));
CGSize textSize = [_titleLabel.text sizeWithFont:_titleLabel.font
constrainedToSize:CGSizeMake(boundsInset.size.width, MAXFLOAT)
lineBreakMode:NSLineBreakByWordWrapping];
printSize(textSize);
_titleLabel.frame = CGRectMake(boundsInset.origin.x,
boundsInset.origin.y,
boundsInset.size.width,
textSize.height);
So, going by the logic above, the label should be visible, right? But this time it's not. This time the label is behind the navBar.
Notice, the red hue behind navBar.
I would really like to align the subView below the navBar consistently. My questions are
1. How is the tableView offset by 64pixels (height of nav + status bar in iOS 7) automatically, even though it's frame is same as the view's?
2. Why does that not happen in the second view?
By default, UITableViewController's views are automatically inset in iOS7 so that they don't start below the navigation bar/status bar. This is controller by the "Adjust scroll view insets" setting on the Attributes Inspector tab of the UITableViewController in Interface Builder, or by the setAutomaticallyAdjustsScrollViewInsets: method of UIViewController.
For a UIViewController's contents, if you don't want its view's contents to extend under the top/bottom bars, you can use the Extend Edges Under Top Bars/Under Bottom Bars settings in Interface Builder. This is accessible via the edgesForExtendedLayout property.
Objective-C:
- (void)viewDidLoad {
[super viewDidLoad];
self.edgesForExtendedLayout = UIRectEdgeNone;
}
Swift 2:
self.edgesForExtendedLayout = UIRectEdge.None
Swift 3+:
self.edgesForExtendedLayout = []
#Gank's answer is correct, but the best place to do this is on the UINavigationControllerDelegate (if you have one):
func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController, animated: Bool) {
viewController.edgesForExtendedLayout = UIRectEdge.None
}
I want to make an app, which uses a navigationbar, but just for showing the title and some buttons (it is using a vie container). I want the navigation bar to "fusion" with the status bar, like this
But when I use a generic view controller and drag a navigation bar in it just looks as this:
Is there a way to do this?
I've found a solution for this specific problem. Set the navigation bar's translucent property to NO:
self.navigationController.navigationBar.translucent = NO;
UINavigationController will alter the height of its UINavigationBar to either 44 points or 64 points, depending on a rather strange and undocumented set of constraints. If the UINavigationController detects that the top of its view’s frame is visually contiguous with its UIWindow’s top, then it draws its navigation bar with a height of 64 points. If its view’s top is not contiguous with the UIWindow’s top (even if off by only one point), then it draws its navigation bar in the “traditional” way with a height of 44 points. This logic is performed by UINavigationController even if it is several children down inside the view controller hierarchy of your application. There is no way to prevent this behavior.
Full explanation here.
Not sure whether this will fix your issue, but you can try what i did.
I had the same problem in viewcontroller's right side menu "Simulated Metics" > Top Bar > "Opaque Navigation Bar"
viewcontroller.h file in create iboutlet of the navigation bar
#property (weak, nonatomic) IBOutlet UINavigationBar *navBar;
You can do as followings in viewDidLoad
UIView *addStatusBar = [[UIView alloc] init];
//draw status bar with width of device
addStatusBar.frame = CGRectMake(0, -20, self.view.frame.size.width, 20);
//set the background colour to status bar
addStatusBar.backgroundColor = [UIColor colorWithRed:24.0/255. green:24/255. blue:24.0/255. alpha:1];
[self.view addSubview:addStatusBar];
// add your status bar to your UINavigationBar *navBar which declared in your file .h
[self.navBar addSubview:addStatusBar];
navigationController.navigationBar.clipsToBounds = YES;
In order for UINavigationBar to extend its background under status bar its clipsToBounds must be set to NO (which is the default). Make sure you do not mock around with it.
Solution simple as:-
navigationController.navigationBar.clipsToBounds = NO;
Swift 5
navigationController?.navigationBar.isTranslucent = false
To customize my navigation bar, I did several steps:
create a subclass which inheriting from UINavigationBar class, do some customization like draw shadow or setting background image for the navigation bar.
create an empty xib file, which contains nothing but a navigation view controller.
set the class name for the navigation bar in the navigation view controller.
Everything works fine, but when I want to add another customized back button on the navigation bar, I tried to attach a UIBarButtonItem to the navigationItem.backbarbuttonitem, I have no idea how to get the navigationItem from the UINavigationBar subclass.
code sample:
// header file
#interface MyNavigationBar : UINavigationBar
#end
// implementation file
#implementation MyNavigationBar
-(void)drawRect:(CGRect)rect
{
// background image
UIImage* background_image = [UIImage imageNamed:#"my-navigation-bar.png"];
[self setBackgroundImage:background_image forBarMetrics:UIBarMetricsDefault];
// draw shadow
self.layer.masksToBounds = NO;
self.layer.shadowOpacity = 0.6;
self.layer.shadowOffset = CGSizeMake(0, 3);
}
#end
Is there any way to get the navigationItem entry in my customized UINavigationBar subclass, or I just did it the wrong way? :P
thanks :)
you can hide the uinavigationbar. then put a uiview which can have height and width of uinavigationbar. and then add as many as buttons can fit or you want. add uiimage to the view. you will have a nice customized uinavigation bar. you can mimic the back button by using popviewcontroller or poptorootviewcontroller.