Centering a custom navigation bar item using layout anchors - ios

I have created a custom navigation item (with both title and a UISearchBar) to replace the standard navigation title on iOS and I am trying to center it in a UINavigationBar using layout anchors.
However, the layout constraint [self.navigationItem.titleView.centerXAnchor constraintEqualToAnchor:self.navigationController.navigationBar.centerXAnchor].active = YES; does not work and the UIView containing the search bar shows up on the left side of the UINavigationBar.
Other constraints:
The width should be 1/3 of the screen width
I want to avoid setting width as a constant to make the view responsive in response to orientation and other layout changes
How to center the navigation bar item?
ScreenViewController.m:
- (void) viewDidLoad {
[super viewDidLoad];
// Create the search bar
self.searchBar = [SearchBar new];
// Create a new UIView as titleView (otherwise I'm receiving an error)
self.navigationItem.titleView = [UIView new];
// Add into view hierarchy and turn off auto constraints
[self.navigationController.navigationBar addSubview:self.navigationItem.titleView];
[self.navigationItem.titleView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.navigationItem.titleView addSubview:self.searchBar.view];
[self.searchBar.view setTranslatesAutoresizingMaskIntoConstraints:NO];
// Set anchors for the wrapping view
[self.navigationItem.titleView.widthAnchor constraintEqualToAnchor:self.navigationController.navigationBar.widthAnchor multiplier:1.0/3.0].active = YES;
//[self.navigationItem.titleView.heightAnchor constraintEqualToConstant:50.0].active = YES;
[self.navigationItem.titleView.topAnchor constraintEqualToAnchor:self.navigationController.navigationBar.topAnchor].active = YES;
[self.navigationItem.titleView.centerXAnchor constraintEqualToAnchor:self.navigationController.navigationBar.centerXAnchor].active = YES;
[self.navigationItem.titleView.bottomAnchor constraintEqualToAnchor:self.navigationController.navigationBar.bottomAnchor].active = YES;
// Set anchors for the search bar
[self.searchBar.view.widthAnchor constraintEqualToAnchor:self.navigationItem.titleView.widthAnchor multiplier:1.0].active = YES;
//[self.navigationItem.titleView.heightAnchor constraintEqualToConstant:50.0].active = YES;
[self.searchBar.view.topAnchor constraintEqualToAnchor:self.navigationItem.titleView.topAnchor].active = YES;
[self.searchBar.view.bottomAnchor constraintEqualToAnchor:self.navigationItem.titleView.bottomAnchor].active = YES;
//[self.searchBar.view.leftAnchor constraintEqualToAnchor:self.navigationItem.titleView.leftAnchor].active = YES;
//[self.searchBar.view.rightAnchor constraintEqualToAnchor:self.navigationItem.titleView.rightAnchor].active = YES;
[self.searchBar.view.centerXAnchor constraintEqualToAnchor:self.navigationItem.titleView.centerXAnchor].active = YES;
}
Expected result:
What I get (the title view is not centered):
View hierarchy:

