UIActionSheetを使いやすくする

| コメント(0) | トラックバック(0)
こんにちは。開発担当のICTFです。

同じコントロール内で複数のActionSheetを用いる場合、一般的には各ActionSheetにタグを割り振り、ボタンタッチのコールバック内でタグ番号とボタン番号から処理を選択する。といった記述をします。
例えば、ボタンタッチのコールバック処理は次の様なイメージになります。

- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex

{

    switch(actonSheet.tag) {

        case tag1:

            switch(clickedButtonAtIndex) {

                case 0:

                    処理①

                    break;

                case 1:

                    処理②

                    break;

                case 3:

                    処理③

                    break;

                default:

                    break;

            }

            break;

        case tag2:

            switch(clickedButtonAtIndex) {

                case 0:

                    処理②

                    break;

                case 1:

                    処理③

                    break;

                case 3:

                    処理④

                    break;

                default:

                    break;

            }

            break;

        default:

            break;

    }

}


たった2種類のアクションシートを作っただけで、こんなにも面倒臭い記述になってしまいます。
それにswitch分やif分をだらだらと書かなければならないのは、なんだかオブジェクト指向になりきれて無い様でどうにも好きになれません。

また、ActionSheetが使い辛くなっている要因の1つとして、アクションの対象となるオブジェクトと紐付けができないという点があります。
例えばリストビューで選択されている1つの項目(オブジェクトA)に対するActionSheetを表示したとします。
アクションとして「削除」を選んだ際に、tag番号は「TAG_DEFAULT」、ボタン番号は「3」というように取得ができるのですが(この2つの値で削除という処理を選択することが可能です)、「オブジェクトAに対する削除」というところまでは判断ができません。
この問題を解決する為に、ActionSheetを表示するタイミングでコントロールのメンバとして宣言されている記憶用変数にオブジェクトAを記憶しておき、ボタンタッチコールバック処理内で記憶しているオブジェクトAを活用する。といった処理が必要になる訳です。
しかし、このような実装もオブジェクト指向としては格好が悪いですよね。できれば1つの機能(ActionSheetの表示からその対象の管理、そのアクションまで)は1つのクラスで実現したいところです。

前置きが長くなりましたが、今回は上で挙げたActionSheetの2つの弱点
 ・ボタンコールバック内の処理が煩雑
 ・アクションシートの操作対象となるオブジェクトとの紐付けができない
という点の解決を目指して改良を加えようと思います。

大まかなイメージとしては、ActionSheetの各ボタンをタイトル名のみで管理するのではなく、タイトル名を含むボタン用クラスで管理します。
ボタン用クラスは配列で管理し、拡張したUIActionSheetクラスのメンバとして記録します。
ボタンがタップされた際は、拡張UIActionSheetクラスのフレームワークとして、ボタン用クラスのactionメソッドをコールします。
actionメソッドはボタン用クラスから派生した各処理クラスで実装を行ないます。

ではまずボタン用クラス(ボタンの基底クラス)を作成しましょう。
.hでは次のように記述します。

// 各種通知から対象オブジェクトを取り出す為の共通キー

#define ACTION_SHEET_BUTTON_KEY_OBJECT @"ActionSheetButtonKey_Object"


// アクションシートのボタンベースクラス

@interface ActionSheetButtonBase : NSObject

{

NSString* _title;

id _object;

}


@property (nonatomic, retain, readwrite) NSString* Title;

@property (nonatomic, retain, readwrite) id Object;


-(id) initWithObject:(id)object;


// アクション

-(void) action;


@end


_titleはボタンの名称となるタイトルを表します。
_objectはこのボタンがタップされた際に操作対象となるオブジェクトを代入します。必要がない場合はnilでも構いません。

.mの実装は次のようになります。

@implementation ActionSheetButtonBase


@synthesize Title = _title;

@synthesize Object = _object;


-(id) initWithObject:(id)object

{

if (self = [super init]) {

self.Title = @"";

self.Object = object;

}

return self;

}


// 継承クラスでオーバーライドする

-(void) action

{

}


@end


こちらも単純で、メンバの初期化とactionメソッドの空実装のみです。
actionメソッドの実装は派生先で行ないますので、まだ必要ありません。

次にActionSheetButtonBaseクラスを継承した、各ボタンクラスを実装します。
例として対象となるオブジェクトAのプロパティ「Title」を変更するアクションボタンクラスを載せますので、応用して実装してみてください。
.hは以下のようになります。

// 計算オブジェクトのタイトル編集ボタン

@interface CalcObjectTitleEditButton : ActionSheetButtonBase


@end


必要なメンバやメソッドはベースクラスで宣言済みですので、新しい要素は必要ありません。
.mは以下のようになります。

@implementation CalcObjectTitleEditButton


-(id) initWithObject:(id)object

{

if (self = [super initWithObject:object]) {

self.Title = @"タイトルの編集";

}

return self;

}


