Блок UIButton эквивалентен addTarget: action: forControlEvents: метод? - PullRequest
61 голосов
/ 11 октября 2010

Я осмотрелся, но не смог найти это ни в Интернете, ни в документах Apple, поэтому я предполагаю, что его не существует.

Но есть ли блоки iOS4, эквивалентные API для:

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

Полагаю, это можно реализовать с помощью категории, но я бы не стал писать это сам из-за extreme laziness:)

Примерно такофигенно:

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

Ответы [ 9 ]

54 голосов
/ 20 октября 2010

Я только что реализовал это.Это работает как шарм!

И это было даже не сложно.

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
23 голосов
/ 31 октября 2012

Существует библиотека дополнений блоков к общим классам Foundation / UI: BlocksKit . Вот документация .

Он не подкласс UIButton, но добавляет UIControl категории :

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

Есть также блоки / функциональные дополнения к коллекциям (карта, фильтр и т. Д.), Связанные с представлениями вещи и многое другое.

ПРИМЕЧАНИЕ: он не очень хорошо работает со Swift.

22 голосов
/ 23 марта 2011

Вот реализация рабочей категории. В его текущей форме это должно использоваться только в 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
7 голосов
/ 20 июня 2018

Swift 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")
}
7 голосов
/ 05 ноября 2013

Я создал библиотеку для этого!

Он поддерживает UIControl (UIButton), UIBarButtonItem и UIGestureRecognizer.Он также поддерживается с помощью CocoaPods.

https://github.com/lavoy/ALActionBlocks

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

Установка

pod 'ALActionBlocks'
5 голосов
/ 04 декабря 2010

Я НАПИСАЛ ЭТО ДОЛГО, И ЭТО НЕ СПОСОБ РЕШЕНИЯ ЭТОЙ ПРОБЛЕМЫ !!!Подкласс UIButton создает минное поле, которое просто не стоит этого.Используйте категорию Шейна Суини (я только что обновил его ответ кучей твиков, чтобы подготовить его пример производства ... надеюсь, они быстро получат одобрение).

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

Код, опубликованный Мартином, должен работать, если вы только назначаете UIControlEventTouchUpInside ... но есть пара проблем:

  • Вы пропустите блоки с кодом, опубликованным, если вы вызовете handleControlEvent: более одного раза.
  • Если вы назначите более одного типа события, он запустит последний блок для всех событий

В моем коде я полагаюсь на обрабатываемые блокикак объекты 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
1 голос
/ 06 ноября 2016

Мне легко и универсально использовать крошечный вспомогательный класс:

@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];
1 голос
/ 14 февраля 2013

Существует REKit , который выявляет скрытую способность блоков.Это дает вам возможность добавлять / переопределять метод к экземпляру, используя Block.

С REKit вы можете динамически создать цель, которая реагирует на buttonAction, как показано ниже:не нужно создавать подкласс или категорию.

В дополнение к парадигме цели / действия вы можете использовать REKit для шаблона делегирования.

0 голосов
/ 09 августа 2016

Быстрое расширение / основанная на категориях реализация, которую я подхватил.Использование объектов, связанных с 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)
}
...