UIKitにBlocksを適用(UIActionSheet)

UIKitでは、ボタンを押したり、アクションが行われると、delegateの仕組みを使って通知を受けます。

例:

- (void)showActionSheet
{
    UIActionSheet* as = [[UIActionSheet alloc] initWithTitle:@"title"                                                    delegate:self                                           cancelButtonTitle:@"cancell"                                      destructiveButtonTitle:@"destruct"                                           otherButtonTitles:@"btn1", @"btn2", nil ];
    [as showInView:self.view];
    [as autorelease];
}
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
    switch(actionSheet.tag) {
        case MSG_CONFIRM_TAG:        {
            switch (buttonIndex) {
                case 1:
                    // destruct                    break;
                case 2:
                    // cancell                    break;
                case 3:
                    //btn1                default:
                    break;
            }
        }
            break;
        default::
            break;
    }
}



表示するロジックと、アクションが別々に書かれていて、直感的なコードではない。
また、異なるUIActionSheetでも同じdelegate先になってしまう(書き方しだいですが)

iOS4からはBlocksというコールバックのような仕組みが準備されていて、それを使えば、表示部分と、アクションされた部分を一カ所で記述できる。

例1:

- (void)showActionSheet{    UIActionSheet* as = [[UIActionSheet alloc] initWithTitle:@"sample"                                                       block:^(NSInteger buttonIndex){                                                           switch (buttonIndex) {                                                               case 1:                                                                   // destruct                                                                   break;                                                                                                                                  case 2:                                                                   // cancell                                                                   break;                                                               case 3:                                                                   //btn1                                                               default:                                                                   break;                                                           }                                                       }                                            cancelButtonTitle:@"cancell"                                      destructiveButtonTitle:@"destruct"                                           otherButtonTitles:@"oth1", @"oth2",                         nil];    [as showInView:self.view];    [as autorelease];}
例2:



- (void)showActionSheet
{
    UIActionSheet* as = [[UIActionSheet alloc] init];
    as.title = @"sample";
    [as addButtonWithTitle:@"btn1" withBlock:^{
// btn1
    }];
    [as addButtonWithTitle:@"btn2" withBlock:^{
// btn2
    }];
    [as addButtonWithTitle:@"cancel" withBlock:^{
// cancell
    }];
    [as setCancelButtonIndex:2];
    [as showInView:self.view];
    [as autorelease];
}

今回はcategoryとして生成しましたが、もちろんサブクラス化しても対応できます

  • initializeと、ボタンを追加する(addButtonWithTitle)にそれぞれblockを受け取る
  • delegate先のblock格納先として、UIActionSheetCallbackを作成



UIActionSheet+Blocks.h




@interface UIActionSheet (Blocks)
typedef void (^UIActionSheetCallback_t)(NSInteger buttonIndex);
typedef void (^UIActionSheetButtonCallback_t)(void);

- (id)initWithTitle:(NSString *)title block:(UIActionSheetCallback_t)block cancelButtonTitle:(NSString *)cancelButtonTitle destructiveButtonTitle:(NSString *)destructiveButtonTitle otherButtonTitles:(NSString *)otherButtonTitles, ... NS_REQUIRES_NIL_TERMINATION;
- (NSInteger)addButtonWithTitle:(NSString *)title withBlock:(UIActionSheetButtonCallback_t)block;
@end


@interface UIActionSheetCallback : NSObject <UIActionSheetDelegate> {
    UIActionSheetCallback_t callback;    NSMutableDictionary* buttonCallbacks;
}
@property (nonatomic, copy) UIActionSheetCallback_t callback;@property (nonatomic, retain) NSMutableDictionary* buttonCallbacks;
- (id)initWithCallback:(UIActionSheetCallback_t) callback;
@end


 UIActionSheet+Blocks.m
#import "UIActionSheet+Blocks.h"
@implementation UIActionSheet (Blocks)
- (id)initWithTitle:(NSString *)title block:(void(^)(NSInteger))block cancelButtonTitle:(NSString *)cancelButtonTitle destructiveButtonTitle:(NSString *)destructiveButtonTitle otherButtonTitles:(NSString *)otherButtonTitles, ... {
    self = [self initWithTitle:title delegate:nil cancelButtonTitle:cancelButtonTitle destructiveButtonTitle:destructiveButtonTitle otherButtonTitles:nil];    if (self) {
        self.delegate = [[[UIActionSheetCallback alloc] initWithCallback:block] autorelease];
      
        va_list args;
        va_start(args, otherButtonTitles);
        for (NSString *arg = otherButtonTitles; arg != nil; arg = va_arg(args, NSString*)) {
            [self addButtonWithTitle:arg];
        }
        va_end(args);
    }
    return self;}
- (NSInteger)addButtonWithTitle:(NSString *)title withBlock:(void(^)(void))block {
    if (!self.delegate) {
        self.delegate = [[[UIActionSheetCallback alloc] init] autorelease];
    }
  
    NSInteger index = [self addButtonWithTitle:title];
    [((UIActionSheetCallback*)self.delegate).buttonCallbacks setObject:[[block copy] autorelease] forKey:[NSNumber numberWithInteger:index]];  
    return index;
}
@end
@implementation UIActionSheetCallback
@synthesize callback;@synthesize buttonCallbacks;
- (void)_init {
    self.buttonCallbacks = [NSMutableDictionary dictionary];    [self retain];  
}
- (id)init {
    if (self = [super init]) {
        [self _init];
    }
    return self;}
- (id)initWithCallback:(UIActionSheetCallback_t)aCallback {
    if(self = [super init]) {
        self.callback = aCallback;
        [self _init];
    }
    return self;}
// UIAlertView delegate メソッド- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
{
    if(callback)        callback(buttonIndex);
    UIActionSheetButtonCallback_t buttonCallback = [buttonCallbacks objectForKey:[NSNumber numberWithInteger:buttonIndex]];    if (buttonCallback)
        buttonCallback();
    [self release];
}
- (void)dealloc {
    self.callback = nil;
    self.buttonCallbacks = nil;    [super dealloc];
}
@end





コードはこちらから
https://github.com/hrk-ys/ios-blocks

0 件のコメント:

コメントを投稿

ReactNativeでAndroid対応する話

前提 ReactNativeでiOS版のアプリをリリースしていて、Android版をリリースする話 トラブルシューティング Build.VERSION_CODES.Q が存在しないエラー compileSdkVersionを29以上にすると解決 メモリー足りないエラー Execu...