Hiding the tabbar and removing the space - ios

Is there a way to hide tabbar and remove that space left (around 50px) ?
I tried
self.tabBarController?.tabBar.hidden = true
self.extendedLayoutIncludesOpaqueBars = true
No luck. I see blank space.

If you're still seeing a black stripe under your hidden tab bar, have you tried to select Extend Edges Under Opaque Bars here?
Make also sure that Under Bottom Bars is still selected. Hope it helps!

Swift 3:
extension UITabBarController {
func setTabBarVisible(visible:Bool, duration: TimeInterval, animated:Bool) {
if (tabBarIsVisible() == visible) { return }
let frame = self.tabBar.frame
let height = frame.size.height
let offsetY = (visible ? -height : height)
// animation
UIViewPropertyAnimator(duration: duration, curve: .linear) {
self.tabBar.frame.offsetBy(dx:0, dy:offsetY)
self.view.frame = CGRect(x:0,y:0,width: self.view.frame.width, height: self.view.frame.height + offsetY)
self.view.setNeedsDisplay()
self.view.layoutIfNeeded()
}.startAnimation()
}
func tabBarIsVisible() ->Bool {
return self.tabBar.frame.origin.y < UIScreen.main.bounds.height
}
}
To use (if for example self is a UITabBarController):
self.setTabBarVisible(visible: false, duration: 0.3, animated: true)
Swift 2.x:
extension UITabBarController {
func setTabBarVisible(visible:Bool, duration: NSTimeInterval, animated:Bool) {
if (tabBarIsVisible() == visible) { return }
let frame = self.tabBar.frame
let height = frame.size.height
let offsetY = (visible ? -height : height)
// animation
UIView.animateWithDuration(animated ? duration : 0.0) {
self.tabBar.frame = CGRectOffset(frame, 0, offsetY)
self.view.frame = CGRectMake(0, 0, self.view.frame.width, self.view.frame.height + offsetY)
self.view.setNeedsDisplay()
self.view.layoutIfNeeded()
}
}
func tabBarIsVisible() ->Bool {
return self.tabBar.frame.origin.y < UIScreen.mainScreen().bounds.height
}
}
To use:
self.tabBarController?.setTabBarVisible(visible: false, duration: 0.3, animated: true)

After saw your screenshot in comment. I think you can try to set hidesBottomBarWhenPushed to true.
hidesBottomBarWhenPushed = true
Or storyboard.
It will hide bottom bar automatically when you pushed to another view controller, and appear it again when you go back.

Programmatically, add this to the next view controller for swift 4.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tabBarController?.tabBar.isHidden = true
edgesForExtendedLayout = UIRectEdge.bottom
extendedLayoutIncludesOpaqueBars = true
}
And add a background color

NOTE - This solution is to just to remove white space left after hiding tab bar.
For hiding tab bar best solution is - #Michael Campsall answer here
The simplest solution to this is to change your view's(in my case its tableView) bottom constraints, instead of giving bottom constraints with BottomLayoutGuide give it with superview. Screenshots attached for reference.
Constraints shown in below screenshots creates the problem, change it according to next screenshot.
Actual constraints to remove white space should be according to this(below) screenshot.

For those that like to do everything programmatically, add this line to the init method of a ViewController that shouldn't have the tabBar:
hidesBottomBarWhenPushed = true

I was facing the same issue and root cause was BOTTOM CONSTRAINT
Make sure you set the bottom constraint of your bottom most view in the main view hierarchy with SUPERVIEW, NOT "SAFE AREA"
Hope this helps someone..

The third answer on this question works for me in the following way:
The code on my view controller
#IBAction func buttonPressed(sender: AnyObject) {
setTabBarVisible(!tabBarIsVisible(), animated: true)
}
func setTabBarVisible(visible: Bool, animated: Bool) {
// hide tab bar
let frame = self.tabBarController?.tabBar.frame
let height = frame?.size.height
var offsetY = (visible ? -height! : height)
print ("offsetY = \(offsetY)")
// zero duration means no animation
let duration:NSTimeInterval = (animated ? 0.3 : 0.0)
// animate tabBar
if frame != nil {
UIView.animateWithDuration(duration) {
self.tabBarController?.tabBar.frame = CGRectOffset(frame!, 0, offsetY!)
self.view.frame = CGRectMake(0, 0, self.view.frame.width, self.view.frame.height + offsetY!)
self.view.setNeedsDisplay()
self.view.layoutIfNeeded()
return
}
}
}
func tabBarIsVisible() -> Bool {
return self.tabBarController?.tabBar.frame.origin.y < UIScreen.mainScreen().bounds.height
}
In storyboard:
The view controller main view background color is black color:
Then you could have another view inside (background color white), constrained trailing and leading space to superview and top and bottom space to the layout guide.
And the result is:

My preferred way to do that is using a wrapping controller. If I want to hide the tab bar, I just increase the height of the tab bar controller, thus effectively the tab bar is moved out of the screen.
With this solution you don't need to hack tab bar frame and you don't depend on navigation controller push animation:
import UIKit
class ViewController: UIViewController {
let tabController: UITabBarController = {
let tabController = UITabBarController()
// setup your tabbar controller here
return tabController;
}()
var tabbarHidden = false {
didSet {
var frame = self.view.bounds;
if (tabbarHidden) {
frame.size.height += self.tabController.tabBar.bounds.size.height;
}
self.tabController.view.frame = frame;
}
}
override func viewDidLoad() {
super.viewDidLoad()
// add the tab controller as child controller
addChildViewController(self.tabController)
self.tabController.view.frame = self.view.bounds
self.tabController.view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
self.view.addSubview(self.tabController.view)
self.tabController.didMoveToParentViewController(self)
// for debugging
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(switchTabbar))
self.tabController.view.addGestureRecognizer(tapRecognizer)
}
override func childViewControllerForStatusBarStyle() -> UIViewController? {
return self.tabController
}
override func childViewControllerForStatusBarHidden() -> UIViewController? {
return self.tabController
}
func switchTabbar() {
UIView.animateWithDuration(0.3) {
self.tabbarHidden = !self.tabbarHidden
}
}
}

try to set the tab bar translucent to before you hide the tab bar set to false again when you want to show again.
it works for me.
tabBarController?.tabBar.isTranslucent = true

Yes. You can hide your tab bar when you push to view controller. You can show tab bar in your home. You can hide your tab bar when you push to next View controller.
See the Hide Botton Bar on Push following image and set in all viewcontrollers where you dont want tab bar.
Hope it helps..

Sometimes that easiest way is just to add a view that uses the UIScreen bounds.
let whiteView = UIView()
whiteView.backgroundColor = .white
view.addSubview(whiteView)
whiteView.translatesAutoresizingMaskIntoConstraints = false
whiteView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
whiteView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
whiteView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
whiteView.heightAnchor.constraint(equalToConstant: UIScreen.main.bounds.height).isActive = true
Cause sometimes the view edges extends beyond the nav bar giving you new problems if you extend the view layout.

Tested in Swift 5.4.
If you're adding any ViewController's view as subview programmatically and not using pushViewController, then you can simply try as follows:
// When you wanna hide TabBar
tabBarController?.tabBar.isHidden = true
tabBarController?.tabBar.isTranslucent = true // This is the key point!
// When you wanna show TabBar
tabBarController?.tabBar.isHidden = false
tabBarController?.tabBar.isTranslucent = false // This is the key point!

This code works on iOS 10, 11, and iPhone X (including simulators) to show/hide the tabBar. I created it several years (iOS 7 time frame?) and it has worked reliably since that time.
It works great on iPhone X as long as content content in your childViewControllers (in tabs) is pinned to topLayoutGuide, bottomLayoutGuide or SafeArea and not the main views walls. Then it all just works. Enjoy!
#interface UITabBarController (HideTabBar)
#property (nonatomic, getter=isTabBarHidden) BOOL tabBarHidden;
-(void)setTabBarHidden:(BOOL)hidden animated:(BOOL)animated;
#end
#implementation UITabBarController (HideTabBar)
-(BOOL)isTabBarHidden
{
CGRect viewFrame = self.view.frame;
CGRect tabBarFrame = self.tabBar.frame;
return tabBarFrame.origin.y >= viewFrame.size.height;
}
-(void)setTabBarHidden:(BOOL)hidden
{
[self setTabBarHidden:hidden animated:NO];
}
-(void)setTabBarHidden:(BOOL)hidden animated:(BOOL)animated
{
BOOL isHidden = self.tabBarHidden;
if(hidden == isHidden)return;
UIView *transitionView = [[[self.view.subviews reverseObjectEnumerator] allObjects] lastObject];
if(transitionView == nil) {
NSLog(#"UITabBarCategory can't get the container view");
return;
}
CGRect viewFrame = self.view.bounds;
CGRect tabBarFrame = self.tabBar.frame;
CGRect containerFrame = transitionView.frame;
CGRect selectedVCFrame = containerFrame;
tabBarFrame.origin.y = viewFrame.size.height - (hidden ? 0 : tabBarFrame.size.height);
containerFrame.size.height = viewFrame.size.height - (hidden ? 0 : tabBarFrame.size.height);
if([self.moreNavigationController.viewControllers containsObject:self.selectedViewController]) {
selectedVCFrame = self.selectedViewController.view.frame;
selectedVCFrame.size.height += hidden ? tabBarFrame.size.height : -tabBarFrame.size.height;
}
self.selectedViewController.view.frame = selectedVCFrame;
[UIView animateWithDuration:.5 animations:^{
self.tabBar.frame = tabBarFrame;
transitionView.frame = containerFrame;
[self.selectedViewController.view setNeedsLayout];
}];
}
#end
Usage - I call it in the viewController on rotation events like so:
-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
[super didRotateFromInterfaceOrientation:fromInterfaceOrientation];
// Hide TabBar on iPhone, iPod Touch
if([UIDevice currentDevice].userInterfaceIdiom != UIUserInterfaceIdiomPad) {
if(_startDateEditor.editing) return;
if(fromInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown || fromInterfaceOrientation == UIInterfaceOrientationPortrait)
[self.tabBarController setTabBarHidden:YES animated:YES];
else
[self.tabBarController setTabBarHidden:NO animated:YES];
}
}