What you're doing is both impossible and illegal. You are not making a "custom navigation bar item" at all (as claimed in your title). You are attempting to add a subview directly to a navigation bar. You can't do that. The way to put something into a navigation bar is to assign it into a view controller's navigationItem, either as a bar button item or as the navigation item's real titleView. (You call your variable titleView, but you are not in fact making it the navigation item's titleView.)

Here is my solution, changing only the title view, setting its width anchor to a fraction of the navigation bar's to make it responsive:
// Create search bar and set it as title view
self.searchBar = [SearchBar new];
self.navigationItem.titleView = self.searchBar.view;
// Turn auto constraints off
[self.searchBar.view setTranslatesAutoresizingMaskIntoConstraints:NO];
// Set the layout anchors
[self.navigationItem.titleView.widthAnchor constraintEqualToConstant:250.0].active = YES; // Had to set the width to a constant
// Was still causing problems
//[self.navigationItem.titleView.widthAnchor constraintEqualToAnchor:self.navigationController.navigationBar.widthAnchor multiplier:1.0/3.0].active = YES;// Still need to access the navigation bar's anchor
[self.navigationItem.titleView.heightAnchor constraintEqualToConstant:50.0].active = YES;

Related

UIView under Navigation Bar after full screen

I have an application with a navigation bar.
When I click on a photo preview in my application, it displays the photo in full screen. But when I close the full screen, the view of my controller is under the navigation bar.
I saw on StackOverflow that it was necessary to add this line:
self.edgesForExtendedLayout = []
It works but it creates another bug.
So I would like to know if there was another alternative?
UPDATE :
1- Make your navigation bar opaque.
self.navigationController.navigationBar.translucent = NO;
2- If you are using Xib then check “Safe Area Layout Guides” in the File Inspector as shown below.
3- And if you want to do it programmatically then after adding on view
self.yourView.translatesAutoresizingMaskIntoConstraints = false;
if (#available(iOS 11, *)) {
UILayoutGuide * guide = self.view.safeAreaLayoutGuide;
[self.yourView.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor].active = YES;
[self.yourView.trailingAnchor constraintEqualToAnchor:guide.trailingAnchor].active = YES;
[self.yourView.topAnchor constraintEqualToAnchor:guide.topAnchor].active = YES;
[self.yourView.bottomAnchor constraintEqualToAnchor:guide.bottomAnchor].active = YES;
}

Unable to click "under" a hidden TabBar

I hide my tab bar like so:
self.tabBarController.tabBar.hidden = YES;
And because now there is a black bar where it once stood I stretch the view which is a UIWebView on top(or is it under?) that empty space. The UIWebView is in a UIViewController. I do that with a constraint which by default is like so:
The code for the constraint:
if(self.tabBarController.tabBar.hidden){
self.webviewBottomConstrain.constant = -self.tabBarController.tabBar.frame.size.height;
}else{
self.webviewBottomConstrain.constant = 0;
}
However if I tap the device on the place where the TabBar was it will not execute. It is as if there is something invisible there with the size of the tab bar. I have also tried hiding it the way this thread sugests. Still the same result.
Update: It seems that when you tap on the invisible tab bar the tap is recognized by the tab bar and not by the view that is visible under the tab bar
self.extendedLayoutIncludesOpaqueBars = YES;
this will solve you problem
You hide your tabBar by setting its hidden property to NO? Try setting it to YES. Unless I am misunderstanding what you are trying to do, it seems like your tab bar is not hidden with that code.
Another thing I would check is to see if User Interaction Enabled is checked for the web view. If it is not, that can seem like there is something invisible blocking you from interacting with your view.
Well I am using quite ugly hack to fix this. I am hiding the tab bar in another way now:
if (shouldShow) {
self.hidesBottomBarWhenPushed = NO;
UIViewController *someView = [[UIViewController alloc] init];
[self.navigationController pushViewController:someView animated:NO];
[self.navigationController popToViewController:self animated:NO];
} else if (shouldHide) {
self.hidesBottomBarWhenPushed = YES;
self.tabBarController.hidesBottomBarWhenPushed = YES;
self.navigationController.hidesBottomBarWhenPushed = YES;
UIViewController *someView = [[UIViewController alloc] init];
[self.navigationController pushViewController:someView animated:NO];
[self.navigationController popToViewController:self animated:NO];
}
I do need that random view because I cannot push the view on itself.
I had the same issue when hiding the tab bar by moving it offscreen to the bottom. My custom UITabBarViewController was intercepting the touch events in the area vacated by the tab bar, so instead of changing the frame of the tab bar to move the tab bar offscreen, I extended the height of my tab bar view controller so that the tab bar still moved offscreen, but the child view above the tab bar now filled that space. This allowed the touches to be received by the child view.
As you may see with view hierarchy instrument, UITabBar is not directly blocking your tap, but your current view controller's view height is not full screen:
So, the tap doesn't response because your finger's y position is higher than view's maxY.
Code like this (inside your UITabBarController) will expand your view's height, according to tabbar visibility, and all tap events will work correctly.
func updateTabBarAppearanceWithDegree(_ degree: CGFloat) {
let screenHeight = UIScreen.main.bounds.size.height
let tabBarHeight = self.tabBar.frame.size.height
self.tabBar.frame.origin.y = screenHeight - tabBarHeight * degree
self.tabBar.alpha = degree
let currentNavigation = self.selectedViewController as? UINavigationController
if let currentTopView = currentNavigation?.viewControllers.last?.view {
currentTopView.frame.size.height = self.tabBar.frame.origin.y
}
}

iOS hidesBarsOnSwipe status bar background color

When I swipe and hide the navigation bar with the hidesBarsOnSwipe property the status bar has a clear background. How can I set the background of the status bar to the same color as the navigation bar? Here are a few pictures showing my problem, this is all contained in a UITableViewController.
Separate
Separate picture, looks like one big one.
I've come across the same issue, and was able to solve it. I'm fairly new to iOS dev, and I don't imagine this solution to be foolproof. I couldn't find any good answers elsewhere, so here's how I overcame it:
I converted from a UITableViewController over to UIViewController with a nested UITableView. Note, double check that the delegate to the child tableview is set to the UIViewController.
I Added a view with a height of 20px and a background colour that you want to set as the "background" to the status bar. Set the constraints on that view as follows:
On your table view, set the constrains to be basically full screen. One important note here, the top constraint is to "Top Layout Guide.Top" and not to "Top Layout Guide.Bottom". By default I believe this constraint ties to the bottom. Double clicking on the constraint allows you to adjust it to the top. Without this, any table header cells weren't positioned properly for me
Hope that helps.
Adding to George Huber's answer. I solved this issue programmatically by adding a 20pt height UIView as a subview of the navigationController's view property -- in viewDidLoad method.
- (void)viewDidLoad
{
[super viewDidLoad];
UIView *statusBarBG = [[UIView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.view.bounds), 20)];
statusBarBG.backgroundColor = [UIColor navBar];
[self.navigationController.view addSubview:statusBarBG];
// REST OF CODE
}
Per skg's answer, I add a relative height for status bar according to iOS version.
self.navigationController.hidesBarsOnSwipe = true;
// add a UIView as subView to navigationController
CGFloat statusBarHeight;
if (#available(iOS 13, *)) {
NSArray *windows = UIApplication.sharedApplication.windows;
UIWindow *keyWindow = nil;
for (UIWindow *window in windows) {
if (window.isKeyWindow) {
keyWindow = window;
break;
}
}
statusBarHeight = keyWindow.windowScene.statusBarManager.statusBarFrame.size.height;
NSLog(#"statusBarHeight: %f", statusBarHeight);
} else {
statusBarHeight = UIApplication.sharedApplication.statusBarFrame.size.height;
}
UIView *statusBarBG = [[UIView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.view.bounds), statusBarHeight)];
statusBarBG.backgroundColor = [UIColor systemBackgroundColor];
[self.navigationController.view addSubview:statusBarBG];

