2012年4月13日金曜日

Number Padに「完了」ボタンを付ける方法


キーボードの中で、なぜかNumber Padに完了(Done)ボタンがありません。デザイン上の理由かもしれませんが、ちょっと不思議です。
ナビゲーションバーの「完了」ボタンでアクションを起こすなどすればよいので実際に困る状況はそれほどないかもしれませんが、それでは不十分と思う人がいて、検索するとNumber Padに完了/Doneボタンを付加する方法がいくつか見つかります。

大きく分けると次の二通りあります。

(1)ViewControllerのサブビューに追加する方法
ナビゲーションバーの「完了」ボタンのかわりにNumber Padのすぐ上(あるいはNumber Pad表示後に隠されない位置)にナビゲーションバーなどを配置します。
Number Padのみを使うモーダルビューの場合はお勧めです。
Number Pad以外でもサイズが変化しない英語キーボードであれば利用できますが、日本語キーボードの場合は変換候補がキーボード外に表示されるため、位置調整が難しくなります。


ナビゲーションバーの場合の位置設定



(2)キーボードにボタンを追加する方法
KeyboradのViewを見つけ、その中にボタンを配置します。
この方法ではNumber Padの左下の空きスペースにボタンを配置するのがよさそうです。
この場合は(1)よりも次の手順が増えます。また非公開の仕様を利用するので、将来使えなくなる可能性があります。
  • Keyboradがサブビューに追加されるのはdelegateメソッドの後に行われるため、NotificationCenterに登録し、UIKeyboardWillShowNotificationの通知を受け取る。
  • キーボードのビューを探す。
  • キーボードのビューに完了ボタンを追加する。

実装例

UIKeyboardTypeNumberPad and the missing "return" keyを基に、Storyboardを利用して少し作りやすくしたものです。

UIButtonを画面に配置する。 
この例では位置、サイズはプログラムで設定するので、どこに配置してもよい。


プロパティーのTypeをCustomに設定し、背景色、文字色を設定する。


MYViewControllerのOutlet/doneButton、Action/doneAction:に接続する。

@interface
  @property (strong, nonatomic) IBOutlet UIButton *doneButton;
  - (IBAction)doneAction:(id)sender;
@end

@implementation

- (void)viewDidLoad
{
    [super viewDidLoad];
    //Notificationの登録。Number Padを使用するときだけ登録する。
    [[NSNotificationCenter defaultCenter] addObserver:self 
                               selector:@selector(keyboardDidShow:) 
                                  name:UIKeyboardDidChangeFrameNotification 
                                 object:nil];
    //doneButtonをビューから切り離しておく。
    [doneButton removeFromSuperview];
}

//Notification登録解除
- (void)viewDidUnload
{
    [super viewDidUnload];
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

//iOS 6以降 はviewDidUnloadが呼ばれないのでdeallocにも実装しておく。
- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

//Keyboard表示直前に呼ばれる。
- (void)keyboardDidShow:(NSNotification *)note
{
    //どのWindowにKeyboardがセットされていてもよいようにループする。
    for(UIWindow *w in [[UIApplication sharedApplication] windows]) {
        //Keyboardが見つかったらループ終了。
        if ([self setDoneButtonToNumberPad:w]) break;
    }
}

//KeyboardがみつかったらdoneButtonをセットし、YESを返す。
//最後までKeyboardがみつからなかったらNOを返す。
- (BOOL)setDoneButtonToNumberPad:(UIView *)view
{
    for(UIView *v in view.subviews) {
        if ([v.description hasPrefix:@"<UIPeripheralHostView"]) {
            //Number Padの左下の空白セルに合わせて位置、サイズをセットする。
            float h = v.frame.size.height/4;
            CGRect r = CGRectMake(0, h*3, v.frame.size.width/3, h);
            doneButton.frame = r;
            //Keyboardのビューに追加する。
            [v addSubview:doneButton];
            return YES;
        }
    }
    return NO;
}

//doneButtonタップ時のアクション。
- (IBAction)doneAction:(id)sender
{
  //処理実行
}

Number Padの判断

Number Padが使われるかどうかをNotification登録時に判断できる場合は問題ありませんが、通知を受けてから判断する必要がある場合はNumber Padであることを直接判断できる情報がみつからないため、ちょっと面倒です。
Number Padの場合はそのsubviewの中に含まれるキー(UIKBKeyView)が12個と他のキーボードより少ないので、その数で判断できます。

//UIPeripheralHostView内のUIKBKeyViewを探し、arrayに追加する。
- (NSArray *)findKeyViews:(UIView *)view array:(NSMutableArray *)array
{
    for(UIView *v in view.subviews) {
        if ([v.description hasPrefix:@"<UIKBKeyView"]) {
            [array addObject:v];
        } else {
            [self findKeyViews:v array:array];
        }
        //12個を超えたらreturnする。
        if (array.count > 12) break;        
    }
    return array;
}

- (BOOL)setDoneButtonToNumberPad:(UIView *)view
{
    for(UIView *v in view.subviews) {
        if ([v.description hasPrefix:@"<UIPeripheralHostView"]) {
            float h = v.frame.size.height/4;
            CGRect r = CGRectMake(0, h*3, v.frame.size.width/3, h);
            doneButton.frame = r;
            NSArray *arr = [self findKeyViews:v array:[NSMutableArray arrayWithCapacity:13]];
            //12個の場合がNumber Pad
            if (arr.count == 12) {
                [v addSubview:doneButton];
            }            
            return YES;
        }
    }
    return NO;
}

0 件のコメント: