How can I change the position of a UIBarButtonItem in a UINavigationBar? I would like my button to be about 5px higher than its normal position.

- 12,337
- 20
- 57
- 73

- 4,695
- 9
- 47
- 72
-
Do you also want the UINavigation bar to be behind it or just have the button free floating? – Jab Apr 23 '11 at 04:07
-
Not quite sure what you mean - I think the answer is that I want it to behave exactly as a normal UIBarButtonItem inside a UINavigationBar would. Say, if it was the leftBarButtonItem...everything the same, just 5px higher. The button is using a custom image, as well, if that's relevant. – Ben Williams Apr 24 '11 at 10:44
-
one approach is, simply use images instead. this "always works" even though it's a hassle. – Fattie Nov 10 '13 at 16:20
21 Answers
This code creates a back button for UINavigationBar with image background and custom position. The trick is to create an intermediate view and modify its bounds.
Swift 5
let menuBtn = UIButton(type: .custom)
let backBtnImage = UIImage(named: "menu")
menuBtn.setBackgroundImage(backBtnImage, for: .normal)
menuBtn.addTarget(self, action: #selector(showMenuTapped), for: .touchUpInside)
menuBtn.frame = CGRect(x: 0, y: 0, width: 45, height: 45)
let view = UIView(frame: CGRect(x: 0, y: 0, width: 45, height: 45))
view.bounds = view.bounds.offsetBy(dx: 10, dy: 3)
view.addSubview(menuBtn)
let backButton = UIBarButtonItem(customView: view)
navigationItem.leftBarButtonItem = backButton
Objective C
UIButton *backBtn = [UIButton buttonWithType:UIButtonTypeCustom];
UIImage *backBtnImage = [UIImage imageNamed:@"btn-back"];
UIImage *backBtnImagePressed = [UIImage imageNamed:@"btn-back-pressed"];
[backBtn setBackgroundImage:backBtnImage forState:UIControlStateNormal];
[backBtn setBackgroundImage:backBtnImagePressed forState:UIControlStateHighlighted];
[backBtn addTarget:self action:@selector(goBack) forControlEvents:UIControlEventTouchUpInside];
backBtn.frame = CGRectMake(0, 0, 63, 33);
UIView *backButtonView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 63, 33)];
backButtonView.bounds = CGRectOffset(backButtonView.bounds, -14, -7);
[backButtonView addSubview:backBtn];
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithCustomView:backButtonView];
self.navigationItem.leftBarButtonItem = backButton;

- 7,528
- 9
- 56
- 96

