Objective C implementing a UIPickerView with a "Done" button

33,457

Solution 1

I added a UIToolbar with a UIBarButtonItem for the 'done' button in my xib with the frame set so that it's not initially visible (y value equal to the height of the parent view).

Every time the user access the picker, I changed the frame (the y value) of the UIDatePicker and the UIToolbar with an animation so that it slides up along with the picker from the bottom of the screen similar to the keyboard.

Check out my code below.

- (IBAction)showPicker
{
    if(pickerVisible == NO)
    {
        // create the picker and add it to the view
        if(self.datePicker == nil) self.datePicker = [[[UIDatePicker alloc] initWithFrame:CGRectMake(0, 460, 320, 216)] autorelease];
        [self.datePicker setMaximumDate:[NSDate date]];
        [self.datePicker setDatePickerMode:UIDatePickerModeDate];
        [self.datePicker setHidden:NO];
        [self.view addSubview:datePicker];

        // the UIToolbar is referenced 'using self.datePickerToolbar'
        [UIView beginAnimations:@"showDatepicker" context:nil];
        // animate for 0.3 secs.
        [UIView setAnimationDuration:0.3];

        CGRect datepickerToolbarFrame = self.datePickerToolbar.frame;
        datepickerToolbarFrame.origin.y -= (self.datePicker.frame.size.height + self.datePickerToolbar.frame.size.height);
        self.datePickerToolbar.frame = datepickerToolbarFrame;

        CGRect datepickerFrame = self.datePicker.frame;
        datepickerFrame.origin.y -= (self.datePicker.frame.size.height + self.datePickerToolbar.frame.size.height);
        self.datePicker.frame = datepickerFrame;

        [UIView commitAnimations];
        pickerVisible = YES;
    }
}

- (IBAction)done
{
    if(pickerVisible == YES)
    {
        [UIView beginAnimations:@"hideDatepicker" context:nil];
        [UIView setAnimationDuration:0.3];

        CGRect datepickerToolbarFrame = self.datePickerToolbar.frame;
        datepickerToolbarFrame.origin.y += (self.datePicker.frame.size.height + self.datePickerToolbar.frame.size.height);
        self.datePickerToolbar.frame = datepickerToolbarFrame;

        CGRect datepickerFrame = self.datePicker.frame;
        datepickerFrame.origin.y += (self.datePicker.frame.size.height + self.datePickerToolbar.frame.size.height);
        self.datePicker.frame = datepickerFrame;
        [UIView commitAnimations];

        // remove the picker after the animation is finished
        [self.datePicker performSelector:@selector(removeFromSuperview) withObject:nil afterDelay:0.3];
    }
}

Solution 2

The easiest way to do it is to model it in Interface Builder. It is a UIView containing a UIToolbar and a UIPickerView.

enter image description here

Then create an outlet for the UIView and connect it.

enter image description here

If you then have a UITextField you can assign your custom view to its inputView property.

[self.textField setInputView:self.customPicker];

Alternatively you can add the picker to your main view...

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.customPicker.frame = CGRectMake(0, CGRectGetMaxY(self.view.frame), CGRectGetWidth(self.customPicker.frame), CGRectGetHeight(self.customPicker.frame));
    [self.view addSubview:self.customPicker];
}

... and then use this method to show or hide the picker.

- (void)setPickerHidden:(BOOL)hidden
{
    CGAffineTransform transform = hidden ? CGAffineTransformIdentity : CGAffineTransformMakeTranslation(0, -CGRectGetHeight(self.customPicker.frame));

    [UIView animateWithDuration:0.3 animations:^{
        self.customPicker.transform = transform;
    }];
}

Solution 3

I create a custom class, this supports multiple orientation:

DateTimePicker.h

    @interface DateTimePicker : UIView {
}

@property (nonatomic, assign, readonly) UIDatePicker *picker;

- (void) setMode: (UIDatePickerMode) mode;
- (void) addTargetForDoneButton: (id) target action: (SEL) action;

@end

DateTimePicker.m

#define MyDateTimePickerToolbarHeight 40

@interface DateTimePicker()

@property (nonatomic, assign, readwrite) UIDatePicker *picker;

@property (nonatomic, assign) id doneTarget;
@property (nonatomic, assign) SEL doneSelector;

- (void) donePressed;

@end


@implementation DateTimePicker

@synthesize picker = _picker;

@synthesize doneTarget = _doneTarget;
@synthesize doneSelector = _doneSelector;

- (id) initWithFrame: (CGRect) frame {
    if ((self = [super initWithFrame: frame])) {
        self.backgroundColor = [UIColor clearColor];

        UIDatePicker *picker = [[UIDatePicker alloc] initWithFrame: CGRectMake(0, MyDateTimePickerToolbarHeight, frame.size.width, frame.size.height - MyDateTimePickerToolbarHeight)];
        [self addSubview: picker];

        UIToolbar *toolbar = [[UIToolbar alloc] initWithFrame: CGRectMake(0, 0, frame.size.width, MyDateTimePickerToolbarHeight)];
        toolbar.barStyle = UIBarStyleBlackOpaque;
        toolbar.autoresizingMask = UIViewAutoresizingFlexibleWidth;

        UIBarButtonItem *doneButton = [[UIBarButtonItem alloc] initWithTitle: @"Done" style: UIBarButtonItemStyleBordered target: self action: @selector(donePressed)];
        UIBarButtonItem* flexibleSpace = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil];
        toolbar.items = [NSArray arrayWithObjects:flexibleSpace, doneButton, nil];

        [self addSubview: toolbar];

        self.picker = picker;
        picker.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleTopMargin|UIViewAutoresizingFlexibleBottomMargin;

        self.autoresizesSubviews = YES;
        self.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleTopMargin|UIViewAutoresizingFlexibleBottomMargin;
    }
    return self;
}

