web-dev-qa-db-fra.com

UIButton block équivalent à addTarget: action: forControlEvents: method?

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!"); }];
60
Ben Scheirman

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
51
Martin Reichl

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.

23
Cfr

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
22
Shayne Sweeney

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'
7
lavoy

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:

  • Si vous appelez handleControlEvent: plusieurs fois, vous allez avoir des fuites de blocs avec le code envoyé.
  • Si vous affectez plus d'un type d'événement, le dernier bloc sera déclenché pour tous les événements.

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
5
Gabe

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")
}
5
levin varghese

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.

1
Kazki

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

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)
}
0
BadPirate