- 7,320
- 4
- 27
- 30
-
unfortunately does not work when the UIBarButtonItem is inside a UIToolbar. :( – Duck Apr 25 '13 at 16:39
-
32A problem with this however is that the area that can be tapped is still restricted to the default UIBarButtonItem area. Moving the UIButton will probably cause the area that can be tapped to get smaller and thus sometime make it seem as if the UIButton doesn't recognise the tap. – iMaddin Sep 11 '13 at 14:20
-
I agree with @iMaddin, the touch area becomes smaller and smaller the larger your offset gets. – ajmccall Nov 03 '13 at 17:10
-
3Setting offset (by `CGRectOffset`) for UIButton doesn't work in iOS7. UIBarButtonItem seems to have fixed position in iOS7 and moving/offsetting x,y coordinate of UIButton doesn't work. Modifying width and height of UIButton works but no impact of changing x,y coordinates. – Saurabh Hooda Nov 23 '13 at 07:30
-
I don't see how this answer gives a solution to changing the position of the `UIBarButtonItem`. It only ads complexity and causes other issues like tapping area. I think this matter should be opened up as a feature or bug on apple's issue reporting pages, since a lot of developers would like to change this coordinates or have a bit more control over the button – Roland Dec 21 '13 at 18:25
-
Best workaround for the problem in tap target size shrinking with this answer is here: http://stackoverflow.com/a/18918378/115676 – AlexD Jan 07 '14 at 19:11
-
I found that adding a TapGestureRecognizer to the UIBarButtonItem customView took care of the shrinking hit area when offset. – Nate Potter Mar 12 '14 at 00:33
-
@iMaddin - this can be fixed by subclassing the wrapper view (superview of button, custom view of uibarbuttonitem) and overriding the hittest method to return button accordingly. – JakubKnejzlik Jul 21 '14 at 19:43
-
-
You can always add a tap gesture recognizer to the intermediate view to receive the tap action. – strongwillow Jul 22 '16 at 07:17
-
-
This answer worked for me in Obj-C but it definitely reduced the tap target size. Does anyone have a more recent solution for this? iOS 15+... I see the comment now by @NatePotter . I will try that and report back. – Jamie M. Nov 07 '22 at 14:14
I solved using transform and custom view:
(Swift)
// create the button
let suggestImage = UIImage(named: "tab-item-popcorn-on")!.imageWithRenderingMode(.AlwaysOriginal)
let suggestButton = UIButton(frame: CGRectMake(0, 0, 40, 40))
suggestButton.setBackgroundImage(suggestImage, forState: .Normal)
suggestButton.addTarget(self, action: Selector("suggesMovie:"), forControlEvents:.TouchUpInside)
// here where the magic happens, you can shift it where you like
suggestButton.transform = CGAffineTransformMakeTranslation(10, 0)
// add the button to a container, otherwise the transform will be ignored
let suggestButtonContainer = UIView(frame: suggestButton.frame)
suggestButtonContainer.addSubview(suggestButton)
let suggestButtonItem = UIBarButtonItem(customView: suggestButtonContainer)
// add button shift to the side
navigationItem.rightBarButtonItem = suggestButtonItem

- 4,540
- 1
- 25
- 25
-
Man how did you come up with this??? Thanks, I have been looking for a solution for weeks now – irkinosor Mar 25 '16 at 22:06
-
This was the only thing that worked for me! (and I tried lots of other proposed solutions) – Joel Mar 10 '17 at 22:41
-
1This approach will break touchable area of the button, unfortunately. – atereshkov Jun 21 '18 at 14:15
There is no particularly good way to do this. Your best bet if you really must is to subclass UINavigationBar, and override layoutSubviews
to call [super layoutSubviews]
and then find and reposition the button's view.

- 92,546
- 13
- 126
- 145
-
I gave this a try, and it works, but you can see the button jumping around. So when the view is transitioning it's in the default position, then when the view is in place the button jumps. – Ben Williams Apr 29 '11 at 00:29
-
2Actually, I take it back. It works nicely. The jumping was a mistake on my behalf. Thanks! – Ben Williams Apr 29 '11 at 01:00
-
8
-
3Thank you for sharing this. Could you provide a sample? Thank you in advance. – Lorenzo B Oct 02 '11 at 11:00
-
Adjusting the frames of the leftBarButtonItem.customView and rightBarButtonItem.customView and not the center property prevented the jumping for me. – Nate Potter Oct 12 '12 at 14:55
-
This is a good sample! Look at the second answer it is more safe: http://stackoverflow.com/a/17434530/1351190 – Simone Lai Oct 18 '13 at 14:15
-
14Does no one know about using the appearance method? Put this in your app delegate: `[[UIBarButtonItem appearanceWhenContainedIn:[UINavigationBar class], nil] setBackButtonBackgroundVerticalPositionAdjustment:-3 forBarMetrics:UIBarMetricsDefault]; ` – barndog Dec 30 '13 at 08:10
-
@Adam see http://stackoverflow.com/questions/5761183/change-position-of-uibarbuttonitem-in-uinavigationbar/33122015#33122015 – Zoltán Matók Oct 14 '15 at 09:48
-
I am also having issues with getting the arrow to stop jumping. @alku83 could you share your code? – Chris Brasino Jan 07 '16 at 21:26
-
For those of you developing for iOS 5 who stumbled across this and were discouraged... Try something like this:
float my_offset_plus_or_minus = 3.0f;
UIBarButtonItem * item = [[UIBarButtonItem alloc] initWithTitle:@"title"
style:UIBarButtonItemStyleDone
target:someObject action:@selector(someMessage)];
[item setBackgroundVerticalPositionAdjustment:my_offset_plus_or_minus forBarMetrics:UIBarMetricsDefault];

- 8,714
- 7
- 67
- 92

- 189
- 1
- 2
The best way is to subclass your UINavigationBar, as described here: https://stackoverflow.com/a/17434530/1351190
Here is my example:
#define NAVIGATION_BTN_MARGIN 5
@implementation NewNavigationBar
- (void)layoutSubviews {
[super layoutSubviews];
UINavigationItem *navigationItem = [self topItem];
UIView *subview = [[navigationItem rightBarButtonItem] customView];
if (subview) {
CGRect subviewFrame = subview.frame;
subviewFrame.origin.x = self.frame.size.width - subview.frame.size.width - NAVIGATION_BTN_MARGIN;
subviewFrame.origin.y = (self.frame.size.height - subview.frame.size.height) / 2;
[subview setFrame:subviewFrame];
}
subview = [[navigationItem leftBarButtonItem] customView];
if (subview) {
CGRect subviewFrame = subview.frame;
subviewFrame.origin.x = NAVIGATION_BTN_MARGIN;
subviewFrame.origin.y = (self.frame.size.height - subview.frame.size.height) / 2;
[subview setFrame:subviewFrame];
}
}
@end
Hope it helps.

- 1
- 1

- 381
- 3
- 10
-
1It works well but using a loop for all subviews is a not necessary action. You just can take `[[navigationItem leftBarButtonItem] customView]` or `[[navigationItem rightBarButtonItem] customView]` and set those frames directly. – Rostyslav Druzhchenko Jan 28 '15 at 15:09
Try the code below,
UIBarButtonItem *button = [[UIBarButtonItem alloc] initWithTitle:@"Logout" style:UIBarButtonItemStyleDone target:self action:nil];
[button setBackgroundVerticalPositionAdjustment:-20.0f forBarMetrics:UIBarMetricsDefault];
[[self navigationItem] setRightBarButtonItem:button];
Its used to change the 'y' position in this code. Change the 'y' value (here it is -20.0f) according to your requirement. If the value is positive, it will down the button position. If the value is negative, it will up your button position.

- 5,008
- 3
- 35
- 39
I needed to set my button to be more over towards the right. Here's how I did it using UIAppearance in Swift. There's a vertical position property there as well so I imagine you can adjust in any direction.
UIBarButtonItem.appearance().setTitlePositionAdjustment(UIOffset.init(horizontal: 15, vertical: 0), forBarMetrics: UIBarMetrics.Default)
This seems much less invasive to me than messing with the frame directly or adding custom subviews.

- 8,228
- 4
- 50
- 65
If you're simply using an image and NOT the default chrome, you can use negative image insets (set in the size inspector) to move your image around inside the UIBarButtonItem (handy because by default the horizontal padding can result in the image being further to the inside than you want). You can use the image insets to position the image outside of the bounds of the UIBarButtonItem, as well, and the entire vicinity of the left-hand-side button is tappable, so you don't have to worry about ensuring it's positioned near a tap target. (at least, within reason.)

- 10,392
- 1
- 31
- 25
-
e.g. [leftButton setImageEdgeInsets:UIEdgeInsetsMake(0, -20, 0, 20)]; – Graham Perks May 27 '15 at 16:28
Navigation bar using change left bar position and image edge insets
swift 4
let leftBarButtonItem = UIBarButtonItem.init(image: UIImage(named:"ic_nav-bar_back.png"), landscapeImagePhone: nil, style: .plain, target: viewController, action: #selector(viewController.buttonClick(_:)))
leftBarButtonItem.imageInsets = UIEdgeInsets(top: 0, left: -15, bottom: 0, right: 0)
leftBarButtonItem.tintColor = UIColor(hex: 0xED6E19)
viewController.navigationItem.setLeftBarButton(leftBarButtonItem, animated: true)

- 972
- 10
- 12
The best solution I could find is to initialize a UIBarButtonItem with a subview that includes extra space to the left/right. That way you wont have to worry about subclassing, and changing the layout of other elements inside the navigation bar, such as the title.
For example, to move a button 14 points to the left:
UIView *containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, image.size.width + 14, image.size.height)];
UIButton* button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(-14, 0, image.size.width, image.size.height);
[button setImage:image forState:UIControlStateNormal];
[button addTarget:target action:action forControlEvents:UIControlEventTouchUpInside];
[containerView addSubview:button];
UIButton* button2 = [UIButton buttonWithType:UIButtonTypeCustom];
button2.frame = CGRectMake(0, 0, image.size.width + 14, image.size.height);
[button2 addTarget:target action:action forControlEvents:UIControlEventTouchUpInside];
[containerView addSubview:button2];
UIBarButtonItem* item = [[[self alloc] initWithCustomView:containerView] autorelease];

