Change position of UIBarButtonItem in UINavigationBar

IphoneObjective CUinavigationbarUibarbuttonitem

Iphone Problem Overview


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.

Iphone Solutions


Solution 1 - Iphone

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;

Solution 2 - Iphone

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

Solution 3 - Iphone

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.

Solution 4 - Iphone

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];

Solution 5 - Iphone

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.

Solution 6 - Iphone

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.

Solution 7 - Iphone

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.

Solution 8 - Iphone

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.)

Solution 9 - Iphone

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)

Solution 10 - Iphone

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];

Solution 11 - Iphone

Swift 3.1

let cancelBarButtonItem = UIBarButtonItem()
cancelBarButtonItem.setBackgroundVerticalPositionAdjustment(4, for: .default)
vc.navigationItem.setLeftBarButton(cancelBarButtonItem, animated: true)

Solution 12 - Iphone

  • 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)
            }
        }
    }
}

Solution 13 - Iphone

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

Solution 14 - Iphone

In my case

  1. change barbuttonItem's frame to customize spaces

  2. Add, Remove barButtonItems dynamically.

  3. change tint colors by tableview's contentOffset.y

like this enter image description here enter image description here

enter image description here enter image description here

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)

enter image description here

Solution 15 - Iphone

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:)

Solution 16 - Iphone

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;
}

Solution 17 - Iphone

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.

Solution 18 - Iphone

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

Solution 19 - Iphone

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];

Solution 20 - Iphone

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.

Solution 21 - Iphone

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)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBen WilliamsView Question on Stackoverflow
Solution 1 - IphoneEvgeny ShadchnevView Answer on Stackoverflow
Solution 2 - IphoneAdriano SpadoniView Answer on Stackoverflow
Solution 3 - IphoneAnomieView Answer on Stackoverflow
Solution 4 - IphoneJoe MarxView Answer on Stackoverflow
Solution 5 - IphoneSimone LaiView Answer on Stackoverflow
Solution 6 - IphoneAugustine P AView Answer on Stackoverflow
Solution 7 - IphoneMark BridgesView Answer on Stackoverflow
Solution 8 - IphoneChristopher SwaseyView Answer on Stackoverflow
Solution 9 - IphoneSrinivasan_iOSView Answer on Stackoverflow
Solution 10 - IphoneJorge AguirreView Answer on Stackoverflow
Solution 11 - IphoneAdam SmakaView Answer on Stackoverflow
Solution 12 - Iphoneandriy_fedinView Answer on Stackoverflow
Solution 13 - IphoneJP AquinoView Answer on Stackoverflow
Solution 14 - IphoneDenView Answer on Stackoverflow
Solution 15 - IphoneZoltán MatókView Answer on Stackoverflow
Solution 16 - Iphonealex1704View Answer on Stackoverflow
Solution 17 - IphoneJoe MView Answer on Stackoverflow
Solution 18 - IphoneBruceView Answer on Stackoverflow
Solution 19 - IphoneannaView Answer on Stackoverflow
Solution 20 - IphoneiGWView Answer on Stackoverflow
Solution 21 - IphonevdugnistView Answer on Stackoverflow