[iPhone] 下からニュッと出てくるDatePicker

Standard

上司からiPhoneアプリのtipsをまとめろとの指示を受けましたが、書くところないのでここに。
 
第1弾は簡単なところから。
 
screenshot

screenshot

日付を設定するボタンを押すと

 
screenshot

screenshot

 
な感じに画面の下からDatePickerがニュッと出てくる、誰もが一度は実装したことがあるであろうアレです。
 
 
やり方はいろいろだと思いますが、ボクがやってるのは

  1. DatePicker用のUIViewControllerを用意
  2. DatePickerを表示したいControllerから↑のcontrollerをalloc initして、そのインスタンスのviewを自分にadd subView
  3. [UIView commitAnimation]でニュッとやる

 
っていう感じです。うえからイキマス
 
 
DatePicker用のUIViewControllerを用意
 
XCodeのプロジェクトの下で [追加] → [新規ファイル]からUIViewControllerクラスのサブクラスを追加。XIBファイルも同時に作ります。できたらこんな感じに編集。
 
ModalDatePickerViewController.h

#import <UIKit/UIKit.h>


@class ModalDatePickerViewController;
@protocol ModalDatePickerViewControllerDelegate

- (void)didCommitButtonClicked:(ModalDatePickerViewController *)controller selectedDate:(NSDate *)selectedDate pickerName:(NSString *)pickerName;
- (void)didCancelButtonClicked:(ModalDatePickerViewController *)controller pickerName:(NSString *)pickerName;
- (void)didClearButtonClicked:(ModalDatePickerViewController *)controller pickerName:(NSString *)pickerName;

@end

@interface ModalDatePickerViewController : UIViewController {
    NSString *pickerName_;
    NSDate *dispDate_;
    IBOutlet UIDatePicker *picker;
    IBOutlet UIButton *commitButton;
    IBOutlet UIButton *cancelButton;
    IBOutlet UIButton *clearButton;
    id<ModalDatePickerViewControllerDelegate>delegate;
}

- (IBAction)commitButtonClicked:(id)sender;
- (IBAction)cancelButtonClicked:(id)sender;
- (IBAction)clearButtonClicked:(id)sender;

@property (nonatomic, assign) id<ModalDatePickerViewControllerDelegate> delegate;
@property (nonatomic, retain) NSString *pickerName_;
@property (nonatomic, retain) NSDate *dispDate_;

@end

 
ModalDatePickerViewController.m

#import "ModalDatePickerViewController.h"


@implementation ModalDatePickerViewController
@synthesize delegate, pickerName_, dispDate_;


- (id) init {
    if ((self = [self initWithNibName:@"ModalDatePickerViewController" bundle:nil])) {
    }
    return self;
}

// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
    [super viewDidLoad];
    picker.datePickerMode = UIDatePickerModeDate;
}

- (void)viewDidAppear:(BOOL)animated {
    if (self.dispDate_ != nil) {
        [picker setDate:self.dispDate_];
    }
}

/*
 // Override to allow orientations other than the default portrait orientation.
 - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
 // Return YES for supported orientations
 return (interfaceOrientation == UIInterfaceOrientationPortrait);
 }
 */

- (IBAction)commitButtonClicked:(id)sender {
    [self.delegate didCommitButtonClicked:self selectedDate:picker.date pickerName:self.pickerName_];
}

- (IBAction)cancelButtonClicked:(id)sender {
    [self.delegate didCancelButtonClicked:self pickerName:self.pickerName_];
}

- (IBAction)clearButtonClicked:(id)sender {
    [self.delegate didClearButtonClicked:self pickerName:self.pickerName_];
}

- (void)didReceiveMemoryWarning {
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];
    
    // Release any cached data, images, etc that aren't in use.
}

- (void)viewDidUnload {
    [super viewDidUnload];
    [pickerName_ release];
    pickerName_ = nil;
    [dispDate_ release];
    dispDate_ = nil;
}

- (void)dealloc {
    [pickerName_ release];
    [dispDate_ release];
    [super dealloc];
}

@end

 
表示時に選択される日付と、呼び出し側のコントローラーでPickerを表示したい箇所が複数あるとき用に自分の名前をプロパティに持ってます。今回は日付だけのPickerで表示していますが、時間、または日付+時間のPickerも表示したいときはプロパティにUIDatePickerModeを追加してコントローラ側で設定するようにすればいいと思います。あとは、「日付を選択」「キャンセル」「選択した日付を削除」するためにdelegateを作ってプロトコルに入れときます。

