46

I'm experiencing excessive UIBarButtonItem padding/spacing when using the LeftBarItems and RightBarItems (see image below). The icons used on the UIBarButtonItems do not contain extra padding. So I would like to know what's causing this?

enter image description here

Rob
  • 4,927
  • 12
  • 49
  • 54
Oliver Weichhold
  • 10,259
  • 5
  • 45
  • 87

6 Answers6

27

I use this in order to remove space before the first item.

However it doesn't work between system items like UIBarButtonSystemItemAdd, only with UIBarButtonItem that has an image.

@interface UIBarButtonItem (NegativeSpacer)
+(UIBarButtonItem*)negativeSpacerWithWidth:(NSInteger)width;
@end
@implementation UIBarButtonItem (NegativeSpacer)
+(UIBarButtonItem*)negativeSpacerWithWidth:(NSInteger)width {
    UIBarButtonItem *item = [[UIBarButtonItem alloc]
                             initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace
                             target:nil
                             action:nil];
    item.width = (width >= 0 ? -width : width);
    return item;
}
@end

Use it like this:


UIBarButtonItem *item0 = [UIBarButtonItem negativeSpacerWithWidth:13];
UIBarButtonItem *item1 = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:@"sidebar.png"]
                                                          style:UIBarButtonItemStylePlain
                                                         target:vc
                                                         action:@selector(sideMenuAction:)];
NSArray* items = @[item0, item1];
[vc.navigationItem setLeftBarButtonItems:items animated:NO];
[vc.navigationItem setLeftItemsSupplementBackButton:YES];
Quinn Taylor
  • 44,553
  • 16
  • 113
  • 131