For me in iOS 13 I had to display image in cell with full screen, I had collection view with trailing, leading, top, bottom constraint. I removed all constraint. set collection view frame to UIScreen.main.bounds. then return sizeForItemAt as collection frame size.

Related

iOS 13 remove UIView for simulate status bar background

I'm working with a UITableViewController which when scrolling makes the navigationBar disappear. Now when the navigation bar is hidden when the user swipes the table view the contents of the cells are seen below the status bar ...
To solve this problem I tried to insert a UIView to simulate a background of the status bar and everything works but the problem is that when I close the UITableViewController the background view of the status bar is not removed from the superview
For now my code is this, can you help me understand where I am wrong? why can't I remove the UIView from the superview?
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
setupStatusBarView()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.navigationBar.isHidden = true
UIApplication.shared.windows.first?.viewWithTag(1)?.removeFromSuperview()
}
//MARK: - Setup Status Bar View
func setupStatusBarView() {
let height = view.window?.windowScene?.statusBarManager?.statusBarFrame.height ?? 0
let statusBarView = UIView()
statusBarView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height:height+5)
statusBarView.backgroundColor = .systemBackground
statusBarView.tag = 1
UIApplication.shared.windows.first?.addSubview(statusBarView)
}
viewDidLayoutSubviews get calls multiple times and you have put setupStatusBarView() in viewDidLayoutSubviews that means your background view has been added multiple times and this is totally wrong flow!
You are removing topmost view only not previous ones!
You should set frame in viewDidLayoutSubviews and should add the view from viewDidLoad!
try this one
let subviewArray = UIApplication.shared.windows.first?.subviews
for view in subviewArray!{
if view.tag == 1{
view.removeFromSuperview()
}
}

Managing view frame when hide and unhide a tabbar

I have a UIViewController that has tab bar controller at bottom. When user click on a button I m hiding the tab bar. Tab bar is getting hidden but there is a white space at bottom. ViewController frame is not changing. How to manage this ? If tabor controller gets hidden, viewController height should get increase.
func apply(_ effect: ActivityFeedEffect) {
switch effect {
case .feedTypeChange(mode: let mode):
self.parent?.tabBarController?.tabBar.isHidden = mode == .hidden
}
}
This is an extension on UITabBarController, which you can use.
This basically, updates the frames of the view.
You can add animation and other frame handling if needed, based on your use case. But this is something that can lead you in that direction.
extension UITabBarController {
func hideTabBar(isHidden:Bool) {
if (isTabBarAlreadyHidden() == isHidden) { return }
let frame = self.tabBar.frame
let height = frame.size.height
let offsetY = (isHidden ? -height : height)
self.tabBar.frame.offsetBy(dx:0, dy:offsetY)
self.view.frame = CGRect(x:0,y:0,width: self.view.frame.width, height: self.view.frame.height + offsetY)
self.view.setNeedsDisplay()
self.view.layoutIfNeeded()
}
func isTabBarAlreadyHidden() ->Bool {
return self.tabBar.frame.origin.y < UIScreen.main.bounds.height
}
}
In my case, I have configured on the storyboard the extended Edges to go under bottom bars and under opaque bars (see image). So My view always takes the hole screen, and I don't need to adjust the frame. Maybe this helps.
My structure is Tab bar -> Navigation Controller -> TableView (here I hide/show the tab bar)

TapGesture doesn't work under tabBar

