4

I'm creating a barButton which when pressed should set the editing mode of a UITableView to yes. Here's my code:

self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle: @"Edit"
                                                                             style: self.navigationController.navigationItem.leftBarButtonItem.style
                                                                            target: self
                                                                             action: ];

What I don't understand is what I need to put as an argument for the action part so that I can execute a code block there. I could easily enough put @selector(someMethod) but I'm only executing one or two lines and creating another method is pretty pointless.

Thanks for any help!

prince
  • 506
  • 6
  • 18

2 Answers2

8

Further to pgb's comment, writing something like this would solve the problem:

@interface PJBlockHolder

+ (id)blockHolderWithBlock:(dispatch_block_t)block;
- (void)invoke;

@end

/* obvious implementation; copy the block, issue it upon invoke */

And:

[[UIBarButtonItem alloc] initWithTitle: @"Edit"
    style: self.navigationController.navigationItem.leftBarButtonItem.style
    target: [PJBlockHolderWithBlock:^{ /* your code here */ }]
    action:@selector(invoke) ];

So you've created a custom object that wraps a block and issues it upon a particular selector.

EDIT: as noted below, UIControls don't retain their targets. So probably the easiest thing is to tie the lifetime of the block holder to the lifetime of the control; that's not necessarily ideal because then the holder will outlive its usefulness if you subsequently remove it as a target while keeping the control alive, but it's probably suitable for the majority of cases.

Options are either to use Objective-C's built in associated objects, or to use the fact that UIControl inherits from UIView, giving it a CALayer, which can store arbitrary keyed objects.

Justin Spahr-Summers links to a well documented, public domain implementation of the former in his comment below so I'll show an example of the latter, even though it's hacky, for the purposes of discussion.

PJBlockHolderWithBlock *blockHolder = [PJBlockHolderWithBlock:^{ /* your code here */ }];
UIBarButtonItem *barButtonItem =
    [[UIBarButtonItem alloc] initWithTitle: @"Edit"
        style: self.navigationController.navigationItem.leftBarButtonItem.style
        target: blockHolder
        action:@selector(invoke) ];
[barButtonItem.layer setValue:blockHolder forKey:@"__myBlockHolderKey__"];
Tommy
  • 99,986
  • 12
  • 185
  • 204
  • 1
    This introduces some memory management complexities, though. Nonetheless, I have a [public domain implementation](https://github.com/jspahrsummers/libextobjc/blob/master/extobjc/EXTBlockTarget.h) of something similar. – Justin Spahr-Summers Jul 03 '12 at 20:24
  • 3
    @JustinSpahr-Summers you're right; I had of course neglected to recall that `UIControl`s don't retain their targets, which was stupid because it would obviously cause retain loops. Your `objc_setAssociatedObject` solution is exactly what I would have done myself. – Tommy Jul 03 '12 at 20:28
  • Thanks for the help, but it seems as if this is much more complex than I was expecting, I'll probably just end up calling the method haha! – prince Jul 03 '12 at 21:01
2

You can't do it as you intend. The target: action: parameters are intended to send an object and a selector to be called on that object. As far as I know, there's no equivalent API that uses blocks.

pgb
  • 24,813
  • 12
  • 83
  • 113