How to Edit Empty Spaces of Left, Right UIBarButtonItem in UINavigationBar [iOS 7]
Asked Answered
O

14

84

I was using iOS 6.1 earlier, but now I have moved to iOS 7. Along with other problems, I have observed that in my navigation bar, the left space of left bar button item and right empty space of the right button bar item are quite more in IOS 7 than in iOS 6.

I need to know is there a way I can reduce empty spaces of left, right bar button items in navigation bar??

Outstretched answered 20/9, 2013 at 10:44 Comment(2)
Try this: github.com/devxoul/UINavigationItem-MarginBurgage
@devxoul, UINavigationItem-Margin has issue with iOS14. i have created issue for that if you have any solution then please look into this.Cecilycecity
B
223

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];
Blatherskite answered 20/9, 2013 at 13:45 Comment(14)
Its really working in iOS 7, as I am using this in my couple of projects right now... there is difference in point system so you have to take care of that....Blatherskite
It does not work if you do [NSArray arrayWithObjects:requiredButton, negativeSpacer]. Mind the order.Disorient
Works great, and note that for rightBarButtonItems the spacer must be the first item in the array if you want to change the right marginGavelkind
Even you can try this category to set margins: github.com/devxoul/UINavigationItem-MarginBurgage
@C_X for iPhone 6, the -16 margin doesn't seem to work. -20 seems right.. Any idea how we can make this work fine across all iPhone devices?Placate
When I post this at that time there were no iPhone 6 or 6 + :)Blatherskite
why is the width given negative value?Caudell
@TheRohanSanap - a positive width would be a spacer that increases how much empty space there is before the first visual item. This trick of a negative width decreases the empty space.Selfrestraint
Is there a way to get this to work for multiple buttons? I have three buttons and I would like to reduce the spacing between each of them.Clinker
@Clinker I'm also asking. Because I found the 'negative' space solution doesn't work for 2 or more UIBarButtonItem. It seems that the spacing is also affected by the titleView width.Acrobatics
@Clinker try using flexibleSpace, you can mix and match as well.Blatherskite
Does not work if there is more than 1 button, iOS 12Betelgeuse
Not working for iOS 13! Do anyone knows how to achieve this in iOS 13 ?Brnaba
@niku, Did you got solution?Nitramine
D
23

Based on @C_X his answer I've created a category which adds and positions the UIBarButtonItem based on the iOS version of the current device.

// UINavigationItem+Additions.h
@interface UINavigationItem (Additions)
- (void)addLeftBarButtonItem:(UIBarButtonItem *)leftBarButtonItem;
- (void)addRightBarButtonItem:(UIBarButtonItem *)rightBarButtonItem;
@end

// UINavigationItem+Additions.m
@implementation UINavigationItem (Additions)

- (void)addLeftBarButtonItem:(UIBarButtonItem *)leftBarButtonItem
{
    if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0) {
        // Add a negative spacer on iOS >= 7.0
        UIBarButtonItem *negativeSpacer = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace
                              target:nil action:nil];
        negativeSpacer.width = -10;
        [self setLeftBarButtonItems:[NSArray arrayWithObjects:negativeSpacer, leftBarButtonItem, nil]];
    } else {
        // Just set the UIBarButtonItem as you would normally
        [self setLeftBarButtonItem:leftBarButtonItem];
    }
}

- (void)addRightBarButtonItem:(UIBarButtonItem *)rightBarButtonItem
{
    if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0) {
        // Add a negative spacer on iOS >= 7.0
        UIBarButtonItem *negativeSpacer = [[UIBarButtonItem alloc]
                                       initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace
                                       target:nil action:nil];
        negativeSpacer.width = -10;
        [self setRightBarButtonItems:[NSArray arrayWithObjects:negativeSpacer, rightBarButtonItem, nil]];
    } else {
        // Just set the UIBarButtonItem as you would normally
        [self setRightBarButtonItem:rightBarButtonItem];
    }
}

@end

In your view controller you can now use [self.navigationItem addLeftBarButtonItem:leftBarButtonItem]; and [self.navigationItem addRightBarButtonItem:rightBarButtonItem];

I've also tried subclassing UIButton and override -alignmentRectInsets but this gave me problems with transitions between views.

Deserved answered 25/9, 2013 at 15:11 Comment(0)
S
10

For Swift 2.0, this was my solution to get the following effect...

(the actual values may be different, depending on your situation)

enter image description here

let captureButton = UIButton()
captureButton.setTitle("CAPTURE DETAILS", forState: .Normal)
captureButton.frame = CGRectMake(0, 0, 200, 95)
captureButton.addTarget(self, action: Selector("showCaptureDetailsForm:"), forControlEvents: .TouchUpInside) // *** See update below for Swift 2.2 syntax
captureButton.setBackgroundImage(UIImage(named: "blueTopRight"), forState: .Normal)
        
let rightBarButton = UIBarButtonItem()
rightBarButton.customView = captureButton        
        
