UIBarButtonItem with UIButton as CustomView - from UIButton, how to access the UIBarButtonItem its in?

15,029

Solution 1

If you have set your UIButton as the customView of a UIBarButtonItem, or a child of the custom view, then you can walk up the view hierarchy from your button until you find the UIToolbar or UINavigationBar that contains the button, then search the bar's items for the one whose custom view is the button (or an ancestor of the button).

Here's my completely untested code for doing that. You would call [[self class] barButtonItemForView:myButton] to get the item containing your button.

+ (BOOL)ifBarButtonItem:(UIBarButtonItem *)item containsView:(UIView *)view storeItem:(UIBarButtonItem **)outItem {
    UIView *customView = item.customView;
    if (customView && [view isDescendantOfView:customView]) {
        *outItem = item;
        return YES;
    } else {
        return NO;
    }
}

+ (BOOL)searchBarButtonItems:(NSArray *)items forView:(UIView *)view storeItem:(UIBarButtonItem **)outItem {
    for (UIBarButtonItem *item in items) {
        if ([self ifBarButtonItem:item containsView:view storeItem:outItem])
            return YES;
    }
    return NO;
}

+ (UIBarButtonItem *)barButtonItemForView:(UIView *)view {
    id bar = view;
    while (bar && !([bar isKindOfClass:[UIToolbar class]] || [bar isKindOfClass:[UINavigationBar class]])) {
        bar = [bar superview];
    }
    if (!bar)
        return nil;

    UIBarButtonItem *item = nil;

    if ([bar isKindOfClass:[UIToolbar class]]) {
        [self searchBarButtonItems:[bar items] forView:view storeItem:&item];
    }

    else {
        UINavigationItem *navItem = [bar topItem];
        if (!navItem)
            return nil;
        [self ifBarButtonItem:navItem.backBarButtonItem containsView:view storeItem:&item]
        || [self ifBarButtonItem:navItem.leftBarButtonItem containsView:view storeItem:&item]
        || [self ifBarButtonItem:navItem.rightBarButtonItem containsView:view storeItem:&item]
        || ([navItem respondsToSelector:@selector(leftBarButtonItems)]
            && [self searchBarButtonItems:[(id)navItem leftBarButtonItems] forView:view storeItem:&item])
        || ([navItem respondsToSelector:@selector(rightBarButtonItems)]
            && [self searchBarButtonItems:[(id)navItem rightBarButtonItems] forView:view storeItem:&item]);
    }

    return item;
}

Solution 2

First of all, why do you need to access the UIBarButtonItem?

Then, to create a UIBarButtonItem with a custom view I suggest you to create a category extension like the following (in this case the custom view is a UIButton).

//UIBarButtonItem+Extension.h    
+ (UIBarButtonItem*)barItemWithImage:(UIImage*)image title:(NSString*)title target:(id)target action:(SEL)action;

//UIBarButtonItem+Extension.m    
+ (UIBarButtonItem*)barItemWithImage:(UIImage*)image title:(NSString*)title target:(id)target action:(SEL)action
{
    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    button.frame = CGRectMake(0.0, 0.0, image.size.width, image.size.height);
    button.titleLabel.textAlignment = UITextAlignmentCenter;

    [button setBackgroundImage:image forState:UIControlStateNormal];
    [button setTitle:title forState:UIControlStateNormal];
    [button addTarget:target action:action forControlEvents:UIControlEventTouchUpInside];

    UIBarButtonItem* barButtonItem = [[UIBarButtonItem alloc] initWithCustomView:button];

    return [barButtonItem autorelease];    
}

and the use it like this (first import UIBarButtonItem+Extension.h):

UIBarButtonItem* backBarButtonItem = [UIBarButtonItem barItemWithImage:[UIImage imageNamed:@"YoutImageName"] title:@"YourTitle" target:self action:@selector(doSomething:)];

where the selector it's a method that is implemented inside the class (the target) that uses that bar button item.

Now inside the doSomething: selector you could the following:

- (void)doSomething:(id)sender

    UIButton* senderButton = (UIButton*)sender;    
    [popover presentPopoverFromRect:senderButton.bounds inView:senderButton...];
}

A category in this case it's useful to not duplicate code. Hope it helps.

P.S. I'm not using ARC but same considerations can be also applied.

Edit:

An easy solution to access the bar button item could be the following:

  1. Subclass a UIButton
  2. Create within it a property of type UIBarButtonItem (a weak one to avoid retaincycle)
  3. Inject the bar button item in the UIButton when you create it.

Maybe it could work, but I will prefer the first one.

Share:
15,029
scooter133
Author by

scooter133

I guy trying to muddle his way through creating a iPad application. Hoping to remove his mortgage from the list of debts.

Updated on June 08, 2022

Comments

  • scooter133
    scooter133 almost 2 years

    I have a UIBarButtonItem with UIButton as custom view.

    The UIButton has a addTarget:action: on it. In the action I present a popover. I'm currently presenting from the sender.frame (the UIButton's Frame) I want to Present from the UIBarButtonItem instead.

    How can I access the UIBarButtonItem from the UIButton?