iOS – UIPickerViewを下から動的に表示する方法

UIPickerViewの実装方法はすでにネット上の様々な記事で紹介されていると思いますが、このメモではStoryboardとARCを使用している環境でのUIPickerViewの実装方法をサンプルプログラムを通して紹介します。

サンプルプログラムの動作イメージは以下の動画をご覧ください。

 

このメモに登場するサンプルプログラムのソースは以下からダウンロードできます。
[サンプルプログラムのソース]

 

1.Picker View用のView Controllerを用意する
pickerviewcontrollerStoryboard上でView Controllerを新たに追加し、左図のように、Picker View(参照名:picker)とボタン(参照名:closeButton)を追加します。

そしてPickerViewControllerというクラスファイルを新規作成し、このView Controllerに関連付けます。関連付けはStoryboard上でView Controllerを選択し、右に表示されているIdentity inspectorのCustom ClassにPickerViewControllerと入力することで行えます。

このPickerViewControllerはモーダルで表示します。このとき、closeButtonが配置されている画面上部を透明化して呼び出し元の親画面が見えるようにしたいと思います。そのために以下の2つの設定を行います。

1.closeButtonのTypeを"Custom"にする

2.このViewのBackgroundを"Default"(透明)にする

これらはどちらもStoryboard上のinspectorの部分で行えます。注意点は背景を透明化する場合、ViewのAlphaでなく、Backgroundに設定する値を変えるという点です。Alpha値を小さくするとそのViewに載っているcloseButtonやpickerまでも透明化されてしまいます。

set_storyboard_id最後にPickerViewControllerにStoryboard IDを設定します。これも右図のように、Identity inspectorから設定できます。値は"PickerViewController"にしておきましょう。このIDは後でこの画面の呼び出し元からPickerViewControllerの参照を取得する際に使用します。

これでPickerViewControllerに対するStoryboard上での作業は完了です。

 

2.PickerViewControllerを実装する
PickerViewController.hをソースを以下のようにします。

#import <UIKit/UIKit.h>

@protocol PickerViewControllerDelegate;

@interface PickerViewController : UIViewController <UIPickerViewDelegate, UIPickerViewDataSource>

@property (weak, nonatomic) IBOutlet UIPickerView *picker;

// 空の領域にある透明なボタン
@property (weak, nonatomic) IBOutlet UIButton *closeButton;

// 処理のデリゲート先の参照
@property (weak, nonatomic) id<PickerViewControllerDelegate> delegate;

// PickerViewを閉じる処理を行うメソッド。closeButtonが押下されたときに呼び出される
- (IBAction)closePickerView:(id)sender;

@end

@protocol PickerViewControllerDelegate <NSObject>
// 選択された文字列を適用するためのデリゲートメソッド
-(void)applySelectedString:(NSString *)str;
// 当該PickerViewを閉じるためのデリゲートメソッド
-(void)closePickerView:(PickerViewController *)controller;
@end

上記のソースを簡単に解説します。まず、PickerViewControllerクラスにUIPickerViewDelegateプロトコルUIPickerViewDataSourceプロトコルを採用しています。このプロトコルに定義されているデリゲートメソッドを本クラスで実装することで、Pickerから送られるイベントを本クラスで処理できるようになります。これらについてはPickerViewController.mファイルで詳しく解説します。

またPickerViewControllerDelegateプロトコルを定義しました。このプロトコルを後述する呼び出し元のMainViewControllerに採用させ、PickerViewで選択した値の反映や、PickerViewを非表示にする処理を行わせます。

そして、closePickerViewメソッドを定義しました。closeButtonが押下されたときにこのメソッドが呼び出されるようにしてください。

 

次が PickerViewController.mファイルです。

#import "PickerViewController.h"

@interface PickerViewController ()

@end

@implementation PickerViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    // PickerViewのデリゲート先とデータソースをこのクラスに設定
    self.picker.delegate = self;
    self.picker.dataSource = self;
}

