UIButton block equivalent to addTarget:action:forControlEvents: method?
Solution 1
I just implemented this. It work's like a charm!
And it wasn't even hard.
typedef void (^ActionBlock)();
@interface UIBlockButton : UIButton {
ActionBlock _actionBlock;
}
-(void) handleControlEvent:(UIControlEvents)event
withBlock:(ActionBlock) action;
@end
@implementation UIBlockButton
-(void) handleControlEvent:(UIControlEvents)event
withBlock:(ActionBlock) action
{
_actionBlock = action;
[self addTarget:self action:@selector(callActionBlock:) forControlEvents:event];
}
-(void) callActionBlock:(id)sender{
_actionBlock();
}
@end
Solution 2
There is a library of blocks additions to the common Foundation/UI classes: BlocksKit. Here is the documentation.
It does not subclass UIButton, but adds UIControl category:
[button addEventHandler:^(id sender) {
//do something
} forControlEvents:UIControlEventTouchUpInside];
There is also blocks/functional additions to collections (map, filter, etc), views-related stuff and more.
NOTE: it does not play well with Swift.
Solution 3
Here's a working category implementation. In it's current form, this should only be used in DEBUG
. I use this category in conjunction with a function (included below) to test various bits of code when user interaction and timing are important. Again this is only for development/debug purposes and shouldn't be considered for production, hence the #ifdef DEBUG
;)
#ifdef DEBUG
#import <objc/runtime.h>
static char UIButtonBlockKey;
@interface UIButton (UIBlockButton)
- (void)handleControlEvent:(UIControlEvents)event withBlock:(ActionBlock)block;
- (void)callActionBlock:(id)sender;
@end
@implementation UIButton (UIBlockButton)
- (void)handleControlEvent:(UIControlEvents)event withBlock:(ActionBlock)block {
objc_setAssociatedObject(self, &UIButtonBlockKey, block, OBJC_ASSOCIATION_COPY_NONATOMIC);
[self addTarget:self action:@selector(callActionBlock:) forControlEvents:event];
}
- (void)callActionBlock:(id)sender {
ActionBlock block = (ActionBlock)objc_getAssociatedObject(self, &UIButtonBlockKey);
if (block) {
block();
}
}
@end
void DSAddGlobalButton(NSString *title, ActionBlock block) {
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[button setTitle:title forState:UIControlStateNormal];
[button handleControlEvent:UIControlEventTouchUpInside withBlock:block];
[button sizeToFit];
[button setFrame:(CGRect){{100.0f, 100.0f}, [button frame].size}];
UIView *firstView = [[[[UIApplication sharedApplication] keyWindow] subviews] objectAtIndex:0];
[firstView addSubview:button];
}
#endif
Solution 4
Swift 4
class ClosureSleeve {
let closure: () -> ()
init(attachTo: AnyObject, closure: @escaping () -> ()) {
self.closure = closure
objc_setAssociatedObject(attachTo, "[\(arc4random())]", self, .OBJC_ASSOCIATION_RETAIN)
}
@objc func invoke() {
closure()
}
}
extension UIControl {
func addAction(for controlEvents: UIControlEvents = .primaryActionTriggered, action: @escaping () -> ()) {
let sleeve = ClosureSleeve(attachTo: self, closure: action)
addTarget(sleeve, action: #selector(ClosureSleeve.invoke), for: controlEvents)
}
}
Example Usage:
button.addAction {
print("button pressed")
}
Solution 5
I created a library to do just this!
It supports UIControl
(UIButton
), UIBarButtonItem
, and UIGestureRecognizer
. It is also supported using CocoaPods.
https://github.com/lavoy/ALActionBlocks
// Assuming you have a UIButton named 'button'
[button handleControlEvents:UIControlEventTouchUpInside withBlock:^(id weakControl) {
NSLog(@"button pressed");
}];
Install
pod 'ALActionBlocks'
smirkingman
I'm an iOS & Rails developer working out of Houston, TX. I run [NSScreencast](http://nsscreencast.com), a site that delivers high quality screencasts for Swift developers. Other projects include: Combine Swift video course TonalTherapy GiggleTouch NSDateFormatter.com You can find me on twitter or on my blog.
Updated on November 18, 2021Comments
-
smirkingman over 2 years
I looked around, but couldn't find this on the internet, nor anywhere in the Apple docs, so I'm guessing it doesn't exist.
But is there a iOS4 blocks equivalent API to:
[button addTarget:self action:@selector(tappy:) forControlEvents:UIControlEventTouchUpInside];
I suppose this could be implemented using a category, but would rather not write this myself due to extreme laziness :)
Something like this would be awesome:
[button handleControlEvent:UIControlEventTouchUpInside withBlock:^ { NSLog(@"I was tapped!"); }];
-
smirkingman over 13 yearsIs there any way to do this to existing buttons via a category? I know the trouble is having an instance variable you can save the block for..
-
Eiko over 12 yearsSubclassing UIButton is an anti-pattern that is open for problems in the future. It's a class cluster and has only one way to correctly initialize it - through a class method that will never return one of your subclass instance. It might work now, it may fail at any time in the future.
-
Sound Blaster over 12 yearsEiko, excuse me. What kind of anti-pattern are you talking about? 'CallSuper'? As far as I know, 'Subclassing UIButton' isn't anti-pattern.
-
Gabe almost 12 yearsSubclassing UIButton is an anti-pattern because to do it safely you would need to override EVERYTHING in it's interface... which is crazy. I've run into crazy things happening when working with subclasses of UIBarButtonItems. Generally speaking; don't subclass class clusters. How to do it safely: (short) mikeash.com/pyblog/… and again: (longer and more detailed) cocoawithlove.com/2008/12/…
-
jeswang almost 12 yearsIf I use ARC, how to change this?
-
Johnny over 11 years@Gabe So then what is the 'correct' way to implement something like this?
-
Gabe about 11 years@Johnny This code does different things on different versions of iOS because it's a subclass(try adding a button of this class to a XIB in iOS4 and iOS5 and toggling 'Custom' and 'Rounded'). The 'correct' way to implement this is with a Category, There are several implementations of that here(including mine).
-
Nate Cook almost 11 yearsWhat about this makes it unsuited for Production?
-
Adam over 10 yearsRequires manual download and install of extra libs, on top of BlocksKit itself. Nice idea, but ... far too much effort for such a simple feature :(.
-
Christian Schnorr almost 9 years@Gabe
UIBarButtonItem
s aren't buttons but items.UIButton
can safely be subclassed, Apple even mentions it in the documentation. -
Gabe almost 9 years@Christian Oops, you are correct that these aren't class clusters... There are several problems similar to problems you have with overriding class-clusters with overriding UIButtons: The designated initializer changed functionality in iOS7 and you can't override the new UIButtonTypeSystem. splinter.com.au/2014/09/04/subclass-uibutton <- goes over the issues in more detail
-
mopsled over 8 yearsThe function in BlocksKit 2.x is now
bk_addEventHandler
. See this example as a guide for usingaddEventHandler
in BlocksKit 1.x/2.x -
Thomas Wana about 8 yearsAlways better to use a (well maintained and popular) library, because you'll get updates in the future. And it works well with Swift.
-
Bijender Singh Shekhawat over 5 yearsthis is an answer for swift 4 and 4.2 also. thanx bro
-
Warpling about 5 yearsYou could also use a UUID for the key rather than a random number:
UUID().uuidString