【问题标题】:UIButton block equivalent to addTarget:action:forControlEvents: method?UIButton 块相当于 addTarget:action:forControlEvents: 方法?
【发布时间】:2011-04-23 21:47:03
【问题描述】:

我环顾四周,但在互联网上找不到这个,也没有在 Apple 文档的任何地方找到,所以我猜它不存在。

但是有没有 iOS4 块等效的 API:

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

我想这可以使用一个类别来实现,但由于极端懒惰,我不想自己写这个:)

这样的东西会很棒:

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

【问题讨论】:

  • 我不知道为什么他们在某些时候没有添加对块而不是目标/动作的内置支持,但也许只是因为滚动一个小助手类非常容易一个没有人真正需要标准 API 扩展的块......

标签: iphone uibutton objective-c-blocks


【解决方案1】:

我刚刚实现了这个。它就像一个魅力!

而且这并不难。

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

【讨论】:

  • 有没有办法通过类别对现有按钮执行此操作?我知道麻烦在于有一个实例变量,您可以将块保存为..
  • 子类化 UIButton 是一种反模式,将来会出现问题。它是一个类集群,只有一种方法可以正确初始化它——通过一个永远不会返回您的子类实例之一的类方法。它现在可能工作,它可能会在未来任何时间失败。
  • 英子,对不起。你在说什么样的反模式? “呼叫超级”?据我所知,“子类化 UIButton”不是反模式。
  • 子类化 UIButton 是一种反模式,因为要安全地进行操作,您需要覆盖其界面中的所有内容……这太疯狂了。在使用 UIBarButtonItems 的子类时,我遇到了一些疯狂的事情。通常来说,一般来说;不要子类集群。如何安全地做到这一点:(短)mikeash.com/pyblog/… 再次:(更长更详细)cocoawithlove.com/2008/12/…
  • @Gabe UIBarButtonItems 不是按钮,而是 itemsUIButton 可以安全地被子类化,Apple 甚至在文档中提到它。
【解决方案2】:

有一个添加到常用 Foundation/UI 类的块库:BlocksKit。这是documentation

它没有继承UIButton,而是添加了UIControl category

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

还有对集合(地图、过滤器等)、与视图相关的内容等的块/功能添加。

注意:它不能很好地与 Swift 配合使用。

【讨论】:

  • 需要在 BlocksKit 本身之上手动下载和安装额外的库。好主意,但是......对于这样一个简单的功能来说付出了太多的努力:(。
  • BlocksKit 2.x 中的函数现在是bk_addEventHandler。请参阅 this example 作为在 BlocksKit 1.x/2.x 中使用 addEventHandler 的指南
  • 最好使用(维护良好且流行的)库,因为您将来会获得更新。它适用于 Swift。
【解决方案3】:

这是一个工作类别实现。在它的当前形式中,这应该只在DEBUG 中使用。当用户交互和时间很重要时,我将这个类别与一个函数(包括在下面)结合使用来测试各种代码。同样,这仅用于开发/调试目的,不应考虑用于生产,因此 #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

【讨论】:

  • 这会导致它不适合生产吗?
【解决方案4】:

斯威夫特 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)
    }
}

示例用法:

button.addAction {
    print("button pressed")
}

【讨论】:

  • 这也是 swift 4 和 4.2 的答案。谢谢兄弟
  • 您也可以使用 UUID 作为密钥而不是随机数:UUID().uuidString
【解决方案5】:

我为此创建了一个库!

它支持UIControl (UIButton)、UIBarButtonItemUIGestureRecognizer。使用 CocoaPods 也支持它。

https://github.com/lavoy/ALActionBlocks

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

安装

pod 'ALActionBlocks'

【讨论】:

    【解决方案6】:

    这是我很久以前写的,这不是解决这个问题的方法!!!子类化 UIButton 会创建一个不值得的雷区。使用 Shayne Sweeney 的类别(我刚刚通过一系列调整更新了他的答案,以使他的示例产品准备就绪......希望他们能很快获得批准)。

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

    如果您只分配 UIControlEventTouchUpInside,Martin 发布的代码应该可以工作......但有几个问题:

    • 如果您多次调用 handleControlEvent:,您将使用发布的代码泄漏块。
    • 如果您分配了多种类型的事件,它将触发所有事件的最后一个块

    在我的代码中,我依赖 Blocks 被视为 object-c 对象,它只适用于 iOS4+(不是 3.2)。当我想为按钮状态(即动画)做一些特别的事情时,它对我很有效。您可以只使用 clickedButton 块来处理正常点击。

    #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
    

    【讨论】:

      【解决方案7】:

      REKit可以带出Blocks的潜在能力。它使您能够使用 Block 向实例添加/覆盖方法。

      使用 REKit,您可以动态创建一个响应 buttonAction 的目标,如下所示:

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

      您不需要创建子类或类别。

      除了目标/动作范式之外,您还可以使用 REKit 进行委托模式。

      【讨论】:

        【解决方案8】:

        我发起的基于 Swift 扩展/类别的实现。使用 OBJC 关联对象不是反模式。 :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)
        }
        

        【讨论】:

          【解决方案9】:

          我发现使用一个小助手类既简单又通用:

          @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
          

          并像这样使用它:

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

          【讨论】:

            【解决方案10】:

            一个更简单的解决方案(一个没有扩展等):

            void (^eventHandlerBlock)(void) = ^{
                printf("\nHandling event for button %lu\n", some_local_variable);
            };
            objc_setAssociatedObject(button, @selector(invoke), eventHandlerBlock, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
            [button addTarget:eventHandlerBlock action:@selector(invoke) forControlEvents:UIControlEventAllEvents];
            

            将选择器替换为块时需要牢记以下几点:

            1. 如果声明块变量static,可以省略objc_setAssociatedObject;但是,您不能使用任何非编译常量(例如some_local_variable)。
            2. 您可以将void(^)(void) 替换为dispatch_block_t

            【讨论】:

              猜你喜欢
              • 2011-03-04
              • 1970-01-01
              • 1970-01-01
              • 2011-03-20
              • 1970-01-01
              • 1970-01-01
              • 2021-07-29
              • 1970-01-01
              • 2019-01-08
              相关资源
              最近更新 更多