// PickerViewで要素が選択されたときに呼び出されるメソッド
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
    // デリゲート先の処理を呼び出し、選択された文字列を親Viewに表示させる
    [self.delegate applySelectedString:[NSString stringWithFormat:@"%d", row]];
}

// PickerViewの列数を指定するメソッド
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView*)pickerView {
    return 1;
}

// PickerViewに表示する行数を指定するメソッド
-(NSInteger)pickerView:(UIPickerView*)pickerView numberOfRowsInComponent:(NSInteger)component {
    return 10;
}

// PickerViewの各行に表示する文字列を指定するメソッド
-(NSString*)pickerView:(UIPickerView*)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component{
    return [NSString stringWithFormat:@"%d", row];
}

// 空の領域にある透明なボタンがタップされたときに呼び出されるメソッド
- (IBAction)closePickerView:(id)sender {
    // PickerViewを閉じるための処理を呼び出す
    [self.delegate closePickerView:self];
}

@end

上記のソースでは、UIPickerViewDelegateプロトコルとUIPickerViewDataSourceプロトコルのデリゲート先をPickerViewControllerクラスに設定し、それらのデリゲートメソッドを実装しています。

また、PickerViewの行が選択されたときには、その文字列を引数としてPickerViewControllerDelegateプロトコルのデリゲートメソッドを実行します。このデリゲートメソッドの実装はこのViewの呼び出し元である親のViewController上に実装します。これについては後述します。

最後に透明なボタンがタップされた場合の処理をclosePickerViewメソッドに記述しています。ここでもこのViewの呼び出し元である親のViewControllerで実装するデリゲートメソッドを呼び出しています。

 

3.PickerViewControllerを呼び出すMainViewControllerを用意する
mainviewcontroller左図のようなアプリ起動後に最初に表示されるViewを制御するMainViewControllerを作成します。

MainViewController上の「選択」ボタンをタップするとPickerViewControllerが呼び出され、PickerViewが表示されるようにします。また、PickerView上で文字列が選択された場合はその文字列をMainViewController上のラベルに表示するようにします。

 

 

4.MainViewControllerを実装する
MainViewController.hのソースです。

#import <UIKit/UIKit.h>
#import "PickerViewController.h"

@interface MainViewController : UIViewController <PickerViewControllerDelegate>

// 「選択」ボタン
@property (weak, nonatomic) IBOutlet UIButton *selectButton;
// PickerViewで選択された文字列を表示するラベル
@property (weak, nonatomic) IBOutlet UILabel *selectedStringLabel;

// 呼び出すPickerViewControllerのポインタ ※strongを指定してポインタを掴んでおかないと解放されてしまう
@property (strong, nonatomic) PickerViewController *pickerViewController;

// 「選択」ボタンがタップされたときに呼び出されるメソッド
- (IBAction)openPickerView:(id)sender;

@end

まずポイントとなるのがPickerViewController.hで宣言したPickerViewControllerDelegateプロトコルを採用しているところです。MainViewControllerクラスでこのプロトコルのデリゲートメソッドを実装し、PickerViewController上のイベント処理するわけです。

次にポイントとなるのが、PickerViewControllerのポインタをプロパティとして定義しているところです。プロパティ属性としてweakではなく、strongを指定しています。これはARCを使用しており、PickerViewControllerのインスタンスへの参照をどこかで「掴んで」いないとPickerViewControllerのインスタンスが勝手に解放されてしまうからです。(たぶん。このあたりの理解は実は曖昧です。。)

 

次がMainViewController.mのソースです。

#import "MainViewController.h"
#import "TWAppDelegate.h"

@interface MainViewController ()

@end

@implementation MainViewController