I have a "classic app" with 3 ViewController and a tabBar that I use to change ViewController.
On my first ViewController, I have a button that display a UIView on all the screen, so I hide tabBar with this setTabBarVisible func :
extension UIViewController
{
func setTabBarVisible(visible: Bool, animated: Bool)
{
//* This cannot be called before viewDidLayoutSubviews(), because the frame is not set before this time
// bail if the current state matches the desired state
if (isTabBarVisible == visible) { return }
// get a frame calculation ready
let frame = self.tabBarController?.tabBar.frame
let height = frame?.size.height
let offsetY = (visible ? -height! : height)
// zero duration means no animation
let duration: TimeInterval = (animated ? 0.3 : 0.0)
// animate the tabBar
if frame != nil
{
UIView.animate(withDuration: duration)
{
self.tabBarController?.tabBar.frame = frame!.offsetBy(dx: 0, dy: offsetY!)
return
}
}
}
var isTabBarVisible: Bool
{
return (self.tabBarController?.tabBar.frame.origin.y ?? 0) < self.view.frame.maxY
}
}
That's working, the tabBar is hidden and I see all my UIVIew.
The problem is, I have a UILabel at bottom of the UIView (at the place I usually display the tabBar), and I can't use my TapGesture on my UILabel, nothing is happening.
(if I display the label somewhere else the UITapGesture works good.)
I tried to set zPosition of my tabBar to 0 and zPosition of my UIView to 1 but that's doesn't work either.
How can I get my label clickable at bottom of my view?
Check UILabel.isUserInterration = enable
Make sure that when you hide tabbar, perticular view controller Under bottom bar property is unselect . See atteh imnage.
You can try with programatically also like
ViewController.edgesForExtendedLayout = UIRectEdge.top
make sure is true
label.isUserInteractionEnabled = true
Please refer this link may it help you.

UISearchBar increases navigation bar height in iOS 11