- 2,787
- 3
- 20
- 27
Swift 3.1
let cancelBarButtonItem = UIBarButtonItem()
cancelBarButtonItem.setBackgroundVerticalPositionAdjustment(4, for: .default)
vc.navigationItem.setLeftBarButton(cancelBarButtonItem, animated: true)

- 5,977
- 3
- 50
- 55
- Swift 3
- custom navigation bar height
- no title jumping
Step 1: Set title position using Appearance API. For example, in AppDelegate's didFinishLaunchingWithOptions
UINavigationBar.appearance().setTitleVerticalPositionAdjustment(-7, for: .default)
Step 2: Subclass UINavigationBar
class YourNavigationBar: UINavigationBar {
let YOUR_NAV_BAR_HEIGHT = 60
override func sizeThatFits(_ size: CGSize) -> CGSize {
return CGSize(width: UIScreen.main.bounds.width,
height: YOUR_NAV_BAR_HEIGHT)
}
override func layoutSubviews() {
super.layoutSubviews()
let navigationItem = self.topItem
for subview in subviews {
if subview == navigationItem?.leftBarButtonItem?.customView ||
subview == navigationItem?.rightBarButtonItem?.customView {
subview.center = CGPoint(x: subview.center.x, y: YOUR_NAV_BAR_HEIGHT / 2)
}
}
}
}