IBで
 
screenshot

screenshot

 
みたいな感じのを作ってOutletとActionをくっつけます。ViewのサイズをメインのViewと同じにして背景をClear Colorにしておけば日付選んでるときに他の操作しないで欲しいんですけどコラッ な思いを形にできます。3つのボタンのところは、ToolbarにBar Button Itemを追加していきます。Bar Button Itemを単純に追加していくと端からお詰まりんぐしていくので、Flexible Space Bar Button Itemってので間隔をとります。
 
screenshot

screenshot

 
 
呼び出し側でニュッとやる
 
メインViewのコントローラを作ります。IBで
 
screenshot

screenshot

 
な感じのを作ったとしたら、コントローラはこんな感じ。
 
ModalDatePickerDemoViewController.h

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

@interface ModalDatePickerDemoViewController : UIViewController <ModalDatePickerViewControllerDelegate> {
    ModalDatePickerViewController *datePickerController;
    NSDate *dateForDate1;
    NSDate *dateForDate2;
    IBOutlet UILabel *labelForDate1;
    IBOutlet UILabel *labelForDate2;
    IBOutlet UIButton *buttonForDate1;
    IBOutlet UIButton *buttonForDate2;
}

- (IBAction) buttonForDate1Clicked:(id)sender;
- (IBAction) buttonForDate2Clicked:(id)sender;

@property (nonatomic,retain) NSDate *dateForDate1;
@property (nonatomic,retain) NSDate *dateForDate2;

@end

さっき作ったModalDatePickerViewControllerDelegateを実装します。
 

ModalDatePickerDemoViewController.m

#import "ModalDatePickerDemoViewController.h"
#import "ModalDatePickerDemoAppDelegate.h"

@implementation ModalDatePickerDemoViewController
@synthesize dateForDate1,dateForDate2;


// Use this to show the modal view (pops-up from the bottom)
- (void) showModal:(UIView*) modalView {
    UIWindow* mainWindow = (((ModalDatePickerDemoAppDelegate*) [UIApplication sharedApplication].delegate).window);    
    CGPoint middleCenter = modalView.center;
    CGSize offSize = [UIScreen mainScreen].bounds.size;
    CGPoint offScreenCenter = CGPointMake(offSize.width / 2.0, offSize.height * 1.5);
    modalView.center = offScreenCenter; // we start off-screen
    [mainWindow addSubview:modalView];    // Show it with a transition effect
    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDuration:0.5]; // animation duration in seconds
    modalView.center = middleCenter;
    [UIView commitAnimations];
}

// Use this to slide the semi-modal view back down.
- (void) hideModal:(UIView*) modalView {
    CGSize offSize = [UIScreen mainScreen].bounds.size;
    CGPoint offScreenCenter = CGPointMake(offSize.width / 2.0, offSize.height * 1.5);
    [UIView beginAnimations:nil context:modalView];
    [UIView setAnimationDuration:0.3];
    [UIView setAnimationDelegate:self];
    [UIView setAnimationDidStopSelector:@selector(hideModalEnded:finished:context:)];
    modalView.center = offScreenCenter;
    [UIView commitAnimations];
}

- (void) hideModalEnded:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
    UIView* modalView = (UIView *)context;
    [modalView removeFromSuperview];
}

- (IBAction) buttonForDate1Clicked:(id)sender {
    datePickerController = [[ModalDatePickerViewController alloc] init];
    datePickerController.pickerName_ = @"pickerOfDate1";
    datePickerController.dispDate_ = (self.dateForDate1 != nil)?self.dateForDate1:[NSDate date];
    datePickerController.delegate = self;
    [self showModal:datePickerController.view];
    
}

- (IBAction) buttonForDate2Clicked:(id)sender {
    datePickerController = [[ModalDatePickerViewController alloc] init];
    datePickerController.pickerName_ = @"pickerOfDate2";
    datePickerController.dispDate_ = (self.dateForDate2 != nil)?self.dateForDate2:[NSDate date];
    datePickerController.delegate = self;
    [self showModal:datePickerController.view];    
}

#pragma mark -
#pragma mark ModalDatePickerViewController delegate