let negativeSpacer = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FixedSpace, target: nil, action: nil)
negativeSpacer.width = -25;
        
self.navigationItem.setRightBarButtonItems([negativeSpacer, rightBarButton ], animated: false)

Swift 2.2 UPDATE:

For Swift 2.2, the action: Selector method has changed, and should be typed as follows

captureButton.addTarget(self, action: #selector(YourViewController.showCaptureDetailsForm(_:)), forControlEvents: .TouchUpInside)

Synonymy answered 18/3, 2016 at 9:20 Comment(0)
O
9

This is my solution for Swift 3.0:

rightBtn.imageInsets = UIEdgeInsets(top: 0, left: -13.0, bottom: 0, right: 13.0) 
self.navigationItem.rightBarButtonItem = rightBtn
Outermost answered 9/3, 2017 at 4:11 Comment(0)
P
6

In order to fix this bug, you must subclass UIButton so that you can override alignmentRectInsets. From my testing, you'll need to return a UIEdgeInsets with either a positive right offset or a positive left offset, depending on the button position. These numbers make no sense to me (at least one of them should be negative, according to common sense), but this is what actually works:

- (UIEdgeInsets)alignmentRectInsets {
    UIEdgeInsets insets;
    if (IF_ITS_A_LEFT_BUTTON) {
        insets = UIEdgeInsetsMake(0, 9.0f, 0, 0);
    } 
    else { // IF_ITS_A_RIGHT_BUTTON
        insets = UIEdgeInsetsMake(0, 0, 0, 9.0f);
    }
    return insets;
}

Special thanks to @zev for suggesting I try adjusting alignmentRectInsets.

Polychromy answered 20/9, 2013 at 13:54 Comment(4)
thanks for the answer. Although it's a good solution but I have preferred @C_X answer because right now I'm trying to avoid sub-classing..Outstretched
This caused me some odd shifting of the buttons during animations in iOS 6, they would start out in the wrong spot and shift to match the insets after the animation completed.Trenna
I wouldn't be surprised. The above code is only needed to get an iOS 6 style layout for iOS 7 builds.Polychromy
Quick Fix, Nice job. However for iOS 9, you need to return the insets not set them:Polyploid
T
5

Following smek's lead I made a category but modified it to provide backwards compatibility rather than forwards. I setup everything to work how I want it in iOS 7 and then if the user is running something lower I start mucking with things.

@interface UINavigationItem (BarButtonItemSpacingSupport)

- (void)addLeftBarButtonItem:(UIBarButtonItem *)leftBarButtonItem;
- (void)addRightBarButtonItem:(UIBarButtonItem *)rightBarButtonItem;

@end

@implementation UINavigationItem (BarButtonItemSpacingSupport)

- (void)addLeftBarButtonItem:(UIBarButtonItem *)leftBarButtonItem
{
    if (SYSTEM_VERSION_LESS_THAN(@"7.0")) {
        // Add a spacer on when running lower than iOS 7.0
        UIBarButtonItem *negativeSpacer = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace
                                                                                        target:nil action:nil];
        negativeSpacer.width = 10;
        [self setLeftBarButtonItems:[NSArray arrayWithObjects:negativeSpacer, leftBarButtonItem, nil]];
    } else {
        // Just set the UIBarButtonItem as you would normally
        [self setLeftBarButtonItem:leftBarButtonItem];
    }
}

- (void)addRightBarButtonItem:(UIBarButtonItem *)rightBarButtonItem
{
    if (SYSTEM_VERSION_LESS_THAN(@"7.0")) {
        // Add a spacer on when running lower than iOS 7.0
        UIBarButtonItem *negativeSpacer = [[UIBarButtonItem alloc]
                                           initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace
                                           target:nil action:nil];
        negativeSpacer.width = 10;
        [self setRightBarButtonItems:[NSArray arrayWithObjects:negativeSpacer, rightBarButtonItem, nil]];
    } else {
        // Just set the UIBarButtonItem as you would normally
        [self setRightBarButtonItem:rightBarButtonItem];
    }
}

@end

And then to get this globally, I have a thin UIViewController subclass that all of my view controllers inherit from.

@interface INFViewController : UIViewController

@end

@implementation INFViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    if (SYSTEM_VERSION_LESS_THAN(@"7.0")) {
        [self setupNavBarForPreIOS7Support];
    }
}

- (void)setupNavBarForPreIOS7Support {
    if (self.navigationController) {
        UINavigationItem *navigationItem = self.navigationItem;
        UIBarButtonItem *leftItem = navigationItem.leftBarButtonItem;
        UIBarButtonItem *rightItem = navigationItem.rightBarButtonItem;

        if (leftItem) {
            [navigationItem addLeftBarButtonItem:leftItem];
        }

        if (rightItem) {
            [navigationItem addRightBarButtonItem:rightItem];
        }
    }
}

@end

I realize that I am checking the OS version twice (once in INFViewController and again in the category), I left it in the category incase I want to use this as a one-off anywhere in the project.