- 175
- 2
- 9
Here's Adriano's solution using Swift 3. It was the only solution that worked for me and I tried several.
let suggestImage = UIImage(named: "menu.png")!
let suggestButton = UIButton(frame: CGRect(x:0, y:0, width:34, height:20))
suggestButton.setBackgroundImage(suggestImage, for: .normal)
suggestButton.addTarget(self, action: #selector(self.showPopover(sender:)), for:.touchUpInside)
suggestButton.transform = CGAffineTransform(translationX: 0, y: -8)
// add the button to a container, otherwise the transform will be ignored
let suggestButtonContainer = UIView(frame: suggestButton.frame)
suggestButtonContainer.addSubview(suggestButton)
let suggestButtonItem = UIBarButtonItem(customView: suggestButtonContainer)
// add button shift to the side
navigationItem.leftBarButtonItem = suggestButtonItem

- 3,946
- 1
- 23
- 25
In my case
change barbuttonItem's frame to customize spaces
Add, Remove barButtonItems dynamically.
change tint colors by tableview's contentOffset.y
If your minimum target is iOS 11, you can change the barButton frames in the viewDidLayoutSubviews
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// Change the navigationBar item frames
if let customView = wishButton.customView?.superview {
customView.transform = CGAffineTransform(translationX: 7.0, y: 0)
}
if let customView = gourmetCountButton.customView?.superview {
customView.transform = CGAffineTransform(translationX: 9.0, y: 0)
}
}
But, it's Only worked on iOS 11.
I also tried using the fixedSpace. But It didn't work in multiple navigationBarButton items.
let space = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
space.width = -10
So, I changed customView's width to adjust horizontal space.
This is one of the my barButtonItem class
final class DetailShareBarButtonItem: UIBarButtonItem {
// MARK: - Value
// MARK: Public
***// Change the width to adjust space***
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 32.0, height: 30.0))
override var tintColor: UIColor? {
didSet {
button.tintColor = tintColor
}
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setButton()
}
required override init() {
super.init()
setButton()
}
// MARK: - Function
// MARK: Private
private func setButton() {
// Button
button.setImage( #imageLiteral(resourceName: "navibarIcShare02White").withRenderingMode(.alwaysTemplate), for: .normal)
button.tintColor = .white
button.imageEdgeInsets = UIEdgeInsetsMake(0, 1.0, 1.0, 0)
button.imageView?.contentMode = .scaleAspectFill
let containerView = UIView(frame: button.bounds)
containerView.backgroundColor = .clear
containerView.addSubview(button)
customView = containerView
}
}
This is the result.
I tested on iOS 9 ~ 11, (Swift 4)