neoneye
  • 50,398
  • 25
  • 166
  • 151
  • Thanks that's a start. But given that even the facebook app devs had to resort to introduce a new bottom toolbar hosting items that used to reside in the navigation bar, I think this is going to stay. – Oliver Weichhold Sep 21 '13 at 13:57
  • Changed name to -negativeSpacerWithWidth: and made it handle negative values correctly. – Quinn Taylor Oct 09 '13 at 17:52
  • 6
    This isn't working for me... Has this changed recently (i.e. iOS 7)? – Stian Høiland Nov 09 '13 at 22:56
  • Works for me in iOS 7 on a UIToolbar. – Danyal Aytekin Nov 12 '13 at 20:55
  • 1
    Works for me on iOS7 UINavigationBar. `[vc.navigationItem setLeftItemsSupplementBackButton:YES];' is not necessary. – jAckOdE Jan 20 '14 at 04:11
  • 1
    But it seem doesnt work with right item, here is how i did with right item:`UIBarButtonItem *rightSpacer = [UIBarButtonItem negativeSpacerWithWidth:13];[self.navigationItem setRightBarButtonItems:[NSArray arrayWithObjects:rightItem,rightSpacer, nil]];` – jAckOdE Jan 20 '14 at 05:26
  • It works with the right. It seems like the spacer should be listed second, but... that's not how it works. If you list the spacer first in the right array it will work. – jaime Aug 20 '14 at 16:21
23

You can move the image

self.myBarButtonItem.imageInsets = UIEdgeInsetsMake(0, 25, 0, -25);
Luda
  • 7,282
  • 12
  • 79
  • 139
  • 1
    Unfortunately this does not move the hit area of the button, only its image. But a good solution if you just need to move the button a little bit! – andrrs Apr 30 '15 at 08:02
8

Apple silently increased the horizontal spacing constraints for UIBarButtonItems and sadly, still hasn't added any UIAppearance methods to adjust the horizontal positioning of UIBarButtonItems.

The best solution (which worked for me) is to wrap your UIBarButtonItems in a UIView using initWithCustomView: and adjust the bounds of that custom view to get your desired positioning. Here's a good answer on how to do this.

If you want to take things a step further, you can create a category on UIBarButtonItem with class methods that return the bar buttons you use throughout your app. That way, when you a need a bar button, you can call something like:

self.navigationItem.leftBarButtonItem = [UIBarButtonItem mySearchBarButtonItemWithTarget:self selector:@selector(search)];
Community
  • 1
  • 1
Wes Dearborn
  • 336
  • 1
  • 6
7

There are two kinds of button on the navigation bar in iOS 7: button with image and button with text. I wrote a class to do it. Here is how:

GlobalUICommon.h:

@interface UIBarButtonItem(CustomUIOfONE)

+ (UIBarButtonItem*)barItemWithImage:(UIImage*)image highlightedImage:(UIImage*)highlightedImage xOffset:(NSInteger)xOffset target:(id)target action:(SEL)action;

+ (UIBarButtonItem*)barItemWithTitle:(NSString*)title xOffset:(NSInteger)xOffset target:(id)target action:(SEL)action;

@end

GlobalUICommon.m:

@implementation UIBarButtonItem(CustomUIOfONE)

+ (UIBarButtonItem*)barItemWithImage:(UIImage*)image highlightedImage:(UIImage*)highlightedImage xOffset:(NSInteger)xOffset target:(id)target action:(SEL)action
{
    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    [button setFrame:CGRectMake(0, 0, image.size.width, image.size.height)];
    [button addTarget:target action:action forControlEvents:UIControlEventTouchUpInside];
    [button setImage:image forState:UIControlStateNormal];
    [button setImage:highlightedImage forState:UIControlStateHighlighted];
    if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7) {
        [button setImageEdgeInsets:UIEdgeInsetsMake(0, xOffset, 0, -xOffset)];
    }

    UIBarButtonItem *customUIBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:button];
    return customUIBarButtonItem;
}

+ (UIBarButtonItem*)barItemWithTitle:(NSString*)title xOffset:(NSInteger)xOffset target:(id)target action:(SEL)action
{
    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    [button setTitle:title forState:UIControlStateNormal];
    [button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
    [button setTitleColor:[UIColor redColor] forState:UIControlStateHighlighted];
    [button.titleLabel setFont:[UIFont systemFontOfSize:15]];
    [button setFrame:CGRectMake(0, 0, [button.titleLabel.text sizeWithFont:button.titleLabel.font].width + 3, 24)];
    [button addTarget:target action:action forControlEvents:UIControlEventTouchUpInside];
    if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7) {
        [button setContentEdgeInsets:UIEdgeInsetsMake(0, xOffset, 0, -xOffset)];
    }

    UIBarButtonItem *customUIBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:button];
    return customUIBarButtonItem;
}

@end

YourViewController.m:

Example for Button with Image:

UIBarButtonItem* leftButtomItem = [UIBarButtonItem barItemWithImage:[UIImage imageNamed:@"yourImage"]
                                                   highlightedImage:[UIImage imageNamed:@"yourImage"]
                                                            xOffset:-11
                                                             target:self
                                                             action:@selector(yourHandler)];
self.navigationItem.leftBarButtonItem = leftButtomItem;
UIBarButtonItem* rightButtonItem = [UIBarButtonItem barItemWithImage:[UIImage imageNamed:@"yourImage"]
                                                   highlightedImage:[UIImage imageNamed:@"yourImage"]
                                                            xOffset:11
                                                             target:self
                                                             action:@selector(yourHandler)];
self.navigationItem.rightBarButtonItem = rightButtonItem;

Example for Button with Text:

self.navigationItem.leftBarButtonItem = [UIBarButtonItem barItemWithTitle:@"yourText" xOffset:-11 target:self action:@selector(yourHandler:)];
self.navigationItem.rightBarButtonItem = [UIBarButtonItem barItemWithTitle:@"yourText" xOffset:11 target:self action:@selector(yourHandler:)];

That's it.

itisalex
  • 71
  • 1
  • 2
2

I solved this by using storybord interface.

1.Select the Bar item.

2.Select the Size Inspector.

Here you can find image Inset,using top,bottom AND left , right you can change the position of Bar Item.

jithin
  • 459
  • 8
  • 21
0

As @Luda comments, the solution is to

self.myBarButtonItem.imageInsets = UIEdgeInsetsMake(0, 25, 0, -25);

However, @andrrs also points a problem here when inset is large: the hit area. In this case,we have to implement a way to setHitTestEdgeInsets. Below is an category method:

@implementation UIButton (Extensions)

@dynamic hitTestEdgeInsets;

static const NSString *KEY_HIT_TEST_EDGE_INSETS = @"HitTestEdgeInsets";

-(void)setHitTestEdgeInsets:(UIEdgeInsets)hitTestEdgeInsets {
    NSValue *value = [NSValue value:&hitTestEdgeInsets withObjCType:@encode(UIEdgeInsets)];
    objc_setAssociatedObject(self, &KEY_HIT_TEST_EDGE_INSETS, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

-(UIEdgeInsets)hitTestEdgeInsets {
    NSValue *value = objc_getAssociatedObject(self, &KEY_HIT_TEST_EDGE_INSETS);
    if(value) {
        UIEdgeInsets edgeInsets; [value getValue:&edgeInsets]; return edgeInsets;
    }else {
        return UIEdgeInsetsZero;
    }
}

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    if(UIEdgeInsetsEqualToEdgeInsets(self.hitTestEdgeInsets, UIEdgeInsetsZero) ||       !self.enabled || self.hidden) {
        return [super pointInside:point withEvent:event];
    }

    CGRect relativeFrame = self.bounds;
    CGRect hitFrame = UIEdgeInsetsInsetRect(relativeFrame, self.hitTestEdgeInsets);

    return CGRectContainsPoint(hitFrame, point);
}

@end
LiangWang
  • 8,038
  • 8
  • 41
  • 54