// 「選択」ボタンがタップされたときに呼び出されるメソッド
- (IBAction)openPickerView:(id)sender {
    // PickerViewControllerのインスタンスをStoryboardから取得し
    self.pickerViewController = [[self storyboard] instantiateViewControllerWithIdentifier:@"PickerViewController"];
    self.pickerViewController.delegate = self;

    // PickerViewをサブビューとして表示する
    // 表示するときはアニメーションをつけて下から上にゆっくり表示させる

    // アニメーション完了時のPickerViewの位置を計算
    UIView *pickerView = self.pickerViewController.view;
    CGPoint middleCenter = pickerView.center;

    // アニメーション開始時のPickerViewの位置を計算
    UIWindow* mainWindow = (((TWAppDelegate*) [UIApplication sharedApplication].delegate).window);
    CGSize offSize = [UIScreen mainScreen].bounds.size;
    CGPoint offScreenCenter = CGPointMake(offSize.width / 2.0, offSize.height * 1.5);
    pickerView.center = offScreenCenter;

    [mainWindow addSubview:pickerView];

    // アニメーションを使ってPickerViewをアニメーション完了時の位置に表示されるようにする
    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDuration:0.5];
    pickerView.center = middleCenter;
    [UIView commitAnimations];
}

// PickerViewのある行が選択されたときに呼び出されるPickerViewControllerDelegateプロトコルのデリゲートメソッド
- (void)applySelectedString:(NSString *)str
{
    self.selectedStringLabel.text = str;
}

// PickerViewController上にある透明ボタンがタップされたときに呼び出されるPickerViewControllerDelegateプロトコルのデリゲートメソッド
- (void)closePickerView:(PickerViewController *)controller
{
    // PickerViewをアニメーションを使ってゆっくり非表示にする
    UIView *pickerView = controller.view;

    // アニメーション完了時のPickerViewの位置を計算
    CGSize offSize = [UIScreen mainScreen].bounds.size;
    CGPoint offScreenCenter = CGPointMake(offSize.width / 2.0, offSize.height * 1.5);

    [UIView beginAnimations:nil context:(void *)pickerView];
    [UIView setAnimationDuration:0.3];
    [UIView setAnimationDelegate:self];
    // アニメーション終了時に呼び出す処理を設定
    [UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:context:)];
    pickerView.center = offScreenCenter;
    [UIView commitAnimations];
}

// 単位のPickerViewを閉じるアニメーションが終了したときに呼び出されるメソッド
- (void)animationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context
{
    // PickerViewをサブビューから削除
    UIView *pickerView = (__bridge UIView *)context;
    [pickerView removeFromSuperview];
}

@end

まずopenPickerViewメソッドですが、一番のポイントはinstantiateViewControllerWithIdentifierメソッドを使用してStoryboard IDからPickerViewControllerのインスタンスへの参照を取得しているところです。ここで、[[PickerViewController alloc] init]などとしてしまうとPickerViewやボタンがない画面が表示されてしまうのでご注意を。
そして、PickerViewControllerのViewをメインスクリーンのサブビューとし、アニメーションを使って下から上に表示されるようにしています。

また、PickerViewControllerDelegateプロトコルのデリゲートメソッドを実装しています。applySelectedStringメソッドでPickerViewから渡された文字列をMainViewController上のラベルに表示しています。closeUnitPickerViewメソッドではアニメーションを使ってPickerViewを非表示にしています。また、アニメーション完了時にremoveFromSuperviewメソッドを呼び出して自身をメインスクリーンのサブビューから外しています。尚、アニメーション完了時に実行するメソッドを@selector(animationDidStop:finished:context:)として指定しましたが、この書式を守らないと正常に動作しないので注意してください。

以上で実装は完了です!ちゃんと動きましたか?

このメモに登場するサンプルプログラムのソースは以下からダウンロードできます。
[サンプルプログラムのソース]

 

いやー今回は割と長編でした。本屋さんで見かけるプログラミングの解説書は300ページを超えるものが多いですが、相当なエネルギーを使って書いてるんでしょうね。頭が下がります。

 

環境:
XCode 4.6
ARCとStoryboardを使用

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です