読者です 読者をやめる 読者になる 読者になる

UIPickerView をキーボードのように表示・非表示させる

環境:iOS Deployment Target 7.1

やりたいこと:
ボタンタップ時にキーボードが下からシュッと出てくるようにピッカーを表示させたい。
完了ボタンのタップで決定、ピッカー以外の部分をタップした時にはピッカーを隠す。

こんなかんじ。

f:id:xyk:20141002105030g:plain

やってること

ピッカー呼び出しボタンタップ時

  • キャンセル用のビューを画面全体に被せる。ターゲット/アクションデザインパターンを使ってターゲットとアクションを設定している
  • 画面外に作成していたピッカーと完了ボタンをアニメーションで画面内に移動させ表示

を行う。
完了ボタンのタップで選択中ピッカーを取得してピッカーを隠す。
ピッカー外のキャンセルビューをタップでピッカーを隠す。

サンプルコード

OverlayView.h

#import <UIKit/UIKit.h>

@interface OverlayView : UIView
@property(nonatomic, weak) id target;
@property(nonatomic, assign) SEL action;
@end

OverlayView.m

#import "OverlayView.h"

@implementation OverlayView

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self.target performSelector:self.action withObject:self afterDelay:0.0f];
}

@end

PickerViewController.h

#import <UIKit/UIKit.h>

@interface PickerViewController : UIViewController
@end

PickerViewController.m

#import "PickerViewController.h"
#import "OverlayView.h"

@interface PickerViewController ()
<
UIPickerViewDelegate,
UIPickerViewDataSource
>

@property(nonatomic, strong) UIView *areaView;
@property(nonatomic, strong) UIPickerView *areaPickerView;
@property(nonatomic, strong) OverlayView *overlayView;

@property(nonatomic, strong) NSArray *areaList;
@property(nonatomic, strong) UILabel *areaLabel;

@end

@implementation PickerViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.areaList = @[
                      @[@"1", @"北海道"],
                      @[@"2", @"東北"],
                      @[@"3", @"関東・信越"],
                      @[@"4", @"東海・北陸・近畿"],
                      @[@"5", @"中国・四国"],
                      @[@"6", @"九州"],
                      @[@"7", @"沖縄"],
                      ];

    // ピッカー呼び出しボタン作成
    UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 100, 50)];
    button.center = CGPointMake(self.view.bounds.size.width / 2, 100);
    button.backgroundColor = [UIColor greenColor];
    [button setTitle:@"エリア選択" forState:UIControlStateNormal];
    [button addTarget:self action:@selector(showAreaView:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];

    // エリア表示用ラベルの作成
    self.areaLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 150, 50)];
    self.areaLabel.center = CGPointMake(self.view.bounds.size.width / 2, 150);
    self.areaLabel.textAlignment = NSTextAlignmentCenter;
    [self.view addSubview:self.areaLabel];

    // キャンセル用ビューの作成
    self.overlayView = [[OverlayView alloc] initWithFrame:CGRectZero];
    self.overlayView.target = self;
    self.overlayView.action = @selector(hideAreaView);
    self.overlayView.backgroundColor = [UIColor blackColor];
    self.overlayView.alpha = 0.4;
    [self.view addSubview:self.overlayView];

    // エリア選択ピッカーの作成
    [self buildAreaPickerView];
}

- (void)showAreaView:(UIButton*)button
{
    [self.view bringSubviewToFront:_overlayView]; // 最前面に移動
    [self.view bringSubviewToFront:_areaView]; // 最前面に移動
    self.overlayView.frame = [[UIScreen mainScreen] bounds];
    [UIView animateWithDuration:.20 animations:^{
        self.areaView.transform = CGAffineTransformMakeTranslation(0, -(AREA_PICKER_ACCESSORY_HEIGHT + AREA_PICKER_HEIGHT));
    }];
}

- (void)hideAreaView
{
    [UIView animateWithDuration:.20 animations:^{
        self.areaView.transform = CGAffineTransformIdentity;
    }];
    self.overlayView.frame = CGRectZero;
}

static const float AREA_PICKER_ACCESSORY_HEIGHT = 44;
static const float AREA_PICKER_HEIGHT = 216;

- (void)buildAreaPickerView
{
    float height = self.view.bounds.size.height;
    float width = self.view.bounds.size.width;

    // 1. アクセサリビューとピッカービューを乗せるビューの作成
    float areaViewHeight = AREA_PICKER_ACCESSORY_HEIGHT + AREA_PICKER_HEIGHT;
    self.areaView = [[UIView alloc] initWithFrame:CGRectMake(0,
                                                             height,
                                                             width,
                                                             areaViewHeight)];
    [self.view addSubview:self.areaView];

    // 2-1. アクセサリビュー作成
    UIView *areaPickerAccessoryView =
        [[UIView alloc] initWithFrame:CGRectMake(0,
                                                 0,
                                                 width,
                                                 AREA_PICKER_ACCESSORY_HEIGHT)];
    areaPickerAccessoryView.backgroundColor = [UIColor greenColor];

    // 2-2. 決定ボタン作成
    const float DONE_BUTTON_WEDTH = 80;
    UIButton *doneBtn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    doneBtn.frame = CGRectMake(width - DONE_BUTTON_WEDTH,
                               4,
                               DONE_BUTTON_WEDTH,
                               36);
    doneBtn.backgroundColor = [UIColor whiteColor];
    [doneBtn setTitle:@"完了" forState:UIControlStateNormal];
    [doneBtn addTarget:self
                action:@selector(performAreaDoneButtonAction)
      forControlEvents:UIControlEventTouchUpInside];
    [areaPickerAccessoryView addSubview:doneBtn];
    [self.areaView addSubview:areaPickerAccessoryView];

    // 3. ピッカー作成
    self.areaPickerView =
        [[UIPickerView alloc] initWithFrame:CGRectMake(0,
                                                       AREA_PICKER_ACCESSORY_HEIGHT,
                                                       width,
                                                       AREA_PICKER_HEIGHT)];
    self.areaPickerView.backgroundColor = [UIColor whiteColor];
    self.areaPickerView.delegate = self;
    self.areaPickerView.dataSource = self;
    [self.areaPickerView selectRow:2 inComponent:0 animated:NO]; // 初期値設定
    [self.areaView addSubview:self.areaPickerView];
}

- (void)performAreaDoneButtonAction
{
    NSInteger row = [self.areaPickerView selectedRowInComponent:0];
    NSLog(@"areaCode:%@, areaName:%@", self.areaList[row][0], self.areaList[row][1]);
    self.areaLabel.text = self.areaList[row][1];
    [self hideAreaView];
}

// UIPickerViewDataSource, UIPickerViewDelegate delegate
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
    return 1;
}

- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
{
    return self.areaList.count;
}

- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
    return self.areaList[row][1];
}

@end

※追記

もっと簡単なやり方を書いた。
UITextField の inputView に UIPickerView を設定する