Negative spacer for UIBarButtonItem in navigation bar on iOS 11
EDIT:
This may no longer work as of iOS 13. You may get the error:
Client error attempting to change layout margins of a private view
OLD ANSWER:
I found a somewhat hacky solution on the Apple developer forums:
https://forums.developer.apple.com/thread/80075
It looks like the problem comes from how iOS 11 handles the UIBarButtonItem
.fixedSpace
buttons and how a UINavigationBar
is laid out in iOS 11. The navigation bars now use autolayout and the layout margins to layout the buttons. The solution presented in that post (at the bottom) was to set all the layout margins to some value you want.
class InsetButtonsNavigationBar: UINavigationBar {
override func layoutSubviews() {
super.layoutSubviews()
for view in subviews {
// Setting the layout margins to 0 lines the bar buttons items up at
// the edges of the screen. You can set this to any number to change
// the spacing.
view.layoutMargins = .zero
}
}
}
To use this new nav bar with custom button spacing, you will need to update where you create any navigation controllers with the following code:
let navController = UINavigationController(navigationBarClass: InsetButtonsNavigationBar.self,
toolbarClass: UIToolbar.self)
navController.viewControllers = [yourRootViewController]
iOS 11 - how to change the position of rightBarButtonItems?
We have acomplished this by subclassing UINavigationBar:
#import <UIKit/UIKit.h>
@interface InsetButtonsNavigationBar : UINavigationBar
@end
and setting layoutMargins
#import "InsetButtonsNavigationBar.h"
@implementation InsetButtonsNavigationBar
- (void)layoutSubviews {
[super layoutSubviews];
for (UIView * view in self.subviews) {
view.layoutMargins = UIEdgeInsetsMake(0, 8, 0, 8);
}
}
@end
How to Edit Empty Spaces of Left, Right UIBarButtonItem in UINavigationBar [iOS 7]
I was also facing this problem. I also have feelings that in iOS 7 there is more space. And I figured out that this is about 10 points more. I usually use negative spaces when I want for LeftBarItemButton
to start from the edge. This can be useful for you as well.
UIBarButtonItem *negativeSpacer = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
negativeSpacer.width = -16; // it was -6 in iOS 6
[self.navigationItem setLeftBarButtonItems:@[negativeSpacer, requiredButton]; /* this will be the button which you actually need */] animated:NO];
How to adjust space between two UIBarButtonItem in rightBarButtonItems
Updated at Jul 2015
A better way to do this is to use storyboard (tested in Xcode 6.4). First, add a UINavigationItem; secondly, add a Bar Button Item; thirdly, add a view to the Bar Button Item you just created in step 2; fourthly, add as many buttons as you wish into that view you just dragged in; lastly, adjust the space with your mouse and constraints.
Related Questions
Can't assign multiple Buttons to UINavigationItem when using Storyboard with iOS 5
How to add buttons to navigation controller visible after segueing?
Old Answer (Only acceptable for small insets)
Use imageInsets property:
leftButton.imageInsets = UIEdgeInsetsMake(0.0, 0.0, 0, -15);
rightButton.imageInsets = UIEdgeInsetsMake(0.0, -15, 0, 0);
for three or more buttons, the middle one(s) get both insets:
leftButton.imageInsets = UIEdgeInsetsMake(0.0, 0.0, 0, -15);
middleButton.imageInsets = UIEdgeInsetsMake(0.0, -15, 0, -15);
rightButton.imageInsets = UIEdgeInsetsMake(0.0, -15, 0, 0);
For the right side buttons, be careful: the FIRST button in the item array is the RIGHT one:
rightButton.imageInsets = UIEdgeInsetsMake(0.0, -15, 0, 0);
middleButton.imageInsets = UIEdgeInsetsMake(0.0, -15, 0, -15);
leftButton.imageInsets = UIEdgeInsetsMake(0.0, 0.0, 0, -15);
IMPORTANT: Split the inset between the two neighbors; if apply the entire inset to one edge, it will become obvious that the buttons are overlapping in the "blank" space - one button gets all of the "gap" touches. Even when "split" the adjustment like this, at -40 on both edges, the tap will definitely go to wrong button sometimes. -15 or -20 is the most to consider using with this technique.
By applying this method, the button could even be moved around in four directions.
iOS 11 UINavigationBar bar button items alignment
There is good article on this : http://www.matrixprojects.net/p/uibarbuttonitem-ios11/
Using this we can at least push rightbarbuttonitems to right till it leaves 8 pixel margin from trailing of the UINavigationBar.
Explained really well.
Placing a custom view based UIBarButtonItem in the navigation bar without default horizontal padding
55As commented above, the solution I went with is based on this answer to a different, but very much related question: How to adjust UIToolBar left and right padding. It is also facilitated by (and depends on) iOS5, which allows you to set multiple buttons on the left or right side, instead of just one.
Here's an example of removing the padding to the left of a custom left button item:
UIBarButtonItem *backButtonItem // Assume this exists, filled with our custom view
// Create a negative spacer to go to the left of our custom back button,
// and pull it right to the edge:
UIBarButtonItem *negativeSpacer = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace
target:nil action:nil];
negativeSpacer.width = -5;
// Note: We use 5 above b/c that's how many pixels of padding iOS seems to add
// Add the two buttons together on the left:
self.navigationItem.leftBarButtonItems = [NSArray
arrayWithObjects:negativeSpacer, backButtonItem, nil];
And with this, the left padding for the left bar button item in a navigation bar, is gone!
NOTE: This has worked for me in iOS5 and iOS6. Given that iOS7 is considerably different (from the public demos), those of you with the early seeds of iOS7 should test if something so unintentional, like this hack, will actually continue to work for you beyond iOS6.
UIToolbar left and right spacing when rotating device in IOS11
First of all layoutSubviews
is not the right place to add all the items. For example every time device orientate layoutSubviews
is called and all items will be created again. Use init?(coder:)
or init(frame:)
.
Once you move your code to init?(coder:)
or init(frame:)
you will see the left and right margin (currently it appears only when you orientate). This is actual behaviour of UIToolBar
, It adds margin on both sides automatically.
To remove that margin just add negative separator of fixed size on start and end of toolbar items.
let negativeFizedSpace = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
negativeFizedSpace.width = -20 // Spacing 20 for iPad and 16 for iPhone
class ToolbarClass: UIToolbar {
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
//Space
let spaceButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let spaceBetween:UIBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.fixedSpace, target: nil, action: nil)
spaceBetween.width = 1.0
let nameSpace:UIBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.fixedSpace, target: nil, action: nil)
nameSpace.width = 10
//Logo
let logoImage = UIImage(named: "MBS-Logo")
let logoImageView = UIImageView(image: logoImage)
logoImageView.frame = CGRect(x: -46, y: 0, width: 48, height: 54)
logoImageView.contentMode = .scaleAspectFit
let logoView = UIView(frame: CGRect(x: 0, y: 0, width: 48, height: 54))
logoView.clipsToBounds = false
logoView.layer.cornerRadius = logoView.frame.width / 2
logoView.addSubview(logoImageView)
let logoImg = UIBarButtonItem(customView: logoView)
logoImg.customView = logoView
//Profile
let profileImage = UIImage(named: "No-Profile")
let profileImageView = UIImageView(image: profileImage)
profileImageView.frame = CGRect(x: 40, y: 0, width: 50, height: 50)
profileImageView.contentMode = .scaleAspectFit
profileImageView.clipsToBounds = true
profileImageView.layer.cornerRadius = profileImageView.frame.width / 2
let profileView = UIView(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
profileView.clipsToBounds = false
profileView.addSubview(profileImageView)
let profileImg = UIBarButtonItem(customView: profileView)
profileImg.customView = profileView
//NameLabel
let nameLbl = UILabel()
nameLbl.frame = CGRect(x: 0, y: 0, width: 200, height: 60)
nameLbl.text = "Hi"
nameLbl.font = UIFont(name: "Roboto", size: 22)
nameLbl.textColor = UIColor.white
let nameLabel = UIBarButtonItem()
nameLabel.customView = nameLbl
//Settings
let settingsBtn = UIButton()
settingsBtn.frame = CGRect(x: 0, y: 0, width: 64, height: 60)
settingsBtn.setImage(UIImage(named: "Settings-Bar")?.withRenderingMode(.alwaysOriginal), for: .normal)
settingsBtn.addTarget(self, action: #selector(self.settingsPressed), for: .touchUpInside)
let settingsButton = UIBarButtonItem()
settingsButton.customView = settingsBtn
//Classes
let classesBtn = UIButton()
classesBtn.frame = CGRect(x: 0, y: 0, width: 64, height: 60)
classesBtn.setImage(UIImage(named: "Classes-Bar")?.withRenderingMode(.alwaysOriginal), for: .normal)
classesBtn.addTarget(self, action: #selector(self.classesPressed), for: .touchUpInside)
let classesButton = UIBarButtonItem()
classesButton.customView = classesBtn
let negativeFizedSpace = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
negativeFizedSpace.width = -20 // Spacing 20 for iPad and 16 for iPhone
//Set buttons
self.setItems([negativeFizedSpace, profileImg, logoImg, nameSpace, nameLabel, spaceButton, classesButton, spaceBetween, settingsButton, negativeFizedSpace], animated: false)
}
//Set height of toolbar
override func sizeThatFits(_ size: CGSize) -> CGSize {
var size = super.sizeThatFits(size)
size.height = 60
return size
}
}
UPDATE
If using Navigation Controller Toolbar. Alternate is to create a UIViewController
extension to add generic toolbar items and call that in viewDidLoad
method of your ViewController.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
addGeneralToolbarItems()
}
}
class ToolbarClass: UIToolbar {
//Set height of toolbar
override func sizeThatFits(_ size: CGSize) -> CGSize {
var size = super.sizeThatFits(size)
size.height = 60
return size
}
}
extension UIViewController {
func addGeneralToolbarItems() {
//Space
let spaceButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let spaceBetween:UIBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.fixedSpace, target: nil, action: nil)
spaceBetween.width = 1.0
let nameSpace:UIBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.fixedSpace, target: nil, action: nil)
nameSpace.width = 10
//Logo
let logoImage = UIImage(named: "MBS-Logo")
let logoImageView = UIImageView(image: logoImage)
logoImageView.frame = CGRect(x: -46, y: 0, width: 48, height: 54)
logoImageView.contentMode = .scaleAspectFit
let logoView = UIView(frame: CGRect(x: 0, y: 0, width: 48, height: 54))
logoView.clipsToBounds = false
logoView.layer.cornerRadius = logoView.frame.width / 2
logoView.addSubview(logoImageView)
let logoImg = UIBarButtonItem(customView: logoView)
logoImg.customView = logoView
//Profile
let profileImage = UIImage(named: "No-Profile")
let profileImageView = UIImageView(image: profileImage)
profileImageView.frame = CGRect(x: 40, y: 0, width: 50, height: 50)
profileImageView.contentMode = .scaleAspectFit
profileImageView.clipsToBounds = true
profileImageView.layer.cornerRadius = profileImageView.frame.width / 2
let profileView = UIView(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
profileView.clipsToBounds = false
profileView.addSubview(profileImageView)
let profileImg = UIBarButtonItem(customView: profileView)
profileImg.customView = profileView
//NameLabel
let nameLbl = UILabel()
nameLbl.frame = CGRect(x: 0, y: 0, width: 200, height: 60)
nameLbl.text = "Hi"
nameLbl.font = UIFont(name: "Roboto", size: 22)
nameLbl.textColor = UIColor.white
let nameLabel = UIBarButtonItem()
nameLabel.customView = nameLbl
//Settings
let settingsBtn = UIButton()
settingsBtn.frame = CGRect(x: 0, y: 0, width: 64, height: 60)
settingsBtn.setImage(UIImage(named: "Settings-Bar")?.withRenderingMode(.alwaysOriginal), for: .normal)
settingsBtn.addTarget(self, action: #selector(self.settingsPressed), for: .touchUpInside)
let settingsButton = UIBarButtonItem()
settingsButton.customView = settingsBtn
//Classes
let classesBtn = UIButton()
classesBtn.frame = CGRect(x: 0, y: 0, width: 64, height: 60)
classesBtn.setImage(UIImage(named: "Classes-Bar")?.withRenderingMode(.alwaysOriginal), for: .normal)
classesBtn.addTarget(self, action: #selector(self.classesPressed), for: .touchUpInside)
let classesButton = UIBarButtonItem()
classesButton.customView = classesBtn
let negativeFizedSpace = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
negativeFizedSpace.width = -20 // Spacing 20 for iPad and 16 for iPhone
//Set buttons
self.setToolbarItems([negativeFizedSpace, profileImg, logoImg, nameSpace, nameLabel, spaceButton, classesButton, spaceBetween, settingsButton, negativeFizedSpace], animated: false)
}
@objc func settingsPressed() {
}
@objc func classesPressed() {
}
}
reduce left space and right space from navigation bar left and right bar button item
UIBarButtonItem *negativeSpacer = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace
target:nil action:nil];
negativeSpacer.width = -16;
[self.navigationItem setLeftBarButtonItems:[NSArray arrayWithObjects:negativeSpacer,yourbutton, nil] animated:YES];
Use like that.
Related Topics
Why Does My Uitableview "Jump" When Inserting or Removing a Row
Negative Spacer for Uibarbuttonitem in Navigation Bar on iOS 11
What Exactly Does 'Pod Repo Update' Do
Autolayout to Keep View Sizes Proportional
"'Cdvplugin.H' File Not Found" in Cordova as Component (Cleaver)
Automatic Signing Is Unable to Resolve an Issue with the "Projectname" Target's Entitlements File
What Exactly Are Protocols and Delegates and How Are They Used in iOS
Re-Authenticating User Credentials Swift
How to Use Swift Repl with iOS Sdk
Xcode 7.1 Swift 2 Unknown Class in Interface Builder File
iOS 11 Large Navigation Bar Title Unexpected Velocity
Accessorybuttontappedforrowwithindexpath: Not Getting Called
Programmatically Focus on a Form in a Webview (Wkwebview)
Allow Uiscrollview and Its Subviews to Both Respond to a Touch