UIButton block equivalent to addTarget:action:forControlEvents: method?

后端 未结 9 1185
旧巷少年郎
旧巷少年郎 2020-11-29 20:58

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 A

相关标签:
9条回答
  • 2020-11-29 21:21

    I find it easy and versatile to use a tiny helper class:

    @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
    

    and use it like this:

    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];
    
    0 讨论(0)
  • 2020-11-29 21:22

    I WROTE THIS LONG AGO AND IT'S NOT THE WAY TO SOLVE THIS PROBLEM!!! Subclassing UIButton creates a minefield that just isn't worth it. Use Shayne Sweeney's Category(I just updated his answer with a bunch of tweaks to make his example production ready... hopefully they get approved quickly).

    -----ORIG POST-----

    The code posted by Martin should work if you are only assigning the UIControlEventTouchUpInside... but there are a couple problems:

    • You will leak blocks with the code posted if you call handleControlEvent: more than once.
    • If you assign more than one type of event, it will fire the last block for all events

    In my code I'm relying on Blocks being treated as object-c objects, which only works on iOS4+(not 3.2). It works well for me when I want to do something special for button states(i.e. animations). You can just use the clickedButton block for handling normal clicks.

    #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
    
    0 讨论(0)
  • 2020-11-29 21:28

    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
    
    0 讨论(0)
  • 2020-11-29 21:29

    Swift 4

    Here is the swift solution

    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")
    }
    
    0 讨论(0)
  • 2020-11-29 21:32

    Swift extension / category based implementation that I whipped up. Using OBJC associated objects is not an anti-pattern. :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 讨论(0)
  • 2020-11-29 21:33

    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'
    
    0 讨论(0)
提交回复
热议问题