86

I've got a custom view inside of a UIBarButtonItem, set by calling -initWithCustomView. My bar button item renders fine, but when I tap it, it doesn't invoke the action on my target object.

Here's my code:

UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"someImage.png"]];
UIBarButtonItem *bbItem = [[UIBarButtonItem alloc] initWithCustomView:imageView];
self.navigationItem.leftBarButtonItem = bbItem;
[imageView release];
[bbItem setTarget:self];
[bbItem setAction:@selector(deselectAll)];
Jacob Relkin
  • 161,348
  • 33
  • 346
  • 320

11 Answers11

131

I do not think the target and action of the UIBarButtonItem apply to custom views. Try using a UIButton instead of UIImageView and applying the target and action to the button.

Sample code in Swift:

let button  = UIButton(type: .Custom)
if let image = UIImage(named:"icon-menu.png") {
    button.setImage(image, forState: .Normal)
}
button.frame = CGRectMake(0.0, 0.0, 30.0, 30.0)
button.addTarget(self, action: #selector(MyClass.myMethod), forControlEvents: .TouchUpInside)
let barButton = UIBarButtonItem(customView: button)
navigationItem.leftBarButtonItem = barButton
Jano
  • 62,815
  • 21
  • 164
  • 192
drawnonward
  • 53,459
  • 16
  • 107
  • 112
  • 31
    Thanks. `UIBarButtonItem` inherits from `UIBarItem` and `NSObject` so it doesn't know anything about touches. It would be nice if the docs mentioned that the `action` and `target` properties only apply if the custom view is a UIButton. – Jason Moore Nov 18 '11 at 15:48
  • 21
    @JasonMoore: better yet, it'd be nice if buttons acted like... buttons. If it behaved the way a programmer expects it to, there's no need for the extra documentation. – GeneralMike Dec 17 '12 at 18:43
  • Thanks so much. Also, we can using IBOutlet references to setTarget and setAction in code. But if we create UIBarButtonItem in code, we can't setTarget and setAction, it will not work. – MOBYTELAB Aug 03 '13 at 14:03
  • 1
    Although this is an old post, I wanted to share this anyway. As I grew tired of having to pay attention to UIBarButtonItem objects having custom views or not, I wrote an extending class which actually behaves the way (I think) it should. You can find the source here: https://gist.github.com/tvervest/8708465 – Thomas Vervest Jan 30 '14 at 13:40
  • heh, I used targetForAction instead of target. – Mathijs Segers Jun 22 '15 at 10:17
24

I had the same problem, but was averse to using a UIButton instead of a custom view for my UIBarButtonItem (per drawnonward's response).

Alternatively, you could add a UIGestureRecognizer to the custom view before using it to initialize UIBarButtonItem; this appears to work in my project.

This is how I would modify your original code:

UIImageView *SOCImageView = [[UIImageView alloc] initWithImage:
                             [UIImage imageNamed:@"cancel_wide.png"]];

UITapGestureRecognizer *tapGesture = 
       [[UITapGestureRecognizer alloc] initWithTarget:self 
                                               action:@selector(deselectAll:)];
[SOCImageView addGestureRecognizer:tapGesture];

SOItem.leftBarButtonItem = 
       [[[UIBarButtonItem alloc] initWithCustomView:SOCImageView] autorelease];
[tapGesture release];
[SOCImageView release];
gerry3
  • 21,420
  • 9
  • 66
  • 74
Tim Arnold
  • 8,359
  • 8
  • 44
  • 67
  • Nothing worked for me, only this solution. Custom view does not allow to set the action. Plain UIButton at the same time is having trouble to display the back ground image. With this gesture recogniser it finally worked out. – Denis Jun 12 '14 at 15:15
  • adding the tapgesture to my custom view did not work, but it did work when i added it to my bar button item. – TheRealRonDez May 07 '15 at 17:51
  • @Ronaldoh1 How did you add UITapgesture to UIBarButtonItem? – Taku Apr 12 '16 at 19:21
  • @user013948 just like you would to any view. UIBarButtonItem subclasses UIView (if im not mistaking). – TheRealRonDez Apr 13 '16 at 15:50
  • @Ronaldoh1 No, its not. https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIBarButtonItem_Class/ – Taku Apr 13 '16 at 15:57
  • Lol 100% impossible to add tapgesture to uibarbutton – Travis Delly Jul 17 '18 at 20:33
24

Here is how I make it work:

UIButton* infoButton = [UIButton buttonWithType: UIButtonTypeInfoLight];
[infoButton addTarget:self action:@selector(displayAboutUs) forControlEvents:UIControlEventTouchDown];

UIBarButtonItem* itemAboutUs =[[UIBarButtonItem alloc]initWithCustomView:infoButton];
…
Forge
  • 6,538
  • 6
  • 44
  • 64
Nicolas Lauquin
  • 1,517
  • 3
  • 14
  • 23
9

Here's how I implemented the UIButton inside of the UIBarButtonItem:

UIButton *logoButton = [UIButton buttonWithType:UIButtonTypeCustom];
[logoButton setImage: [UIImage imageNamed:@"icon.png"] forState:UIControlStateNormal];
logoButton.frame = CGRectMake(0, 0, 30, 30);
[logoButton addTarget:self action:@selector(showAbout:) forControlEvents:UIControlEventTouchUpInside];
UIBarButtonItem *barItem = [[UIBarButtonItem alloc] initWithCustomView:logoButton];
self.navigationItem.rightBarButtonItem = barItem;
[barItem release];
Zac Witte
  • 135
  • 2
  • 5
7

I had a similar problem. And I initially followed the path suggested by @drawnonward, but then ran into trouble when I tried to have my action present a popover controller on an iPad: Using an embedded UIButton as a custom view means the UIButton is the sender of the event, and the popover controller’s presentPopoverFromBarButtonItem: method crashes when it tries to send it messages which are only appropriate to actual UIBarButtonItems.

The solution I eventually found was to steal the image I wanted to use (the “info” icon) from a throwaway UIButton, and construct my UIBarButtonItem as follows:

// Make the info button use the standard icon and hook it up to work
UIButton *infoButton = [UIButton buttonWithType:UIButtonTypeInfoLight];
UIBarButtonItem *barButton = [[[UIBarButtonItem alloc]        
      initWithImage:infoButton.currentImage
              style:UIBarButtonItemStyleBordered
             target:self
             action:@selector(showInfo:)] autorelease];

Using this initializer yields a bar button whose target and selector actually work. It is also easier than wrapping the image in a custom view, but that is just icing.

James Elliott
  • 694
  • 8
  • 14
  • 1
    This solved it for me! For use with a popover, use this: [myPopover presentPopoverFromBarButtonItem: sender permittedArrowDirections: UIPopoverArrowDirectionAny animated: YES]; – mpemburn Jun 08 '12 at 22:04
3

If you do not want to settle with an UIImage and have a custom view in your barbuttonitem, set a tap gesture recognizer to your barbuttonitem's customview property

let tap = UITapGestureRecognizer(target: self, action: #selector(goProButtonPressed)) deviceStatusBarButtonItem.customView?.addGestureRecognizer(tap)

Leo
  • 311
  • 3
  • 5
2

Swift 3. I had a similar issue where I had a custom view inside of the bar button item. In order to get the tap to work I assigned a gesture to the view and set the action. The view needed to be an outlet to assign to it. Doing this it worked with the custom view.

Setting the gesture as class variable

@IBOutlet weak var incidentView: UIView!
let incidentTap = UITapGestureRecognizer()

In viewDidLoad

incidentTap.addTarget(self, action: #selector(self.changeIncidentBarItem))
incidentView.addGestureRecognizer(incidentTap)
Micah Montoya
  • 757
  • 1
  • 8
  • 24
1

Jacob, everything looks good, however you may not have provided the correct selector.

Can you verify that your action is actually declared

- (void) deselectAll;

and not

- (void) deselectAll:(id)sender;

If it's the latter, you will need to set the action to @selector(deselectAll:). (note the semi-colon to match the method declaration)

Also, void might be IBAction, but that's not relevant to this problem you're having.

ohhorob
  • 11,695
  • 7
  • 41
  • 50
1

Swift 3 : Below is my full implementation for button customization and event handling.

override func viewDidLoad() {
    super.viewDidLoad()

    let button = UIButton.init(type: .custom)
    button.setTitle("Tester", for: .normal)
    button.setTitleColor(.darkGray, for: .normal)
    button.layer.borderWidth = 1
    button.layer.cornerRadius = 5
    button.layer.borderColor = UIColor.darkGray.cgColor
    button.addTarget(self, action: #selector(self.handleButton), for: .touchUpInside)

    self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: button)
}

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    if let button = self.navigationItem.rightBarButtonItem?.customView {
        button.frame = CGRect(x:0, y:0, width:80, height:34)
    }
}

func handleButton( sender : UIButton ) {
    // It would be nice is isEnabled worked...
    sender.alpha = sender.alpha == 1.0 ? 0.5 : 1.0
}

Hope this helps

Joe Andolina
  • 648
  • 1
  • 11
  • 23
0

To make this easy to work with, I created a category on UIBarButtonItem which looks like this:

@implementation UIBarButtonItem (CustomButtonView)

- (void)setButtonImage:(UIImage *)image
{
    UIButton * button = [UIButton buttonWithType:UIButtonTypeCustom];
    [button setBackgroundImage:image forState:UIControlStateNormal];
    [button sizeToFit];
    [button addTarget:self.target action:self.action forControlEvents:UIControlEventTouchUpInside];
    self.customView = button;
}

- (UIImage *)buttonImage
{
    return [(UIButton *)self.customView imageForState:UIControlStateNormal];
}

@end

In your client code, simply use:

myBarButtonItem.buttonImage = [UIImage imagedNamed:@"image_name"];

Done this way you can still hook up your targets and actions in IB (pushing as much UI config into IB as you can is a Good Thing).

Tylerc230
  • 2,883
  • 1
  • 27
  • 31
0

Is your custom view eating touch events or passing them on to parent view?

Alex Reynolds
  • 95,983
  • 54
  • 240
  • 345