- (void)didCommitButtonClicked:(ModalDatePickerViewController *)controller selectedDate:(NSDate *)selectedDate pickerName:(NSString *)pickerName {
    [self hideModal:datePickerController.view];
    [datePickerController release];
    datePickerController = nil;
    
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    [formatter setDateFormat:@"yyyy-MM-dd"];
    if ([pickerName isEqualToString:@"pickerOfDate1"]) {
        self.dateForDate1 = selectedDate;
        labelForDate1.text = [formatter stringFromDate:self.dateForDate1];
    } else if ([pickerName isEqualToString:@"pickerOfDate2"]) {
        self.dateForDate2 = selectedDate;
        labelForDate2.text = [formatter stringFromDate:self.dateForDate2];
    }
    [formatter release];
}

- (void)didCancelButtonClicked:(ModalDatePickerViewController *)controller pickerName:(NSString *)pickerName {
    [self hideModal:datePickerController.view];
    [datePickerController release];
    datePickerController = nil;
}

- (void)didClearButtonClicked:(ModalDatePickerViewController *)controller pickerName:(NSString *)pickerName {
    [self hideModal:datePickerController.view];
    [datePickerController release];
    datePickerController = nil;
    
    if ([pickerName isEqualToString:@"pickerOfDate1"]) {
        self.dateForDate1 = nil;
        labelForDate1.text = @"";
    } else if ([pickerName isEqualToString:@"pickerOfDate2"]) {
        self.dateForDate2 = nil;
        labelForDate2.text = @"";
    }
}

- (void)didReceiveMemoryWarning {
	// Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];
	
	// Release any cached data, images, etc that aren't in use.
}

- (void)viewDidUnload {
	[dateForDate1 release];
    dateForDate1 = nil;
    [dateForDate2 release];
    dateForDate2 = nil;
}


- (void)dealloc {
    [dateForDate1 release];
    [dateForDate2 release];
    [super dealloc];
}

@end

 
ModalDatePickerViewControllerをプロパティとして持っておいて、表示させたいタイミングでalloc init。
- (void) showModal:(UIView*) modalView
メソッドにそのViewを渡します。showModalメソッドでは、受け取ったViewをいったん見えない状態にしてからメインwindowにaddSubview。
[UIView beginAnimations:nil context:nil];
 〜 
[UIView commitAnimations];
のおなじみパターンで表示状態を変えていきます。ここでは下からニュッとやりたいので、addSubviewしたタイミングではviewをwindowサイズよりも下に配置しておいて、0.5秒かけて真ん中に移動させています。
あとは、ModalDatePickerViewControllerDelegateのそれぞれでやりたいことをやった後にhideModalで再度画面の下に移動させた後、ModalDatePickerViewControllerインスタンスを解放してあげればニュッと出てくるアレの完成です。
 
 
UIPickerView
 
UIPickerViewの場合もほぼ同じ感じで作っていけばよいですが、UIPickerViewの場合はコントローラで、pickerが変更されたときの処理を実装するために、UIPickerViewDelegateの

- (void)pickerView:(UIPickerView *)pickerView
                didSelectRow:(NSInteger)row
                inComponent:(NSInteger)component;

を実装することになります。んで、変更を確定したいときにDatePickerと同じくdelegateを呼び出し側のコントローラに投げるわけですが、そのタイミングで、↑のUIPickerViewDelegateの矛先を↓な感じで、selfから逸らしてあげる必要があります。
 

- (IBAction)commitButtonClicked:(id)sender {
    [self.delegate didPickerSelected:self selectedItem:_seledtedItem_ pickerName:pickerName_];
    picker_.delegate = nil;
    picker_.dataSource = nil;
}

 
datePickerのときもやっとくべきですが、これをやっとかないと「pickerを勢いよく回転させてすぐ確定」とかやると(呼び出し側でpickerのコントローラをreleaseする処理があった場合)EXEC_BAD_ACCESSで落ちたりします。当たり前のことですがしょっちゅー忘れるので。。

Facebook comments:

3 thoughts on “[iPhone] 下からニュッと出てくるDatePicker

  1. I think other web-site proprietors should take this web
    site as an model, very clean and wonderful user genial style and
    design, let alone the content. You’re an expert in this topic!

コメントを残す

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

次のHTML タグと属性が使えます: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>