J'ai regardé autour de moi, mais je n'ai pas trouvé cela sur Internet, ni nulle part dans la documentation Apple. Je suppose donc que cela n'existe pas.
Mais existe-t-il une API iOS4 équivalente à:
[button addTarget:self action:@selector(tappy:) forControlEvents:UIControlEventTouchUpInside];
Je suppose que cela pourrait être implémenté en utilisant une catégorie, mais je préférerais ne pas l'écrire moi-même à cause de extrême la paresse :)
Quelque chose comme ça serait génial:
[button handleControlEvent:UIControlEventTouchUpInside withBlock:^ { NSLog(@"I was tapped!"); }];
Je viens de mettre en œuvre cela. Il fonctionne comme un charme!
Et ce n'était même pas difficile.
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
Il existe une bibliothèque d’additions de blocs aux classes communes Foundation/UI: BlocksKit . Voici la documentation .
Il ne sous-classe pas UIButton, mais ajoute UIControl category :
[button addEventHandler:^(id sender) {
//do something
} forControlEvents:UIControlEventTouchUpInside];
Il existe également des blocs/ajouts fonctionnels aux collections (carte, filtre, etc.), des éléments liés aux vues, etc.
NOTE: cela ne fonctionne pas bien avec Swift.
Voici une implémentation de la catégorie de travail. Dans sa forme actuelle, cela ne devrait être utilisé que dans DEBUG
. J'utilise cette catégorie conjointement avec une fonction (incluse ci-dessous) pour tester divers bits de code lorsque l'interaction et le minutage de l'utilisateur sont importants. Encore une fois, ceci est uniquement à des fins de développement/débogage et ne devrait pas être considéré pour la production, d'où le #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
J'ai créé une bibliothèque pour faire cela!
Il prend en charge UIControl
(UIButton
), UIBarButtonItem
et UIGestureRecognizer
. Il est également pris en charge avec CocoaPods.
https://github.com/lavoy/ALActionBlocks
// Assuming you have a UIButton named 'button'
[button handleControlEvents:UIControlEventTouchUpInside withBlock:^(id weakControl) {
NSLog(@"button pressed");
}];
Installer
pod 'ALActionBlocks'
J'ai écrit cela il y a longtemps et ce n'est pas le moyen de résoudre ce problème !!! Sous-classer UIButton crée un champ de mines qui n'en vaut tout simplement pas la peine. Utilisez la catégorie de Shayne Sweeney (je viens de mettre à jour sa réponse avec une série de modifications pour rendre son exemple de production prêt… espérons-le, ils seront approuvés rapidement).
----- POSTE ORIG -----
Le code envoyé par Martin devrait fonctionner si vous attribuez uniquement UIControlEventTouchUpInside ... mais il existe quelques problèmes:
Dans mon code, je m'appuie sur le traitement des blocs en tant qu'objets d'objet-c, qui ne fonctionnent que sur iOS4 + (et non 3.2). Cela fonctionne bien pour moi lorsque je veux faire quelque chose de spécial pour les états de bouton (animations). Vous pouvez simplement utiliser le bloc clickedButton pour gérer les clics normaux.
#import <UIKit/UIKit.h>
@interface ButtWithBlockActions : UIButton {
void (^downBlock_)(void);
void (^upBlock_)(void);
void (^clickedBlock_)(void);
}
@property(nonatomic,retain) void (^downBlock)(void);
@property(nonatomic,retain) void (^upBlock)(void);
@property(nonatomic,retain) void (^clickedBlock)(void);
@end
#import "ButtWithBlockActions.h"
@implementation ButtWithBlockActions
- (void)dealloc {
[downBlock_ release];
[upBlock_ release];
[clickedBlock_ release];
[super dealloc];
}
- (void (^)(void))downBlock { return downBlock_; }
- (void) fireDownBlock { downBlock_(); }
- (void) setDownBlock:(void (^)(void))block {
if(downBlock_) {
[self removeTarget:self action:@selector(fireDownBlock) forControlEvents:UIControlEventTouchDown];
[self removeTarget:self action:@selector(fireDownBlock) forControlEvents:UIControlEventTouchDragEnter];
[downBlock_ release];
}
downBlock_ = [block copy];
if(downBlock_) {
[self addTarget:self action:@selector(fireDownBlock) forControlEvents:UIControlEventTouchDown];
[self addTarget:self action:@selector(fireDownBlock) forControlEvents:UIControlEventTouchDragEnter];
}
}
- (void (^)(void))upBlock { return upBlock_; }
- (void) fireUpBlock { upBlock_(); }
- (void) setUpBlock:(void (^)(void))block {
if(upBlock_) {
[self removeTarget:self action:@selector(fireUpBlock) forControlEvents:UIControlEventTouchUpInside];
[self removeTarget:self action:@selector(fireUpBlock) forControlEvents:UIControlEventTouchUpOutside];
[self removeTarget:self action:@selector(fireUpBlock) forControlEvents:UIControlEventTouchDragOutside];
[self removeTarget:self action:@selector(fireUpBlock) forControlEvents:UIControlEventTouchCancel];
[upBlock_ release];
}
upBlock_ = [block copy];
if(upBlock_) {
[self addTarget:self action:@selector(fireUpBlock) forControlEvents:UIControlEventTouchUpInside];
[self addTarget:self action:@selector(fireUpBlock) forControlEvents:UIControlEventTouchUpOutside];
[self addTarget:self action:@selector(fireUpBlock) forControlEvents:UIControlEventTouchDragOutside];
[self addTarget:self action:@selector(fireUpBlock) forControlEvents:UIControlEventTouchCancel];
}
}
- (void (^)(void))clickedBlock { return clickedBlock_; }
- (void) fireClickedBlock { clickedBlock_(); }
- (void) setClickedBlock:(void (^)(void))block {
if(clickedBlock_) {
[self removeTarget:self action:@selector(fireClickedBlock) forControlEvents:UIControlEventTouchUpInside];
[clickedBlock_ release];
}
clickedBlock_ = [block copy];
if(clickedBlock_) {
[self addTarget:self action:@selector(fireClickedBlock) forControlEvents:UIControlEventTouchUpInside];
}
}
@end
Swift 4
Voici la solution Swift
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)
}
}
Exemple d'utilisation:
button.addAction {
print("button pressed")
}
Il y a REKit qui fait ressortir la capacité latente de Blocks. Il vous donne la possibilité d'ajouter/de remplacer une méthode à une instance à l'aide de Block.
Avec REKit, vous pouvez créer dynamiquement une cible - qui répond à buttonAction - comme ci-dessous:
id target;
target = [[NSObject alloc] init];
[target respondsToSelector:@selector(buttonAction) withKey:nil usingBlock:^(id receiver) {
// Do something…
}];
[button addTarget:target action:@selector(buttonAction) forControlEvents:UIControlEventTouchUpInside];
Vous n'avez pas besoin de créer une sous-classe ni une catégorie.
En plus du paradigme cible/action, vous pouvez utiliser REKit pour le modèle de délégation.
Je trouve facile et polyvalent d’utiliser une petite classe d’aide:
@interface Handler : NSObject
@end
@implementation Handler {
void (^block)(id);
}
+ (Handler *)create:(void (^)(id))block {
Handler *result = [[Handler alloc] init];
result->block = block;
return result;
}
- (void)call:(id)sender {
block(sender);
}
@end
et l'utiliser comme ça:
Handler *handler = [Handler create:^(id sender) {
// ... handle the event, using local state captured by the block ...
}];
// store the handler because the target is not retained in addTarget
[handlers addObject:handler];
[button addTarget:handler action:@selector(call:) forControlEvents:UIControlEventTouchUpInside];
Une implémentation rapide basée sur une extension/catégorie que j'ai préparée. L'utilisation d'objets associés à OBJC n'est pas un anti-motif. : P
import UIKit
// MARK: UIControl Block based actions
typealias ActionBlock = (UIControl) -> ()
class UIButtonActionDelegate : NSObject {
let actionBlock : ActionBlock
init(actionBlock: ActionBlock) {
self.actionBlock = actionBlock
}
func triggerBlock(control : UIControl) {
actionBlock(control)
}
}
private var actionHandlersKey: UInt8 = 0
extension UIControl {
var actionHandlers: NSMutableArray { // cat is *effectively* a stored property
get {
return associatedObject(self, key: &actionHandlersKey, initialiser: { () -> NSMutableArray in
return NSMutableArray()
})
}
set { associateObject(self, key: &actionHandlersKey, value: newValue) }
}
func addBlockForEvents(events: UIControlEvents, block: ActionBlock) {
let actionDelegate = UIButtonActionDelegate(actionBlock: block)
actionHandlers.addObject(actionDelegate) // So it gets retained
addTarget(actionDelegate, action: #selector(UIButtonActionDelegate.triggerBlock(_:)), forControlEvents: events)
}
}
// MARK: Associated Object wrapper
func associatedObject<ValueType: AnyObject>(
base: AnyObject,
key: UnsafePointer<UInt8>,
initialiser: () -> ValueType)
-> ValueType {
if let associated = objc_getAssociatedObject(base, key)
as? ValueType { return associated }
let associated = initialiser()
objc_setAssociatedObject(base, key, associated,
.OBJC_ASSOCIATION_RETAIN)
return associated
}
func associateObject<ValueType: AnyObject>(
base: AnyObject,
key: UnsafePointer<UInt8>,
value: ValueType) {
objc_setAssociatedObject(base, key, value,
.OBJC_ASSOCIATION_RETAIN)
}