I have my UISearchBar being part of the navigation bar like:
let searchBar = UISearchBar()
//some more configuration to the search bar
.....
navigationItem.titleView = searchBar
After updating to iOS 11 something weird happened to the search bar in my app. On iOS 10 and prior I had my navigation bar looking like:
Now with iOS 11 I have:
As you can see there is difference in the rounding of the two search bars which does not bothers me. The problem is that the search bar increases the height of the navigation bar. So when I go to another controller it looks weird too:
In fact that weird black line's height plus the current navigation bar's height is equal to the height of navigation bar shown in the second picture ...
Any ideas how to get rid of the black line and having consistent navigation bar height across all view controllers ?
I got black line under NavigationBar with SearchBar in iOS 11 in two cases:
when i pushed another ViewControllers from ViewController with UISearchBar
when i dismissed ViewController with UISearchBar with "drag right to dismiss"
My solution was: adding this code to my ViewController with UISearchBar:
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
[self.navigationController.view setNeedsLayout]; // force update layout
[self.navigationController.view layoutIfNeeded]; // to fix height of the navigation bar
}
Swift 4 Update
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.view.setNeedsLayout() // force update layout
navigationController?.view.layoutIfNeeded() // to fix height of the navigation bar
}
You can add a constraint of height 44 to the search bar for iOS 11.
// Swift
if #available(iOS 11.0, *) {
searchBar.heightAnchor.constraint(equalToConstant: 44).isActive = true
}
// Objective-C
if (#available(iOS 11.0, *)) {
[searchBar.heightAnchor constraintEqualToConstant:44].active = YES;
}
I believe in iOS 11 UISearchBar now has the height equals to 56, and UINavigationBar uses autolayout to fit its subviews hence it increases the height. If you still want to have UISearchBar as titleView as in pre-iOS 11, I found out the best way to do it is to embed UISearchBar in a custom view, and set this view's height to 44, and assign it to navigationItem.titleView
class SearchBarContainerView: UIView {
let searchBar: UISearchBar
init(customSearchBar: UISearchBar) {
searchBar = customSearchBar
super.init(frame: CGRect.zero)
addSubview(searchBar)
}
override convenience init(frame: CGRect) {
self.init(customSearchBar: UISearchBar())
self.frame = frame
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
searchBar.frame = bounds
}
}
class MyViewController: UIViewController {
func setupNavigationBar() {
let searchBar = UISearchBar()
let searchBarContainer = SearchBarContainerView(customSearchBar: searchBar)
searchBarContainer.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: 44)
navigationItem.titleView = searchBarContainer
}
}
try this code on "ACKNOWLEDGEMENTS" view controller
in viewDidLoad
self.extendedLayoutIncludesOpaqueBars = true
Thank you all! I finally found a solution.
Adding the following code to ViewController with UISearchBar.
First step: viewDidLoad
-(void)viewDidLoad
{
[super viewDidLoad];
self.extendedLayoutIncludesOpaqueBars = YES;
...
}
override func viewDidLoad() {
super.viewDidLoad()
self.extendedLayoutIncludesOpaqueBars = true
}
Second step:viewWillDisappear
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
// force update layout
[self.navigationController.view setNeedsLayout];
// to fix height of the navigation bar
[self.navigationController.view layoutIfNeeded];
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.view.setNeedsLayout() // force update layout
navigationController?.view.layoutIfNeeded() // to fix height of the navigation bar
}
In Objective-C
if (#available(iOS 11.0, *)) {
[self.searchBar.heightAnchor constraintLessThanOrEqualToConstant: 44].active = YES;
}
This happen to me too, all running well in iOS 12.4 and getting weird in 13 above.
The problem is in iOS 13 navigation bar height increase from 88 to 100 after jump from UIViewController that implement searchBar.
Try this in your UIViewController that implement searchBar.
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.view.setNeedsLayout()
navigationController?.view.layoutIfNeeded()
}
Preview after fixing:
Preview before fixing:
EDIT: The #zgjie answer is a better solution for this problem: https://stackoverflow.com/a/46356265/1713123
It seems this happens because in iOS 11 the default height value of SearchBar was changed to 56, instead 44 on previous iOS versions.
For now, I've applied this workaround, setting searchBar height back to 44:
let barFrame = searchController.searchBar.frame
searchController.searchBar.frame = CGRect(x: 0, y: 0, width: barFrame.width, height: 44)
Another solution could be use the new searchController property on navigationItem in iOS 11:
navigationItem.searchController = searchController
But this way da searchBar appears below navigation title.
All solution didn't work for me so before I pushed view controller I did:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.navigationItem.titleView = UIView()
}
And to make search bar present when going back:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.navigationItem.titleView = UISearchBar()
}
I couldn't use the solution of keeping the navBar at 44.
So it took me a day but finally, I found a solution that doesn't change the bar height and position the button in the middle of the bar. The issue is that the buttons are placed in a stack view which is configured as Horizontal stack view and therefore doesn't adjust to the height change.
This is done on init:
UIBarButtonItem *cancelButton;
if (#available(iOS 11.0, *)) {
// For iOS11 creating custom button to accomadate the change of navbar + search bar being 56 points
self.navBarCustomButton = [UIButton buttonWithType:UIButtonTypeCustom];
[self.navBarCustomButton setTitle:#"Cancel"];
[self.navBarCustomButton addTarget:self action:#selector(cancelButtonTapped) forControlEvents:UIControlEventTouchUpInside];
cancelButton = [[UIBarButtonItem alloc] initWithCustomView:self.navBarCustomButton];
} else {
cancelButton = [[UIBarButtonItem alloc] initWithTitle:MagicLocalizedString(#"button.cancel", #"Cancel")
style:UIBarButtonItemStylePlain
target:self
action:#selector(cancelButtonTapped)];
}
on viewWillApear (or anytime after the view was added to the navigation stack)
if (#available(iOS 11.0, *)) {
UIView *buttonsStackView = [navigationController.navigationBar subviewOfClass:[UIStackView class]];
if (buttonsStackView ) {
[buttonsStackView.centerYAnchor constraintEqualToAnchor:navigationController.navigationBar.centerYAnchor].active = YES;
[self.navBarCustomButton.heightAnchor constraintEqualToAnchor:buttonsStackView.heightAnchor];
}
}
And subviewOfClass is a category on UIView:
- (__kindof UIView *)subviewOfClass:(Class)targetClass {
// base case
if ([self isKindOfClass:targetClass]) {
return self;
}
// recursive
for (UIView *subview in self.subviews) {
UIView *dfsResult = [subview subviewOfClass:targetClass];
if (dfsResult) {
return dfsResult;
}
}
return nil;
}
All you have to do is to subclass UISearchBar and override "intrinsicContentSize":
#implementation CJSearchBar
-(CGSize)intrinsicContentSize{
CGSize s = [super intrinsicContentSize];
s.height = 44;
return s;
}
#end
Unable to comment, but wanted to share some additional issues I ran into while spending many hours trying to get to the bottom of this issue even after using one of the other solutions.
It appears the best fix for me was Andrew's answer:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.view.setNeedsLayout() // force update layout
navigationController?.view.layoutIfNeeded() // to fix height of the navigation bar
}
However, at the very least in iOS 12.1, if your UINavigationBar:
has isTranslucent set to false, the View Controller with the search bar appears to not get it's view's layout adjusted back when interactively dismissing (normal dismissing via back button appears to work).
has it's background image set using setBackgroundImage(UIImage(), for: .default), the transition animation doesn't work properly and will jump back to its position after finishing.
These particular properties were set to get the Navigation Bar to appear in a certain way however, so I need to do some adjusting to get it back, or put up with the weird behaviour. Will try to remember to update the above if I run into anything else or find other solutions or differences in other OS versions.
In my case, bigger UINavigationBar's height wasn't a problem for me. I just needed to realign left and right bar button items. That's the solution i've come up with:
- (void)iOS11FixNavigationItemsVerticalAlignment
{
[self.navigationController.navigationBar layoutIfNeeded];
NSString * currSysVer = [[UIDevice currentDevice] systemVersion];
if ([currSysVer compare:#"11" options:NSNumericSearch] != NSOrderedAscending)
{
UIView * navigationBarContentView;
for (UIView * subview in [self.navigationController.navigationBar subviews])
{
if ([subview isKindOfClass:NSClassFromString(#"_UINavigationBarContentView")])
{
navigationBarContentView = subview;
break;
}
}
if (navigationBarContentView)
{
for (UIView * subview in [navigationBarContentView subviews])
{
if (![subview isKindOfClass:NSClassFromString(#"_UIButtonBarStackView")]) continue;
NSLayoutConstraint * topSpaceConstraint;
NSLayoutConstraint * bottomSpaceConstraint;
CGFloat topConstraintMultiplier = 1.0f;
CGFloat bottomConstraintMultiplier = 1.0f;
for (NSLayoutConstraint * constraint in navigationBarContentView.constraints)
{
if (constraint.firstItem == subview && constraint.firstAttribute == NSLayoutAttributeTop)
{
topSpaceConstraint = constraint;
break;
}
if (constraint.secondItem == subview && constraint.secondAttribute == NSLayoutAttributeTop)
{
topConstraintMultiplier = -1.0f;
topSpaceConstraint = constraint;
break;
}
}
for (NSLayoutConstraint * constraint in navigationBarContentView.constraints)
{
if (constraint.firstItem == subview && constraint.firstAttribute == NSLayoutAttributeBottom)
{
bottomSpaceConstraint = constraint;
break;
}
if (constraint.secondItem == subview && constraint.secondAttribute == NSLayoutAttributeBottom)
{
bottomConstraintMultiplier = -1.0f;
bottomSpaceConstraint = constraint;
break;
}
}
CGFloat contentViewHeight = navigationBarContentView.frame.size.height;
CGFloat subviewHeight = subview.frame.size.height;
topSpaceConstraint.constant = topConstraintMultiplier * (contentViewHeight - subviewHeight) / 2.0f;
bottomSpaceConstraint.constant = bottomConstraintMultiplier * (contentViewHeight - subviewHeight) / 2.0f;
}
}
}
}
Basically, we search for stack views that contain bar button items and then changing their's top and bottom constraints values. Yeah, it's a dirt hack, and won't recommend to use it if you can fix your issue in any other way.
//
// Created by Sang Nguyen on 10/23/17.
// Copyright © 2017 Sang. All rights reserved.
//
import Foundation
import UIKit
class CustomSearchBarView: UISearchBar {
final let SearchBarHeight: CGFloat = 44
final let SearchBarPaddingTop: CGFloat = 8
override open func awakeFromNib() {
super.awakeFromNib()
self.setupUI()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.setupUI()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
// fatalError("init(coder:) has not been implemented")
}
func findTextfield()-> UITextField?{
for view in self.subviews {
if view is UITextField {
return view as? UITextField
} else {
for textfield in view.subviews {
if textfield is UITextField {
return textfield as? UITextField
}
}
}
}
return nil;
}
func setupUI(){
if #available(iOS 11.0, *) {
self.translatesAutoresizingMaskIntoConstraints = false
self.heightAnchor.constraint(equalToConstant: SearchBarHeight).isActive = true
}
}
override func layoutSubviews() {
super.layoutSubviews()
if #available(iOS 11.0, *) {
if let textfield = self.findTextfield() {
textfield.frame = CGRect(x: textfield.frame.origin.x, y: SearchBarPaddingTop, width: textfield.frame.width, height: SearchBarHeight - SearchBarPaddingTop * 2)`enter code here`
return
}
}
}
}
I found Mai Mai's solution to be the only one that's really usable.
However it's still not perfect:
When rotating the device, the search bar is not properly resized and remains in the smaller dimension.
I have found a fix for that. Here is my code in Objective C with the relevant parts annotated:
// improvements in the search bar wrapper
#interface SearchBarWrapper : UIView
#property (nonatomic, strong) UISearchBar *searchBar;
- (instancetype)initWithSearchBar:(UISearchBar *)searchBar;
#end
#implementation SearchBarWrapper
- (instancetype)initWithSearchBar:(UISearchBar *)searchBar {
// setting width to a large value fixes stretch-on-rotation
self = [super initWithFrame:CGRectMake(0, 0, 4000, 44)];
if (self) {
self.searchBar = searchBar;
[self addSubview:searchBar];
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
self.searchBar.frame = self.bounds;
}
// fixes width some cases of resizing while search is active
- (CGSize)sizeThatFits:(CGSize)size {
return size;
}
#end
// then use it in your VC
#implementation MyViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationItem.titleView = [[SearchBarWrapper alloc] initWithSearchBar:self.searchController.searchBar];
}
#end
Now there is still one case left that I haven't figured out yet. To reproduce do the following:
- start in portrait
- activate search field
- rotate to landscape
- error: the bar doesn't resize
I fixed this by added the constraint to viewDidAppear on the map view controller where the search bar is embedded
public override func viewDidAppear(_ animated: Bool) {
if #available(iOS 11.0, *) {
resultSearchController?.searchBar.heightAnchor.constraint(equalToConstant: 44).isActive = true
// searchBar.heightAnchor.constraint(equalToConstant: 44).isActive = true
}
}
Hi to the people who uses UISearchController and then attaching its UISearchBar to the navigationItem.titleView. I've spend a crazy 4-5 hours of my day to solve this. Following the iOS 11+ recommended approach, which is putting the searchController to the navigation.searchController is not just right for my case. The screen that has this searchController/searchBar has a backButton, a custom one.
I have tested this in iOS 10, iOS 11, and 12. In different devices. I just had to. I can't go home without solving this demon. This is the most perfect I could do for today, given my tight deadline.
So I just wanna share this hard work that I did, it's up to you to put everything into where ever you want (ex. variables in your viewModel). Here it goes:
In my first screen (say home screen, that does not have this search controller), I have this in my viewDidLoad().
self.extendedLayoutIncludesOpaqueBars = true
In my second screen, the one that has the searchController, I have this in my viewDidAppear.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let systemMajorVersion = ProcessInfo.processInfo.operatingSystemVersion.majorVersion
if systemMajorVersion < 12 {
// Place the search bar in the navigation item's title view.
self.navigationItem.titleView = self.searchController.searchBar
}
if systemMajorVersion >= 11 {
self.extendedLayoutIncludesOpaqueBars = true
UIView.animate(withDuration: 0.3) {
self.navigationController?.navigationBar.setNeedsLayout()
self.navigationController?.navigationBar.layoutIfNeeded()
}
self.tableView.contentInset = UIEdgeInsets(top: -40, left: 0, bottom: 0, right: 0)
if self.viewHadAppeared {
self.tableView.contentInset = .zero
}
}
self.viewHadAppeared = true // this is set to false by default.
}
and here's my searchController's declaration:
lazy var searchController: UISearchController = {
let searchController = UISearchController(searchResultsController: nil)
searchController.hidesNavigationBarDuringPresentation = false
searchController.dimsBackgroundDuringPresentation = false
searchController.searchBar.textField?.backgroundColor = .lalaDarkWhiteColor
searchController.searchBar.textField?.tintColor = .lalaDarkGray
searchController.searchBar.backgroundColor = .white
return searchController
}()
So I hope this helps someone someday.
I tried various things to get the size back to the original 44, but then the search bar always looks and behaves weird - like being to far stretched, y-offset and alike.
I found a nice solution here (via some other stackoverflow post):
https://github.com/DreamTravelingLight/searchBarDemo
Just derive your viewcontroller from the SearchViewController and include in your project the SearchViewController and WMSearchbar classes. Worked out of the box for me without any ugly if (iOS11) else... uglyness.
In my case, I have to decrease the textField's height 36pt -> 28pt.
So I tried to change the frame's height, layer's height. But the ways didn't work.
Finally, I found a solution that's the mask.
I think, It's not a good way but it works.
let textField = searchBar.value(forKey: "searchField") as? UITextField
textField?.font = UIFont.systemFont(ofSize: 14.0, weight: .regular)
textField?.textColor = #colorLiteral(red: 0.1960784314, green: 0.1960784314, blue: 0.1960784314, alpha: 1)
textField?.textAlignment = .left
if #available(iOS 11, *) {
let radius: CGFloat = 5.0
let magnifyIconWidth: CGFloat = 16.0
let inset = UIEdgeInsets(top: 4.0, left: 0, bottom: 4.0, right: 0)
let path = CGMutablePath()
path.addArc(center: CGPoint(x: searchBar.bounds.size.width - radius - inset.right - magnifyIconWidth, y: inset.top + radius), radius: radius, startAngle: .pi * 3.0/2.0, endAngle: .pi*2.0, clockwise: false) // Right top
path.addArc(center: CGPoint(x: searchBar.bounds.size.width - radius - inset.right - magnifyIconWidth, y: searchBar.bounds.size.height - radius - inset.bottom), radius: radius, startAngle: 0, endAngle: .pi/2.0, clockwise: false) // Right Bottom
path.addArc(center: CGPoint(x: inset.left + radius, y: searchBar.bounds.size.height - radius - inset.bottom), radius: radius, startAngle: .pi/2.0, endAngle: .pi, clockwise: false) // Left Bottom
path.addArc(center: CGPoint(x: inset.left + radius, y: inset.top + radius), radius: radius, startAngle: .pi, endAngle: .pi * 3.0/2.0, clockwise: false) // Left top
let maskLayer = CAShapeLayer()
maskLayer.path = path
maskLayer.fillRule = kCAFillRuleEvenOdd
textField?.layer.mask = maskLayer
}
You can change the insets, if you want to change the textField's frame.

TableView Showing Behind Tab Bar

I am updating my app to use iOS 7 and I'm having a problem with a table view. My tab bar is translucent. The problem is when I scroll to the bottom of my table view, part of the last cell is still behind the tab bar. I'd like to have a bit of space between the last cell and the tab bar. I could fix this by using an opaque tab bar instead, but I want to keep it translucent.
Try setting
self.edgesForExtendedLayout = UIRectEdgeNone;
self.extendedLayoutIncludesOpaqueBars = NO;
self.automaticallyAdjustsScrollViewInsets = NO;
Inside the tableview controller
Swift 4.x
let adjustForTabbarInsets: UIEdgeInsets = UIEdgeInsetsMake(0, 0, self.tabBarController!.tabBar.frame.height, 0)
self.yourTableView.contentInset = adjustForTabbarInsets
self.yourTableView.scrollIndicatorInsets = adjustForTabbarInsets
Check the screen shot
Check the under top Bar and Un-checke under Bottom Bar
SWIFT 3
put this inside viewDidLoad of your tableViewController:
self.edgesForExtendedLayout = UIRectEdge()
self.extendedLayoutIncludesOpaqueBars = false
self.automaticallyAdjustsScrollViewInsets = false
Swift 3.0
This is what worked for me. In your Custom ViewController:
override func viewDidLoad() {
super.viewDidLoad()
let adjustForTabbarInsets: UIEdgeInsets = UIEdgeInsetsMake(self.tabBarController!.tabBar.frame.height, 0, 0, 0);
//Where tableview is the IBOutlet for your storyboard tableview.
self.tableView.contentInset = adjustForTabbarInsets;
self.tableView.scrollIndicatorInsets = adjustForTabbarInsets;
}
Not to sure I like the solution but it works for me.
With iOS 11 I have no issue, I simply use the following in viewDidLoad():
self.collectionView.bottomAnchor.constraint(self.view.safeAreaLayoutGuide.bottomAnchor).isActive = true
However on iOS 10 I need to hack my way like this:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let tabBarHeight: CGFloat = (self.parent?.tabBarController?.tabBar.frame.size.height)!
if #available(iOS 11.0, *) {
} else {
self.collectionView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -tabBarHeight).isActive = true
}
}
This is working for me
override func viewDidLoad() {
self.edgesForExtendedLayout = UIRectEdge()
self.extendedLayoutIncludesOpaqueBars = false
}
If any view shows behind a UITabBar you can grab the bottomLayoutGuide and make adjustments at runtime. What I do is have a BaseViewController that all my view controllers inherit from. Then if the tab bar is visible we adjust the view like so:
import UIKit
class BaseVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidLayoutSubviews() {
//Ensures that views are not underneath the tab bar
if tabBarController?.tabBar.hidden == false {
var viewBounds = self.view.bounds;
var bottomBarOffset = self.bottomLayoutGuide.length;
self.view.frame = CGRectMake(0, 0, viewBounds.width, viewBounds.height - bottomBarOffset)
}
}
}
Since I don't use storyboards (where you can click a checkbox in IB to fix this problem), this has been the best solution I have found.
It is really hard to resolve the issue without detail information or actual codes. I have similar issue of tabview behind UItabBar in my project. The solutions offered here do not work in my case. After exploring my codes, I found a solution for my case.
Here is brief explanation of my case. I have a UItabBar in main view with two tab buttons. In one tab view, there is table view. If user taps on a row, a detail view is presented by using navigation controller. In the detail view, the tab bar is hidden, and a toolbar is showing at the bottom.
In order to bring tab bar back and hide the toolbar when the main view is brought back, I have to explicitly show tab bar and hide toolbar in the event of viewWillAppear:
class myMainViewController: UITableViewController {
private var tabBarHidden: Bool? = {
didSet {
self.tabBarController?.tabBar.isHidden = tabBarIsHidden ?? true
}
}
private var toolBarIsHidden: Bool? {
didSet {
let hidden = toolBarIsHidden ?? true
self.navigationController?.toolbar.isHidden = hidden
self.navigationController?.setToolbarHidden(hidden, animated: true)
}
}
...
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.tabBarIsHidden = false
self.toolBarIsHidden = true
}
...
}
I finally realize that the visibility of bar at the bottom is set in the event of viewWillAppear. At that time, the tableView or scroll view's content insets are set already based on no bar at the bottom. That's why my tableView is behind the bottom bar.
The solution I found is to reset content insets in the event of viewDidAppear:
override func viewDidAppear(_ animated: Bool) {
// In the event of viewWillAppear, visibilities of tool bar and tab bar are set or changed,
// The following codes resets scroll view's content insets for tableview
let topInset = self.navigationController!.navigationBar.frame.origin.y +
self.navigationController!.navigationBar.frame.height
let adjustForTabbarInsets: UIEdgeInsets = UIEdgeInsetsMake(
topInset, 0,
self.tabBarController!.tabBar.frame.height, 0)
self.tableView.contentInset = adjustForTabbarInsets
self.tableView.scrollIndicatorInsets = adjustForTabbarInsets
}
The best approch would be to Embed TabBarController to your ViewController (Editor -> Embed In -> TabBar Controller)and set the bottom of the tableview to be bottom of safe area of viewcontroller. The other ways wont be as perfect as this one.
You need to adjust the height of the table view. Just leave 49px at the bottom, as the tabbar height is 49 px. Adjust the height of table view so that it leaves 49px space below it.

Resources