-(void) action

{

// タイトル編集ビューを表示する

UIAlertView* view = [[UIAlertView alloc] initWithTitle:@"タイトルの編集" message:@"新しいタイトルを入力してください。" delegate:_object cancelButtonTitle:@"Cancel" otherButtonTitles:@"OK", nil];

view.alertViewStyle = UIAlertViewStylePlainTextInput;

[view show];

}


@end


initメソッド内でこのボタンのタイトルを設定しています。
次にactionメソッド内の処理を実装します。ここでは新しくタイトルを入力してもらう為のAlertViewを表示しています。
AlertViewのdelegateは対象オブジェクトである_objectに設定し、対象オブジェクトごとに受け取った値を処理する作りになっています。
delegateの設定先をselfにして、CalcObjectTitleEditButtonクラス内で_object.Titleを書き換える処理を実装しても良いかもしれません。

こんな感じでまずは必要となるActionSheetボタンの数だけクラスを作成してください。

必要なボタンクラスの作成が終わりましたら、次にUIActionSheetの拡張クラスを実装します。
.hは以下のようになります。

#import "ActionSheetButtonBase.h"


// アクションシートの拡張クラス

@interface UIActionSheetEx : UIActionSheet

<UIActionSheetDelegate>

{

NSArray* _buttonList;

}


@property (nonatomic, readonly) NSArray* ButtonList;


// アクションシートのボタンを追加する

-(void) addButtonList:(NSArray*)buttonList;


@end



ボタンタッチイベントをフレームワークとして実装する為、UIActionSheetDelegateプロトコルの宣言を追加します。
メンバ変数としてActionSheetで使われるボタンクラスの一覧を宣言します。ActionSheet内部でボタンクラスを管理する事により、他のアクションシートとの区分けを気にする必要が無くなります。
最後に各ボタンクラスのTitleプロパティを抽出してボタンを作成するのが面倒ですので、ボタンクラスの配列を渡すだけで一連のボタン作成と記憶を行なってくれるメソッドを宣言しています。

.mは以下のようになります。

@implementation UIActionSheetEx


@synthesize ButtonList = _buttonList;


-(id) init

{

if (self = [super init]) {

self.delegate = self;

}

return self;

}


-(void) dealloc

{

[_buttonList release];

[super dealloc];

}


// アクションシートのボタンを追加する

-(void) addButtonList:(NSArray*)buttonList

{

_buttonList = [buttonList retain];

for (ActionSheetButtonBase* button in buttonList) {

[self addButtonWithTitle:button.Title];

}

}


// アクションシートのコールバック

- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex

{

ActionSheetButtonBase* button = [_buttonList objectAtIndex:buttonIndex];

// ボタンに設定されたアクションを実行する

[button action];

}


@end


initで自身のdelegateを自身に設定します。
ActionSheetの各コールバックをフレームワーク化する為に必要な設定ですので、忘れずに行なってください。
-(void) addButtonList:(NSArray*)buttonListでは、配列の各要素をループで回し、Titleプロパティからボタンを作成しています。
また_buttonListに受け取った配列を格納します。これはActionSheetコールバックで該当するactionメソッドを呼び出す為に用います。
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndexコールバックでは、_buttonListから該当のオブジェクトを得、そのactionメソッドをコールするのみです。
これで選択したボタンのactionメソッドがコールされるというフレームワークが完成しました。

最後に実際の使用例のサンプルを挙げておきます。

// アクションシートを生成する

UIActionSheetEx* sheet = [[[UIActionSheetEx alloc] init] autorelease];

sheet.title = @"";

sheet.actionSheetStyle = UIActionSheetStyleBlackTranslucent;

[sheet addButtonList:[NSArray arrayWithObjects:

    [[[CalcObjectTitleEditButton alloc] initWithObject:obj] autorelease], 

    [[[CalcObjectSetDefaultValueButton alloc] initWithObject:obj] autorelease],

    [[[CalcObjectDeleteButton alloc] initWithObject:obj] autorelease],

    [[[ActionSheetCancelButton alloc] initWithObject:obj] autorelease],

    nil]];

sheet.destructiveButtonIndex = 2;

sheet.cancelButtonIndex = 3;

[sheet showInView:self.view];


普通のUIActionSheetの利用法とほぼ変わりません。
違いはボタン作成で文字列の配列を渡すのではなく、ActionSheetButtonBaseクラスを継承したクラスオブジェクトの配列を渡すくらいです。

以上の実装でActionSheet利用元のコントロールでActionSheetの管理全般から解放されます。
メッセージを表示するだけのAlertView並みにお手軽になったのではないでしょうか。

もし「ここを改良できる」や「ここに問題がある」などありましたら、是非是非教えて下さいませ。

トラックバック(0)

トラックバックURL: http://www.ict-fractal.com/MovableType/mt/mt-tb.cgi/23

コメントする

Twitterボタン
Twitterブログパーツ