- 3,179
- 29
- 26
As @Anomie said, we need to subclass UINavigationBar
, and override layoutSubviews()
.
This will place all right bar button items firmly attached to the right side of the navigation bar (as opposed to being slightly left-adjusted by default):
class AdjustedNavigationBar: UINavigationBar {
override func layoutSubviews() {
super.layoutSubviews()
if let rightItems = topItem?.rightBarButtonItems where rightItems.count > 1 {
for i in 0..<rightItems.count {
let barButtonItem = rightItems[i]
if let customView = barButtonItem.customView {
let frame = customView.frame
customView.frame = CGRect(x: UIApplication.sharedApplication().windows.last!.bounds.size.width-CGFloat(i+1)*44, y: frame.origin.y, width: frame.size.width, height: frame.size.height)
}
}
}
}
}
The only place to set the UINavigationBar property of UINavigationController is in its init(), like so:
let controllerVC = UINavigationController(navigationBarClass: AdjustedNavigationBar.self, toolbarClass: nil)
controllerVC.viewControllers = [UIViewController()]
The second line sets the root view controller of UINavigationController.
(Since we cannot set it via init(rootViewController:)

- 3,923
- 2
- 33
- 64
-
You really shouldn't be using your window bounds as a reference point... `UIApplication.sharedApplication().windows.last!.bounds.size.width`. – Zorayr Feb 18 '16 at 02:08
You can always do adjustments using Insets on the button. For example,
UIButton *toggleBtn = [UIButton buttonWithType:UIButtonTypeCustom];
[toggleBtn setFrame:CGRectMake(0, 0, 20, 20)];
[toggleBtn addTarget:self action:@selector(toggleView) forControlEvents:UIControlEventTouchUpInside];
[toggleBtn setImageEdgeInsets:((IS_IPAD)? UIEdgeInsetsMake(0,-18, 0, 6) : UIEdgeInsetsMake(0, -3, 0, -3))];
UIBarButtonItem *toggleBtnItem = [[UIBarButtonItem alloc] initWithCustomView: toggleBtn];
self.navigationItem.rightBarButtonItems = [NSArray arrayWithObjects:searchBtnItem, toggleBtnItem, nil];
It works for me.

- 669
- 6
- 9
Init UIBarButtonItem
with custom view and overload layoutSubviews
in custom view, like this
-(void) layoutSubviews {
[super layoutSubviews];
CGRect frame = self.frame;
CGFloat offsetY = 5;
frame.origin.y = (44 - frame.size.height) / 2 - offsetY;
self.frame = frame;
}

- 479
- 5
- 8
If you're only looking for adjusting the position of the customized back button like me, I used one of the solutions that add insets to the UIImage itself to do achieve this.
I used the solution from: https://stackoverflow.com/a/31240900/1241783
Add this extension function
import UIKit
extension UIImage {
func imageWithInsets(insets: UIEdgeInsets) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(
CGSize(width: self.size.width + insets.left + insets.right,
height: self.size.height + insets.top + insets.bottom), false, self.scale)
let _ = UIGraphicsGetCurrentContext()
let origin = CGPoint(x: insets.left, y: insets.top)
self.draw(at: origin)
let imageWithInsets = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return imageWithInsets
}
}
then use it like such when customizing the back button
let backIcon = UIImage(named: "back_btn_icon")!.imageWithInsets(insets: UIEdgeInsets(top: 0, left: 0, bottom: 5, right: 0))
navigationController?.navigationBar.backIndicatorImage = backIcon
navigationController?.navigationBar.backIndicatorTransitionMaskImage = backIcon
Adjust the insets as you desire

- 2,357
- 5
- 29
- 50
I found the solution of this problem by making adjustment in the Image Edge Insets of the custom button. I had the requirement in the app to increase the height of the navigation bar and after increasing the height makes the rightBarButtonItem and leftBarButtonItem images unpositioned problem.
Find the code below:-
UIImage *image = [[UIImage imageNamed:@"searchbar.png"];
UIButton* searchbutton = [UIButton buttonWithType:UIButtonTypeCustom];
[searchbutton addTarget:self action:@selector(searchBar:) forControlEvents:UIControlEventTouchUpInside];
searchbutton.frame = CGRectMake(0,0,22, 22);
[searchbutton setImage:image forState:UIControlStateNormal];
[searchbutton setImageEdgeInsets:UIEdgeInsetsMake(-50, 0,50, 0)];
// Make BarButton Item
UIBarButtonItem *navItem = [[UIBarButtonItem alloc] initWithCustomView:searchbutton];
self.navigationItem.rightBarButtonItem = navItem;
Hope this helps anyone.

- 633
- 5
- 14
Affine transform can do what you need. In my case designer gave me 16x16 close icon and I want to create 44x44 tap area.
closeButton.transform = CGAffineTransform(translationX: (44-16)/2, y: 0)
closeButton.snp.makeConstraints { make in
make.size.equalTo(CGSize(width: 44, height: 44))
}
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: closeButton)

- 371
- 2
- 7
Here is a simple workaround that was sufficient for my needs. I added an info button on the right hand side of the UINavigationBar but by default it sits way too close to the edge. By extending the width of the frame I was able to create the extra spacing needed on the right.
UIButton *info = [UIButton buttonWithType:UIButtonTypeInfoLight];
CGRect frame = info.frame;
frame.size.width += 20;
info.frame = frame;
myNavigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc]initWithCustomView:info]autorelease];

- 2,723
- 4
- 28
- 37
-
1That works ok for moving horizontally, but not vertically I don't think. – Ben Williams Aug 29 '11 at 07:06