2010年9月13日月曜日

UIMenuController - メニューのカスタマイズ

iOS3.2 からメニューのカスタマイズが可能になった。少し試してみた。


UIMenuController


メニューは UIMenuController を使ってカスタマイズする。
UIMenuController Class Reference

使い方は次の通り。

1. setTargetRect:inView: でメニュー出現の位置を決める
2. 表示するメニューを配列にして menuItemsに格納する
3. setMenuVisible:animated: で表示する
4. canPerformAction:withSender: をオーバーライドして表示したいセレクタを選別する

表示内容は menuItemsプロパティに設定する。これは UIMenuItem の配列を指定する。
UIMenuItem Class Reference

使い方については下記サイトがわかりやすくて参考になる。
UIMenuControllerをカスタマイズする | iPad Techfirm Lab


サンプル


簡単なサンプルを作った。UITextFieldのサブクラスを作り、タッチされた時に3つのメニューアイテムを出すようにしてみた。
@interface CustomTextField : UITextField {

}

@end

@implementation CustomTextField

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
 UIMenuController* menuController = [UIMenuController sharedMenuController];
 [menuController setTargetRect:CGRectZero inView:self];
 menuController.arrowDirection = UIMenuControllerArrowDown;
 
 NSMutableArray* menuItems = [NSMutableArray array];
 
 [menuItems addObject:
  [[[UIMenuItem alloc] initWithTitle:@"メニュー1"
         action:@selector(menu1:)] autorelease]];
 [menuItems addObject:
  [[[UIMenuItem alloc] initWithTitle:@"メニュー2"
         action:@selector(menu2:)] autorelease]];
 [menuItems addObject:
  [[[UIMenuItem alloc] initWithTitle:@"メニュー3"
         action:@selector(menu3:)] autorelease]];
 menuController.menuItems = menuItems;
 [menuController setMenuVisible:YES animated:YES];
}

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {

 if (action == @selector(menu1:) ||
  action == @selector(menu2:) ||
  action == @selector(menu3:)) {
  return YES;
 }
 return NO;
}
- (void)menu1:(id)sender
{
 NSLog(@"menu1: %@", sender);
}

- (void)menu2:(id)sender
{
 NSLog(@"menu2: %@", sender);
}

- (void)menu3:(id)sender
{
 NSLog(@"menu3: %@", sender);
}

@end

実行結果は冒頭の図のようになる。


標準メニュー


UITextFieldの beganTouhces:でカスタムメニューを表示させた場合、標準のコピー&ペーストが使えなくなる。これらを表示するには canPerformAction:withEvent: で許可する必要がある。たとえばペーストの場合:
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {

 if (
  action == @selector(paste:) ||
  action == @selector(menu1:) ||
  action == @selector(menu2:) ||
  action == @selector(menu3:)
  ) {
  return YES;
 }
 return NO;
}
するとこうなる。

ただカスタムメニューは "More..." に押し込められてしまった。"More..."を押すとメニューが表示される。


考察


もともとメニューカスタマイズに興味を持ったのはテキストフィールドの履歴表示に使えないかと思ったから。例えばテキストフィールドにフォーカスが当たった時に過去に入力した履歴が表示されて選ぶことができる(このインターフェイスは遥か昔 NewtonOSで実装されていた)。この用途で UIMenuController を使って実現するにはいくつかの課題があることがわかった。

1. 標準メニューとの共存
先に見たように標準メニューを表示するとカスタムメニューは "More..."へ追いやられてしまう。これでは使えない。同じメニューでの共存は難しいので、ハンドリングするイベントを変えることで使い分けで回避できるかが鍵。

2. 動的に内容が変化するメニューへの対応
どのメニューアイテムが選択されたかはセレクタでしか判断できない。なので履歴のように表示内容が変わる場合、あらかじめ3〜5の固定数のメニューアイテムを用意しておいて呼び出されたメソッドによってどの履歴が選択されたかを判断させる必要がある。

3. その他
そもそも可変内容の選択に UIMenuControllerを使うのは UIガイドライン的に良いのかどうか(でも、そういうUIを見たことがあるような気もする)。


ソースコード


GitHubからどうぞ
MenuSample at 2010-09-13 from xcatsan's iOS-Sample-Code - GitHub


参考情報


UIMenuControllerをカスタマイズする | iPad Techfirm Lab
分かりやすくて参考になる。

1 件のコメント:

  1. お疲れさまです。

     git-hub からダウンロードさせてもらいましたが、どのフォルダに上記のUIMenucontroller のサンプルは入っているのでしょうか?

    よろしくお願いいたします。

    返信削除