2010-10-11 13 views
58

Miré a mi alrededor, pero no pude encontrar esto en Internet ni en ningún otro lugar de los documentos de Apple, así que supongo que no existe.Bloque UIButton equivalente a addTarget: action: forControlEvents: method?

Pero hay una API de bloques iOS4 equivalente a:

[button addTarget:self action:@selector(tappy:) forControlEvents:UIControlEventTouchUpInside]; 

supongo que esto podría ser implementado utilizando una categoría, pero prefieren no escribir esto mismo debido a la extrema pereza :)

Algo como esto sería genial:

[button handleControlEvent:UIControlEventTouchUpInside withBlock:^ { NSLog(@"I was tapped!"); }]; 
+0

no sé por qué se dejase' t agregar soporte integrado para bloques en lugar de destino/acción en algún momento, pero tal vez es solo porque es tan fácil rodar una pequeña clase de ayuda que contiene un bloque que nadie realmente realmente necesita una extensión de la API estándar ... – yeoman

Respuesta

50

Acabo de implementar esto. ¡Funciona a las mil maravillas!

Y ni siquiera fue difícil.

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 
+4

es el ¿Hay alguna manera de hacer esto con los botones existentes a través de una categoría? Sé que el problema es tener una variable de instancia que puede guardar el bloque para ... –

+25

Subclase UIButton es un antipatrón que está abierto para problemas en el futuro. Es un clúster de clase y solo tiene una forma de inicializarlo correctamente, mediante un método de clase que nunca devolverá una instancia de su subclase. Podría funcionar ahora, puede fallar en cualquier momento en el futuro. – Eiko

+7

Eiko, perdón. ¿De qué tipo de anti-patrón estás hablando? 'CallSuper'? Hasta donde yo sé, 'Subclassing UIButton' no es anti-patrón. –

5

¡ESCRÍBELO POR ESTE MOMENTO Y NO ES LA MANERA DE RESOLVER ESTE PROBLEMA! La subclase UIButton crea un campo minado que simplemente no lo vale. Usa la Categoría de Shayne Sweeney (Acabo de actualizar su respuesta con un montón de ajustes para preparar su producción de ejemplo ... espero que se aprueben rápidamente).

----- ----- ORIG POSTAL

El código publicado por Martin debería funcionar si sólo se está asignando el UIControlEventTouchUpInside ... pero hay un par de problemas:

  • Perderá bloques con el código publicado si llama a handleControlEvent: más de una vez.
  • Si se asigna más de un tipo de evento, que se disparará el último bloque para todos los eventos

En mi código que estoy confiando en bloques de ser tratados como objetos objeto-C, que sólo funciona en iOS4 + (no 3.2) Me funciona bien cuando quiero hacer algo especial para los estados de los botones (es decir, las animaciones). Puede usar el bloque clickedButton para manejar los clics normales.

#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 
21

Aquí hay una implementación de la categoría de trabajo. En su forma actual, esto solo debe usarse en DEBUG. Uso esta categoría junto con una función (incluida a continuación) para probar varios bits de código cuando la interacción y el tiempo del usuario son importantes. Una vez más esto es sólo para propósitos de desarrollo/depuración y no debe ser considerado para la producción, de ahí el #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 
+21

¿Qué pasa con esto hace que sea inadecuado para la producción? –

+1

^¿verdad? Si funciona en depuración, debería funcionar en prod – jamespick

23

Hay una biblioteca de bloques adiciones a las clases/UI comunes Fundación: BlocksKit. Aquí está el documentation.

No subclase UIButton, pero añade UIControl category:

[button addEventHandler:^(id sender) { 
    //do something 
} forControlEvents:UIControlEventTouchUpInside]; 

También hay bloques/adiciones funcionales a colecciones (mapa, filtro, etc.), las cosas vistas-relacionadas y más.

NOTA: no funciona bien con Swift.

+0

Requiere descarga manual e instalación de libs adicionales, además de BlocksKit. Buena idea, pero ... demasiado esfuerzo para una característica tan simple :(. – Adam

+5

echa un vistazo a http://cocoapods.org – Cfr

+2

Este post me introdujo al maravilloso mundo de los cocoapods – Prat

1

Hay REKit que pone de manifiesto la capacidad latente de Blocks. Le da la capacidad de agregar/anular el método a una instancia usando Bloquear.

Con REKit, se puede hacer de forma dinámica un objetivo - que responde a buttonAction -, como a continuación:

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

No es necesario hacer una subclase ni una categoría.

Además del paradigma objetivo/acción, puede utilizar REKit para el patrón de delegación.

7

¡Creé una biblioteca para hacer esto!

Es compatible con UIControl (UIButton), UIBarButtonItem y UIGestureRecognizer. También es compatible con CocoaPods.

https://github.com/lavoy/ALActionBlocks

// Assuming you have a UIButton named 'button' 
[button handleControlEvents:UIControlEventTouchUpInside withBlock:^(id weakControl) { 
    NSLog(@"button pressed"); 
}]; 

Instalar

pod 'ALActionBlocks' 
0

Swift extensión/categoría de implementación basada en que me prepararon rápidamente. El uso de objetos asociados a OBJC no es un antipatrón. : 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) 
} 
1

Me resulta fácil y versátil para usar una pequeña clase de ayuda:

@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 

y utilizar de esta manera:

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