18

So I am having the same problem that many others are experiencing when creating a UIBarButtonItem with a UIButton as a custom view.

Basically the button is about 10 pixel to far either left or right. When I use a regular BarButtonItem without a custom view, this does not happen.

This post provided a partial solution: UIBarButton With Custom View

Here is my code I have created by subclassing UIButton (as stated in the other post)

    - (UIEdgeInsets)alignmentRectInsets {
    UIEdgeInsets insets;
    if ([self isLeftButton]) {
        insets = UIEdgeInsetsMake(0, 9.0f, 0, 0);
    }
    else { // IF ITS A RIGHT BUTTON
        insets = UIEdgeInsetsMake(0, 0, 0, 9.0f);
    }
    return insets;
}


- (BOOL)isLeftButton {
    return self.frame.origin.x < (self.superview.frame.size.width / 2);
}

This works great, but when I pop a view controller from the navigation controller back to this main view, the button is still incorrectly positioned for about .3 seconds, and then it snaps into the correct inset.

This is a HUGE eyesore and I have no idea how to stop it from snapping like so. Any thoughts? Thanks!

Community
  • 1
  • 1
Kyle Begeman
  • 7,169
  • 9
  • 40
  • 58

4 Answers4

53

I had the same problem like you and many other. After long time trying to fix it, finally I did it. This is the category you have to include in your *-Prefix.pch file. And that's all!

UINavigationItem+iOS7Spacing.h

#import <Foundation/Foundation.h>
@interface UINavigationItem (iOS7Spacing)
@end

UINavigationItem+iOS7Spacing.m

#import "UINavigationItem+iOS7Spacing.h"
#import <objc/runtime.h>

@implementation UINavigationItem (iOS7Spacing)

- (BOOL)isIOS7
{
    return ([[[UIDevice currentDevice] systemVersion] compare:@"7" options:NSNumericSearch] != NSOrderedAscending);
}

- (UIBarButtonItem *)spacer
{
    UIBarButtonItem *space = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
    space.width = -11;
    return space;
}

- (void)mk_setLeftBarButtonItem:(UIBarButtonItem *)leftBarButtonItem
{
    if ([self isIOS7] && leftBarButtonItem) {
        [self mk_setLeftBarButtonItem:nil];
        [self mk_setLeftBarButtonItems:@[[self spacer], leftBarButtonItem]];
    } else {
        [self mk_setLeftBarButtonItem:leftBarButtonItem];
    }
}

- (void)mk_setLeftBarButtonItems:(NSArray *)leftBarButtonItems
{
    if ([self isIOS7] && leftBarButtonItems && leftBarButtonItems.count > 0) {

        NSMutableArray *items = [[NSMutableArray alloc] initWithCapacity:leftBarButtonItems.count + 1];
        [items addObject:[self spacer]];
        [items addObjectsFromArray:leftBarButtonItems];

        [self mk_setLeftBarButtonItems:items];
    } else {
        [self mk_setLeftBarButtonItems:leftBarButtonItems];
    }
}

- (void)mk_setRightBarButtonItem:(UIBarButtonItem *)rightBarButtonItem
{
    if ([self isIOS7] && rightBarButtonItem) {
        [self mk_setRightBarButtonItem:nil];
        [self mk_setRightBarButtonItems:@[[self spacer], rightBarButtonItem]];
    } else {
        [self mk_setRightBarButtonItem:rightBarButtonItem];
    }
}

- (void)mk_setRightBarButtonItems:(NSArray *)rightBarButtonItems
{
    if ([self isIOS7] && rightBarButtonItems && rightBarButtonItems.count > 0) {

        NSMutableArray *items = [[NSMutableArray alloc] initWithCapacity:rightBarButtonItems.count + 1];
        [items addObject:[self spacer]];
        [items addObjectsFromArray:rightBarButtonItems];

        [self mk_setRightBarButtonItems:items];
    } else {
        [self mk_setRightBarButtonItems:rightBarButtonItems];
    }
}

+ (void)mk_swizzle:(SEL)aSelector
{
    SEL bSelector = NSSelectorFromString([NSString stringWithFormat:@"mk_%@", NSStringFromSelector(aSelector)]);

    Method m1 = class_getInstanceMethod(self, aSelector);
    Method m2 = class_getInstanceMethod(self, bSelector);

    method_exchangeImplementations(m1, m2);
}

+ (void)load
{
    [self mk_swizzle:@selector(setLeftBarButtonItem:)];
    [self mk_swizzle:@selector(setLeftBarButtonItems:)];
    [self mk_swizzle:@selector(setRightBarButtonItem:)];
    [self mk_swizzle:@selector(setRightBarButtonItems:)];
}

@end

UINavigationItem+iOS7Spacing category on GitHub