Trenna answered 3/10, 2013 at 21:37 Comment(0)
P
5

for swift you can do this

var negativeSpace:UIBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FixedSpace, target: nil, action: nil)
negativeSpace.width = -17.0
self.navigationItem.rightBarButtonItems = [negativeSpace, requiredButton /* this will be the button which you actually need */]
Plainspoken answered 22/3, 2016 at 4:47 Comment(0)
B
5
As of iOS 11 wont accept negative space width, in order to align the bar button items to the margin, I have used the below code.

 override func viewWillLayoutSubviews() {
                super.viewWillLayoutSubviews()
                for view in (self.navigationController?.navigationBar.subviews)! {
                    view.layoutMargins = UIEdgeInsets.zero
                }
            }
Barboza answered 17/9, 2018 at 14:43 Comment(2)
this method called with delayPutdown
This is the accurate solution to do this, negative space won't work from iOS 11Jarad
M
4

Swift 3:

let negativeSpacer:UIBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.fixedSpace, target: nil, action: nil)
        negativeSpacer.width = -10
self.navigationItem.leftBarButtonItems = [negativeSpacer, yourBarButtonItem]
Metabolite answered 5/3, 2017 at 13:59 Comment(0)
H
2

Swift 3.1

To give left bar button item negative space:

    let backButton = UIButton.init(type: .custom)
    backButton.frame = CGRect.init(x: 0, y: 0, width: 40, height: 40)
    // negative space
    backButton.contentEdgeInsets = UIEdgeInsets(top: 0, left: -44.0, bottom: 0, right: 0)
    backButton.setImage(Ionicons.iosArrowBack.image(30, color: UIColor(hex: 0xFD6250)), for: .normal)
    backButton.addTarget(self, action: #selector(InviteVC.goBack), for: .touchUpInside)   
    // set back button
    self.navigationItem.leftBarButtonIteUIBarButtonItem.init(customView: backButton) 

Hereto answered 4/6, 2017 at 19:51 Comment(0)
J
2

Negetive space won't work from iOS 11

override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()
    // to remove navigation bar extra margin
    for view in (self.navigationController?.navigationBar.subviews)! {
        view.layoutMargins = UIEdgeInsets.zero
    }
}

Above code will remove margin from both side leftBarButtonItem and RightBarButtonItem. If you need to add extra margin(After removing margin) add following code

    let rightButton = UIButton(frame: CGRect(x: 0, y: 0, width: 17, height: 20))
    rightButton.setImage(UIImage(named: "ic_cart"), for: .normal)

    let rightBarButtomItem = UIBarButtonItem(customView: rightButton)

    let spacer = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.fixedSpace, target: nil, action: nil)
    spacer.width = 28 //This will add extra margin on right side
    navigationItem.rightBarButtonItems = [spacer,rightBarButtomItem]
Jarad answered 6/3, 2019 at 12:5 Comment(1)
'Client error attempting to change layout margins of a private view'Violist
O
1

worked for me

rightBarButton.customView?.transform = CGAffineTransform(translationX: 10, y: 0)

Oliverolivera answered 12/7, 2019 at 7:42 Comment(0)
K
0

I believe you need to use a custom button with a UIButton subclass, and in your subclass, override -alignmentRectInsets. I forget whether you need a positive or negative value for the appropriate edge to get it to shift correctly, but if one doesn’t work, try the other.

Koala answered 20/9, 2013 at 13:11 Comment(0)
H
-1

Nice decision, thanks a lot! I needed to add just two elements to the left side of navigation header. This is my solution:

  // Settings Button
  // This trick correct spacing between two left buttons
  UIBarButtonItem *settingsButtonItem = [[UIBarButtonItem alloc] init];
  UIView *settingsButtonItemView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 22.0, 22.0)];
  settingsButtonItem.customView = settingsButtonItemView;
  UIButton *settingsButton = [UIButton buttonWithType:UIButtonTypeSystem];
  settingsButton.frame = settingsButtonItemView.frame;
  [settingsButton setImage:[UIImage imageNamed:@"settings"] forState:UIControlStateNormal];
  settingsButton.tintColor = [[[[UIApplication sharedApplication] delegate] window] tintColor];
  [settingsButton addTarget:self action:@selector(showSettings:) forControlEvents:UIControlEventTouchDown];
  [settingsButtonItemView addSubview:settingsButton];

  // Star Button
  UIBarButtonItem *starButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"star_gray"] style:UIBarButtonItemStyleBordered target:self action:@selector(showStarred)];
  starButtonItem.width = 22.0;
  NSLog(@"%f", starButtonItem.width);

  // Negative Spacer
  // It shifts star button to the left
  UIBarButtonItem *negativeSpacer = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
  negativeSpacer.width = -10.0;

  NSArray *leftNavigtaionBarButtonItems = @[negativeSpacer, starButtonItem, settingsButtonItem];
  [self.navigationItem setLeftBarButtonItems:leftNavigtaionBarButtonItems];
Heteronym answered 29/8, 2014 at 18:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.