Strange UISearchDisplayController view offset behavior in iOS 7 when embedded in navigation bar

I am building an iOS 7-only app. I am trying to set a UISearchDisplayController into the navigation bar.
I have it set up like this: In the storyboard, I added a "Search Bar and Search Display Controller" to my view controller's view, and set it at (0,0) relative to the top layout guide. I set constraints to pin to left, top and right. (I played with the constraints, i removed them completely, it doesn't matter) On top of that I have my Table view. When I added the search bar to the view in the storyboard, it automatically setup outlets for searchDisplayController and searchBar delegate. In code I have self.searchDisplayController.displaysSearchBarInNavigationBar = YES; I have two problems:
1) Without any buttons showing for the search bar (Interface builder -> select search bar -> Options: none selected) the search bar is in the middle of the screen:
If I click on the navigation bar, it starts editing the search bar:
notice also that the dark overlay appears to be offset from the navigation bar. It seems to me that the space is the same height as the navigation bar. Like it has been shifted down by that much. Also, when it displays the search results, the top of the content view is shifted down by the same amount (more pictures follow), which brings me to the second problem.
2) I messed around with it for a while and decided to check the option to have it show the cancel button. Now I have the search bar embedded in the nav bar correctly, but the overlay is still shifted down:
Again, when the search results table view appears, it is shifted down by the same amount (notice the scroll bar on the right side):
Even more bizarrely, I set a border on the search display controller's tableview layer, and it appears correct:
I have never used the UISearchDisplayController before and I unfamiliar with how to set it up, but functionally it works fine. I have read some other similar posts but the only advice is to hack it up by adjusting frames and setting manual offsets. I'd prefer to know what is causing this, is it a bug? Something I'm doing wrong? If it's a bug I can wait for a fix. It seems like such a basic thing that a thousand people must have done without any problem so I feel like I'm not setting it up correctly somehow. Thanks for you input.
I remember running into the same exact problem that you are observing.There could be a couple of solutions you can try.
If you are using storyboards
You should click on the view controller or TableView Controller which you have set up for your tableview and go to its attribute inspector and look under ViewController section and set the Extend Edges section to be under Top Bars.
If you are not using storyboards you can manually set the settings using the viewcontrollers edgesForExtendedLayout property and that should do the trick. I was using storyboards.
In my case, using storyboards, I had to check both Under Top Bars and Under Opaque Bars and leave Under Bottom Bars unchecked.
In my case, I actually had to uncheck all the Extended Edges boxes (essentially the same as programmatically setting Extended Edges to UIRectEdgeNone I believe) in my Storyboard in order to stop my search bar from offsetting itself. Thank you guys!
definesPresentationContext = true
override func viewDidLoad() {
super.viewDidLoad()
searchController = UISearchController(searchResultsController: nil)
searchController.searchResultsUpdater = self
searchController.hidesNavigationBarDuringPresentation = false
searchController.dimsBackgroundDuringPresentation = true
searchController.searchBar.searchBarStyle = UISearchBarStyle.Prominent
self.tableView.tableHeaderView = searchController.searchBar
definesPresentationContext = true
or see UISearchBar presented by UISearchController in table header view animates too far when active
My problem was just Adjust scroll view inserts. After change to false I didn't have problem
I had a same problem. And I solve this issue with adding view object under the tableview.
Add new ViewController on the Storyboard
Drag TableView to the new VC
Drag Table Cell to the TableView
Make a Connection for TableView DataSource, TableView Delegate to the new VC
I had very similar behavior happening. For me, the solution was to uncheck Extend Edges Under Top Bar in the storyboard settings for the parent view controller (I've turned off transparent navbars, not sure if that effects anything). If you're not using storyboard, you have to set [UIViewController edgesForExtendedLayout].
From the Apple docs:
This property is only applied to view controllers that are embedded in containers, such as UINavigationController or UITabBarController. View controllers set as the root view controller do not react to this property. Default value is UIRectEdgeAll.
Unfortunately none of the above solutions worked for me, I'm using a UITableViewController.
This link helped:
http://petersteinberger.com/blog/2013/fixing-uisearchdisplaycontroller-on-ios-7/
I put the code below for convenience:
static UIView *PSPDFViewWithSuffix(UIView *view, NSString *classNameSuffix) {
if (!view || classNameSuffix.length == 0) return nil;
UIView *theView = nil;
for (__unsafe_unretained UIView *subview in view.subviews) {
if ([NSStringFromClass(subview.class) hasSuffix:classNameSuffix]) {
return subview;
}else {
if ((theView = PSPDFViewWithSuffix(subview, classNameSuffix))) break;
}
}
return theView;
}
- (void)correctSearchDisplayFrames {
// Update search bar frame.
CGRect superviewFrame = self.searchDisplayController.searchBar.superview.frame;
superviewFrame.origin.y = 0.f;
self.searchDisplayController.searchBar.superview.frame = superviewFrame;
// Strech dimming view.
UIView *dimmingView = PSPDFViewWithSuffix(self.view, #"DimmingView");
if (dimmingView) {
CGRect dimmingFrame = dimmingView.superview.frame;
dimmingFrame.origin.y = self.searchDisplayController.searchBar.frame.size.height;
dimmingFrame.size.height = self.view.frame.size.height - dimmingFrame.origin.y;
dimmingView.superview.frame = dimmingFrame;
}
}
- (void)setAllViewsExceptSearchHidden:(BOOL)hidden animated:(BOOL)animated {
[UIView animateWithDuration:animated ? 0.25f : 0.f animations:^{
for (UIView *view in self.tableView.subviews) {
if (view != self.searchDisplayController.searchResultsTableView &&
view != self.searchDisplayController.searchBar) {
view.alpha = hidden ? 0.f : 1.f;
}
}
}];
}
// This fixes UISearchBarController on iOS 7. rdar://14800556
- (void)correctFramesForSearchDisplayControllerBeginSearch:(BOOL)beginSearch {
if (PSPDFIsUIKitFlatMode()) {
[self.navigationController setNavigationBarHidden:beginSearch animated:YES];
dispatch_async(dispatch_get_main_queue(), ^{
[self correctSearchDisplayFrames];
});
[self setAllViewsExceptSearchHidden:beginSearch animated:YES];
[UIView animateWithDuration:0.25f animations:^{
self.searchDisplayController.searchResultsTableView.alpha = beginSearch ? 1.f : 0.f;
}];
}
}
- (void)searchDisplayControllerWillBeginSearch:(UISearchDisplayController *)controller {
[self correctFramesForSearchDisplayControllerBeginSearch:YES];
}
- (void)searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller {
[self correctSearchDisplayFrames];
}
- (void)searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller {
[self correctFramesForSearchDisplayControllerBeginSearch:NO];
}
- (void)searchDisplayController:(UISearchDisplayController *)controller didShowSearchResultsTableView:(UITableView *)tableView {
// HACK: iOS 7 requires a cruel workaround to show the search table view.
if (PSPDFIsUIKitFlatMode()) {
controller.searchResultsTableView.contentInset = UIEdgeInsetsMake(self.searchDisplayController.searchBar.frame.size.height, 0.f, 0.f, 0.f);
}
}
Go to storyboard.
Click on the view controller.
Go to attribute inspector under the ViewController section.
Set the Extend Edges section to be Under Top Bars and Under Opaque Bars.
Make sure to un-check Under Bottom Bars.

Work with iOS 7 top layout guide

I'm designing a view controller in my storyboard. It contains a navigation bar at the top and a content view. I've set the constraints as
Navigation bar's top space to top layout guide = 0
Content view fills the remaining space (leading and trailing space to superview = 0 ; bottom space to superview = 0, top space to navigation bar =0)
Because I want to display the status bar, I'm using the top layout guide to make the navigation bar displayed just under it. It works with a standard UINavigationBar.
But I have custom navigation bar : I've created my own class inheriting UINavigationBar.
The problem is : when specifying this custom class for the navigation bar in the Identity Inspector of the interface builder, the bar is not at the right place when running the app.
Is there anything to add or modify in initWitFrame or initWithCoder, or something else to do, so my custom navigation bar will follow the constraints of my storyboard.
EDIT - source added
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// additional setup
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:(NSCoder *)aDecoder];
if (self) {
// same setup than initWithFrame:
}
return self;
}
- (void)drawRect:(CGRect)rect
{
//[self setFrame:rect]; navigation bar is not at the right place if uncommented
}
Why are you calling [self setFrame:rect];? frame is correctly set up at this point, you shouldn't change it. Also, rect is in an internal coordinate system of the navigation bar.

Resources