Marius Kažemėkaitis
  • 1,723
  • 19
  • 22
  • I think this is best solution,better than change UIEdegeInset,this has no tap area problem – Nick Oct 14 '13 at 09:06
  • Your category works very good. But I seem to have a problem. Correct spacer width for me is -15, and also I need an -4 width spacer for pre-iOS 7 as well. Do you have any idea about the 4 pt difference? – Bartu Nov 06 '13 at 15:02
  • I have no idea why correct spacer for you is -15. I tested this category on many projects and -11 worked perfect on all of them. – Marius Kažemėkaitis Nov 07 '13 at 16:56
  • 3
    You should actually check if the barButtonItem has a custom view or not as the alignment is messed up only when there is a custom view. Otherwise, putting a spacer in all cases causes normal UIBarButtonItems to appear offscreen. ie: if ([self isIOS7] && leftBarButtonItem.customView) – droussel Nov 08 '13 at 16:05
  • Insets in may cases (also in UITextView) seem to be all weird compared to how they were in iOS 6. – NovaJoe Nov 09 '13 at 19:40
  • If i use the solution above the spaces of my custom buttons disappear. Great! But i can´t reference the navigationItem of the parentViewController anymore. self.categoryThemesTVC.navigationItem.rightBarButtonItem = self.parentViewController.navigationItem.rightBarButtonItem; Did someone run into this issue as well? – rockstarberlin Nov 27 '13 at 15:58
  • Because with this category, your rightBarButtonItem is set to nil. You should use rightBarButtonItems. This is an array of spacer and your rightBarButtonItem. – Marius Kažemėkaitis Nov 28 '13 at 08:46
  • I'm facing this issue but in my case, the left button moves UP, not left or right. I have a status bar up there, it overlays with it for .3 second and moves back. This causes a big eyescore. Any suggestions? – alper_k Dec 30 '13 at 15:56
  • Genius and nothing more. – ytpm Apr 13 '14 at 08:13
  • Respect, great stuff. – Don Miguel Jun 09 '14 at 14:25
4

Not a huge fan of subclassing UIButton or method swizzling from Marius's answer: https://stackoverflow.com/a/19317105/287403

I just used a simple wrapper, and moved the button's frame's x in a negative direction until I found the correct positioning. Button tapping appears to be fine as well (although you could extend the width of the button to match the negative offset if you needed).

Here's the code I use to generate a new back button:

- (UIBarButtonItem *) newBackButton {
    // a macro for the weak strong dance
    @weakify(self);
    UIButton *backButton = [[UIButton alloc] init];
    [backButton setTitle:@"Back" forState:UIControlStateNormal];
    backButton.titleLabel.font = [UIFont systemFontOfSize:17];
    CGFloat width = [@"Back" boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX) options:NSStringDrawingUsesFontLeading|NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:17]} context:nil].size.width + 27;
    backButton.frame = CGRectMake(-17.5, 0, width + 17.5, 44);
    [backButton setImage:[UIImage imageNamed:@"UINavigationBarBackIndicatorDefault"] forState:UIControlStateNormal];
    [backButton setTitleEdgeInsets:UIEdgeInsetsMake(0, 14, 0, 0)];
    [backButton setTitleColor:mTCOrangeColor forState:UIControlStateNormal];
    backButton.contentEdgeInsets = UIEdgeInsetsZero;
    backButton.imageEdgeInsets = UIEdgeInsetsZero;
    [backButton addEventHandler:^(id sender) {
        // a macro for the weak strong dance
        @strongify(self);
        // do stuff here
    } forControlEvents:UIControlEventTouchUpInside];
    UIView *buttonWrapper = [[UIView alloc] initWithFrame:CGRectMake(0, 0, width, 44)];
    [buttonWrapper addSubview:backButton];
    return [[UIBarButtonItem alloc] initWithCustomView:buttonWrapper];
}
Community
  • 1
  • 1
Bob Spryn
  • 17,742
  • 12
  • 68
  • 91
  • This the only solution that worked for me, and it will be the solution for each one that using UIButton as subview (+1) – Yossi Jan 30 '14 at 15:37
0

I have not tried this code for multiple navigationBarButton items, but it seems to be working for single buttons. I have overriden UINavigationBar and its layoutSubviews method.

First, a constant for horizontal offset is defined at the top of the file. Modify is as you wish:

static CGFloat const kNavBarButtonHorizontalOffset = 10;

layoutSubviews:

- (void)layoutSubviews{

    [super layoutSubviews];

    //Do nothing on iOS6
    if ( ![[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0f){return;}

    UINavigationItem * navigationItem = [self topItem];

    for(UIBarButtonItem * item in [navigationItem rightBarButtonItems]){

        UIView * subview = [item customView];
        CGFloat width = CGRectGetWidth(subview.frame);

        CGRect newRightButtonRect = CGRectMake(CGRectGetWidth(self.frame) - width - kNavBarButtonHorizontalOffset,
                                           CGRectGetMinY(subview.frame),
                                           width,
                                           CGRectGetHeight(subview.frame));
        [subview setFrame:newRightButtonRect];

    }

    for(UIBarButtonItem * item in [navigationItem leftBarButtonItems]){
        UIView * subview = [item customView];
        CGRect newRightButtonRect = CGRectMake(kNavBarButtonHorizontalOffset,
                                           CGRectGetMinY(subview.frame),
                                           CGRectGetWidth(subview.frame),
                                           CGRectGetHeight(subview.frame));

        [subview setFrame:newRightButtonRect];
    }
}
Yunus Nedim Mehel
  • 12,089
  • 4
  • 50
  • 56
0

Alternatively, you can adjust the frames of the contents of your custom view (-5 for the left subviews, +5 for the right subviews), and as long as your custom view does not have clipsToBounds set to YES then those will be honored. It's not the cleanest, but other suggested solutions can be even more hacky IMO.

The subviews will appear cutoff by 5 points in IB, but the entire contents show when you run the app in the simulator.

Kyle Clegg
  • 38,547
  • 26
  • 130
  • 141