- (void) setMode: (UIDatePickerMode) mode {
    self.picker.datePickerMode = mode;
}

- (void) donePressed {
    if (self.doneTarget) {
        [self.doneTarget performSelector:self.doneSelector withObject:nil afterDelay:0];
    }
}

- (void) addTargetForDoneButton: (id) target action: (SEL) action {
    self.doneTarget = target;
    self.doneSelector = action;
}

Using custom view in your view controller:

- (void)viewDidLoad
{
    [super viewDidLoad];
    UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [button addTarget:self
               action:@selector(buttonPressed:)
     forControlEvents:UIControlEventTouchDown];
    [button setTitle:@"Show picker" forState:UIControlStateNormal];
    button.frame = CGRectMake(100, 50, 100, 40.0);
    [self.view addSubview:button];

    CGRect screenRect = [[UIScreen mainScreen] bounds];
    CGFloat screenWidth = screenRect.size.width;
    CGFloat screenHeight = screenRect.size.height;
    picker = [[DateTimePicker alloc] initWithFrame:CGRectMake(0, screenHeight/2 - 35, screenWidth, screenHeight/2 + 35)];
    [picker addTargetForDoneButton:self action:@selector(donePressed)];
    [self.view addSubview:picker];
    picker.hidden = YES;
    [picker setMode:UIDatePickerModeDate];
}

-(void)donePressed {
    picker.hidden = YES;
}

-(void)buttonPressed:(id)sender {
    picker.hidden = NO;
}

Hope this helps. :)

Solution 4

There is a more elegant solution. I'm not sure if this is recent (as of iOS7), but this has been working for me splendidly.

TJDatePicker.h

@protocol TJDatePickerActionDelegate <NSObject>
- (void)cancel:(id)sender;
- (void)done:(id)sender;
@end

@interface TJDatePicker : UIDatePicker
@property (strong, nonatomic) UIView *navInputView;
@property (weak, nonatomic) id<TJDatePickerActionDelegate> actionDelegate;
@end

TJDatePicker.m

#import "TJDatePicker.h"

@interface TJDatePicker ()
@property (strong, nonatomic) TJButton *cancelButton;
@property (strong, nonatomic) TJButton *doneButton;
@end

@implementation TJDatePicker

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self)
    {
        [self updateSubviews];
    }

    return self;
}

- (void)layoutSubviews
{
    [super layoutSubviews];

    [self updateSubviews];
}

- (void)updateSubviews
{
    self.navInputView.frame = CGRectMake(0, 0, self.width, 45);
    self.cancelButton.frame = CGRectMake(5, 5, 80, 35);
    CGFloat width = 80;
    self.doneButton.frame = CGRectMake(CGRectGetMaxX(self.navInputView.frame) - width, self.cancelButton.frame.origin.y, width, self.cancelButton.height);
}

- (UIView *)navInputView
{
    if (!_navInputView)
    {
        _navInputView = [[UIView alloc] init];
        _navInputView.backgroundColor = [UIColor whiteColor];

        self.cancelButton = [UIButton buttonWithType:UIButtonTypeCustom];
        [self.cancelButton setTitle:@"CANCEL" forState:UIControlStateNormal];
        [self.cancelButton addTarget:self action:@selector(cancelButtonPressed) forControlEvents:UIControlEventTouchUpInside];
        [_navInputView addSubview:self.cancelButton];

        self.doneButton = [UIButton buttonWithType:UIButtonTypeCustom];
        [self.doneButton setTitle:@"DONE" forState:UIControlStateNormal];
        [self.doneButton addTarget:self action:@selector(doneButtonPressed) forControlEvents:UIControlEventTouchUpInside];
        [_navInputView addSubview:self.doneButton];
    }

    return _navInputView;
}

- (void)cancelButtonPressed
{
    [self.actionDelegate cancel:self];
}

- (void)doneButtonPressed
{
    [self.actionDelegate done:self];
}

And then at implementation time...

self.datePicker = [[TJDatePicker alloc] init];
self.datePicker.actionDelegate = self;
self.textField.inputAccessoryView = self.datePicker.navInputView;

The key here is to use the inputAccessoryView of the UITextField that you are planning on setting the UIDatePicker to.

Solution 5

You get the "Done" button in the toolbar using the ActionSheetPicker. Its is a great alternative to building it yourself.

https://github.com/skywinder/ActionSheetPicker-3.0

Share:
33,457
user1051935
Author by

user1051935

Updated on July 09, 2022

Comments

  • user1051935
    user1051935 almost 2 years

    I am trying to implement a "Done" button in a UIPickerView Similar to the one under this link

    I looked in the class reference but I couldn t find it

    Thanks