UIBarButtonItem: target-action not working?

IphoneIosObjective CCocoa Touch

Iphone Problem Overview


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)];

Iphone Solutions


Solution 1 - Iphone

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

Solution 2 - Iphone

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];

Solution 3 - Iphone

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];

Solution 4 - Iphone

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];

Solution 5 - Iphone

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.

Solution 6 - Iphone

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)

Solution 7 - Iphone

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

deviceStatusBarButtonItem.customView?.addGestureRecognizer(tap)

Solution 8 - Iphone

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.

Solution 9 - Iphone

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

Solution 10 - Iphone

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

Solution 11 - Iphone

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).

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJacob RelkinView Question on Stackoverflow
Solution 1 - IphonedrawnonwardView Answer on Stackoverflow
Solution 2 - IphoneNicolas LauquinView Answer on Stackoverflow
Solution 3 - IphoneTim ArnoldView Answer on Stackoverflow
Solution 4 - IphoneZac WitteView Answer on Stackoverflow
Solution 5 - IphoneJames ElliottView Answer on Stackoverflow
Solution 6 - IphoneMicah MontoyaView Answer on Stackoverflow
Solution 7 - IphoneLeoView Answer on Stackoverflow
Solution 8 - IphoneohhorobView Answer on Stackoverflow
Solution 9 - IphoneJoe AndolinaView Answer on Stackoverflow
Solution 10 - IphoneAlex ReynoldsView Answer on Stackoverflow
Solution 11 - IphoneTylerc230View Answer on Stackoverflow