Question

I used the amended DateCell code from the following link DateCell without storyboard.

I want to replace UIDatePicker with UIPickerView. Below is the code that I have amended/commented to make it work for UIPickerView.

On each cell tap, I'm able to show UIPickerView but it only loads once and for the rest of the cells UIPickerView with same row titles appears and it crashes when I select a row from UIPickerView. I'm stuck into this for last couple of days.

Any help would be highly appreciated.

MoreTableViewController.h

#import <UIKit/UIKit.h>

@interface MoreTableViewController : UITableViewController<UIPickerViewDataSource, UIPickerViewDelegate>

@property(nonatomic,retain)NSArray *ASOR;
@property(nonatomic,retain)NSArray *ASQT;
@property(nonatomic,retain)NSArray *respectiveOptions;

@end

MoreTableViewController.m

#import "MoreTableViewController.h"
#import "AppDelegate.h"

#define kPickerAnimationDuration    0.40   // duration for the animation to slide the date picker into view
#define kDatePickerTag              99     // view tag identifiying the date picker view

//#define kTitleKey       @"title"   // key for obtaining the data source item's title
//#define kDateKey        @"date"    // key for obtaining the data source item's date value

// keep track of which rows have date cells
#define kDateStartRow   1
#define kDateEndRow     2

static NSString *kDateCellID = @"dateCell";     // the cells with the start or end date
static NSString *kDatePickerID = @"datePicker"; // the cell containing the date picker
static NSString *kOtherCell = @"otherCell";     // the remaining cells at the end

#pragma mark -

@interface MoreTableViewController ()

@property (nonatomic, strong) NSArray *dataArray;
//@property (nonatomic, strong) NSDateFormatter *dateFormatter;

// keep track which indexPath points to the cell with UIDatePicker
@property (nonatomic, strong) NSIndexPath *datePickerIndexPath;

@property (assign) NSInteger pickerCellRowHeight;

@property (nonatomic, strong) UIPickerView *pickerView;

@end

@implementation MoreTableViewController
@synthesize ASOR, ASQT, respectiveOptions;

- (UITableViewCell *) createCellWithIdetifier:(NSString *)cellId {
    NSArray *reusableUiComponents = [[NSBundle mainBundle] loadNibNamed:@"ReusableUIComponents" owner:self options:nil];

    if ([kDateCellID isEqualToString:cellId]) {
        return reusableUiComponents[0];
    }
    if ([kDatePickerID isEqualToString:cellId]) {
        return reusableUiComponents[1];
    }
//    if ([kOtherCell isEqualToString:cellId]) {
//        return reusableUiComponents[2];
//    }
    return nil;
}

- (AppDelegate *)appDelegate {
    return (AppDelegate *)[[UIApplication sharedApplication] delegate];
}

- (id)initWithStyle:(UITableViewStyle)style
{
    self = [super initWithStyle:style];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)setupDataSource {
    // setup our data source
//    NSMutableDictionary *itemOne = [@{ kTitleKey : @"Tap a cell to change its date:" } mutableCopy];
//    NSMutableDictionary *itemTwo = [@{ kTitleKey : @"Start Date",
//                                       kDateKey : [NSDate date] } mutableCopy];
//    NSMutableDictionary *itemThree = [@{ kTitleKey : @"End Date",
//                                         kDateKey : [NSDate date] } mutableCopy];
//    NSMutableDictionary *itemFour = [@{ kTitleKey : @"(other item1)" } mutableCopy];
//    NSMutableDictionary *itemFive = [@{ kTitleKey : @"(other item2)" } mutableCopy];
    self.dataArray = [[self appDelegate]advanceSearchQuestionsText];

    //self.dateFormatter = [[NSDateFormatter alloc] init];
    //[self.dateFormatter setDateStyle:NSDateFormatterShortStyle];    // show short-style date format
    //[self.dateFormatter setTimeStyle:NSDateFormatterNoStyle];

    // obtain the picker view cell's height, works because the cell was pre-defined in our storyboard
    UITableViewCell *pickerViewCellToCheck = [self createCellWithIdetifier:kDatePickerID];
    self.pickerCellRowHeight = pickerViewCellToCheck.frame.size.height;
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    [self setupDataSource];

    //self.title = @"DateCell";

    // Uncomment the following line to preserve selection between presentations.
    // self.clearsSelectionOnViewWillAppear = NO;

    // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
    // self.navigationItem.rightBarButtonItem = self.editButtonItem;

    ASQT = [[NSArray alloc]init];
    ASOR = [[NSArray alloc]init];
    ASQT = [[self appDelegate]advanceSearchQuestionsText];
    ASOR = [[self appDelegate]advanceSearchOptionsArray];
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

#pragma mark - Utilities

/*! Returns the major version of iOS, (i.e. for iOS 6.1.3 it returns 6)
 */
NSUInteger UNUSED_DeviceSystemMajorVersion() // TODO - move this to Utils
{
    static NSUInteger _deviceSystemMajorVersion = -1;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        _deviceSystemMajorVersion = [[[[[UIDevice currentDevice] systemVersion] componentsSeparatedByString:@"."] objectAtIndex:0] intValue];
    });

    return _deviceSystemMajorVersion;
}

/*! Determines if the given indexPath has a cell below it with a UIDatePicker.

 @param indexPath The indexPath to check if its cell has a UIDatePicker below it.
 */
- (BOOL)hasPickerForIndexPath:(NSIndexPath *)indexPath
{
    BOOL hasDatePicker = NO;

    NSInteger targetedRow = indexPath.row;
    targetedRow++;

    UITableViewCell *checkDatePickerCell =
    [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:targetedRow inSection:0]];
    UIPickerView *checkDatePicker = (UIPickerView *)[checkDatePickerCell viewWithTag:kDatePickerTag];

    hasDatePicker = (checkDatePicker != nil);
    return hasDatePicker;
}

/*! Updates the UIDatePicker's value to match with the date of the cell above it.
 */
- (void)updateDatePicker
{
    if (self.datePickerIndexPath != nil)
    {
        UITableViewCell *associatedDatePickerCell = [self.tableView cellForRowAtIndexPath:self.datePickerIndexPath];

        UIPickerView *targetedDatePicker = (UIPickerView *)[associatedDatePickerCell viewWithTag:kDatePickerTag];
        if (targetedDatePicker != nil)
        {
            // we found a UIDatePicker in this cell, so update it's date value
            //
//            NSDictionary *itemData = self.dataArray[self.datePickerIndexPath.row - 1];
//            [targetedDatePicker setDate:[itemData valueForKey:kDateKey] animated:NO];

            // set the call action for the date picker to dateAction
//            [targetedDatePicker addTarget:self action:@selector(dateAction:) forControlEvents:UIControlEventValueChanged];
        }
    }
}

/*! Determines if the UITableViewController has a UIDatePicker in any of its cells.
 */
- (BOOL)hasInlineDatePicker
{
    return (self.datePickerIndexPath != nil);
}

/*! Determines if the given indexPath points to a cell that contains the UIDatePicker.

 @param indexPath The indexPath to check if it represents a cell with the UIDatePicker.
 */
- (BOOL)indexPathHasPicker:(NSIndexPath *)indexPath
{
    return ([self hasInlineDatePicker] && self.datePickerIndexPath.row == indexPath.row);
}

/*! Determines if the given indexPath points to a cell that contains the start/end dates.

 @param indexPath The indexPath to check if it represents start/end date cell.
 */
- (BOOL)indexPathHasDate:(NSIndexPath *)indexPath
{
    BOOL hasDate = NO;

    if ((indexPath.row == kDateStartRow) ||
        (indexPath.row == kDateEndRow || ([self hasInlineDatePicker] && (indexPath.row == kDateEndRow + 1))))
    {
        hasDate = YES;
    }

    return hasDate;
}

#pragma mark - Table view data source

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    CGFloat height = ([self indexPathHasPicker:indexPath] ? self.pickerCellRowHeight : self.tableView.rowHeight);
    return height;


}

//- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
//{
//    return 1;
//}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    if ([self hasInlineDatePicker])
    {
        // we have a date picker, so allow for it in the number of rows in this section
        NSInteger numRows = self.dataArray.count;
        return ++numRows;
    }

    return self.dataArray.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = nil;

    NSString *cellID = kDateCellID;

    if ([self indexPathHasPicker:indexPath])
    {
        // the indexPath is the one containing the inline date picker
        cellID = kDatePickerID;     // the current/opened date picker cell
    }
    else if ([self indexPathHasDate:indexPath])
    {
        // the indexPath is one that contains the date information
        cellID = kDateCellID;       // the start/end date cells
    }

    cell = [tableView dequeueReusableCellWithIdentifier:cellID];

    if (!cell) {
        cell = [self createCellWithIdetifier:cellID];
    }

    if (indexPath.row == 0)
    {
        // we decide here that first cell in the table is not selectable (it's just an indicator)
        cell.selectionStyle = UITableViewCellSelectionStyleNone;
    }

    // if we have a date picker open whose cell is above the cell we want to update,
    // then we have one more cell than the model allows
    //
    NSInteger modelRow = indexPath.row;
    if (self.datePickerIndexPath != nil && self.datePickerIndexPath.row < indexPath.row)
    {
        modelRow--;
    }

    NSDictionary *itemData = self.dataArray[modelRow];

    // proceed to configure our cell
    if ([cellID isEqualToString:kDateCellID])
    {
        // we have either start or end date cells, populate their date field
        //
        cell.textLabel.text = [self.dataArray objectAtIndex:indexPath.row];
        //cell.detailTextLabel.text = [self.dateFormatter stringFromDate:[itemData valueForKey:kDateKey]];
    }
//    else if ([cellID isEqualToString:kOtherCell])
//    {
//        // this cell is a non-date cell, just assign it's text label
//        //
//        //cell.textLabel.text = [itemData valueForKey:kTitleKey];
//        cell.textLabel.text = [self.dataArray objectAtIndex:indexPath.row];
//    }

    return cell;
}

/*! Adds or removes a UIDatePicker cell below the given indexPath.

 @param indexPath The indexPath to reveal the UIDatePicker.
 */
- (void)toggleDatePickerForSelectedIndexPath:(NSIndexPath *)indexPath
{
    [self.tableView beginUpdates];

    NSArray *indexPaths = @[[NSIndexPath indexPathForRow:indexPath.row + 1 inSection:0]];

    // check if 'indexPath' has an attached date picker below it
    if ([self hasPickerForIndexPath:indexPath])
    {
        // found a picker below it, so remove it
        [self.tableView deleteRowsAtIndexPaths:indexPaths
                              withRowAnimation:UITableViewRowAnimationFade];
    }
    else
    {
        // didn't find a picker below it, so we should insert it
        [self.tableView insertRowsAtIndexPaths:indexPaths
                              withRowAnimation:UITableViewRowAnimationFade];
    }

    [self.tableView endUpdates];
}

/*! Reveals the date picker inline for the given indexPath, called by "didSelectRowAtIndexPath".

 @param indexPath The indexPath to reveal the UIDatePicker.
 */
- (void)displayInlineDatePickerForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // display the date picker inline with the table content
    [self.tableView beginUpdates];

    BOOL before = NO;   // indicates if the date picker is below "indexPath", help us determine which row to reveal
    if ([self hasInlineDatePicker])
    {
        before = self.datePickerIndexPath.row < indexPath.row;
    }

    BOOL sameCellClicked = (self.datePickerIndexPath.row - 1 == indexPath.row);

    // remove any date picker cell if it exists
    if ([self hasInlineDatePicker])
    {
        [self.tableView deleteRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:self.datePickerIndexPath.row inSection:0]]
                              withRowAnimation:UITableViewRowAnimationFade];
        self.datePickerIndexPath = nil;
    }

    if (!sameCellClicked)
    {
        // hide the old date picker and display the new one
        NSInteger rowToReveal = (before ? indexPath.row - 1 : indexPath.row);
        NSIndexPath *indexPathToReveal = [NSIndexPath indexPathForRow:rowToReveal inSection:0];

        [self toggleDatePickerForSelectedIndexPath:indexPathToReveal];
        self.datePickerIndexPath = [NSIndexPath indexPathForRow:indexPathToReveal.row + 1 inSection:0];
    }

    // always deselect the row containing the start or end date
    [self.tableView deselectRowAtIndexPath:indexPath animated:YES];

    [self.tableView endUpdates];

    // inform our date picker of the current date to match the current cell
    [self updateDatePicker];
}


#pragma mark - UITableViewDelegate

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
    respectiveOptions = [[NSArray alloc]init];


    respectiveOptions = [ASOR objectAtIndex:indexPath.row];


    if ([kDateCellID isEqualToString:cell.reuseIdentifier])
    {

        [self displayInlineDatePickerForRowAtIndexPath:indexPath];
    }
    else
    {
        [tableView deselectRowAtIndexPath:indexPath animated:YES];
    }
}

- (UIPickerView*)pickerViewForCell:(UITableViewCell*)cell {
    UIPickerView * picker = [[UIPickerView alloc] initWithFrame:cell.bounds];
    [picker setDelegate:self];
    [picker setDataSource:self];
    picker.hidden = YES;
    [cell addSubview:picker];
    return picker;
}

#pragma mark - Actions

/*! User chose to change the date by changing the values inside the UIDatePicker.

 @param sender The sender for this action: UIDatePicker.
 */
//- (void)dateAction:(id)sender
//{
//    NSIndexPath *targetedCellIndexPath = nil;
//    
//    if ([self hasInlineDatePicker])
//    {
//        // inline date picker: update the cell's date "above" the date picker cell
//        //
//        targetedCellIndexPath = [NSIndexPath indexPathForRow:self.datePickerIndexPath.row - 1 inSection:0];
//    }
//    else
//    {
//        // external date picker: update the current "selected" cell's date
//        targetedCellIndexPath = [self.tableView indexPathForSelectedRow];
//    }
//    
//    UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:targetedCellIndexPath];
//    UIDatePicker *targetedDatePicker = sender;
//    
//    // update our data model
//    NSMutableDictionary *itemData = self.dataArray[targetedCellIndexPath.row];
//    [itemData setValue:targetedDatePicker.date forKey:kDateKey];
//    
//    // update the cell's date string
//    cell.detailTextLabel.text = [self.dateFormatter stringFromDate:targetedDatePicker.date];
//}

// Number of components.
-(NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView{
    return 1;
}

// Total rows in our component.
-(NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component{
    return [respectiveOptions count];

}

// Display each row's data.
-(NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component{

    return [respectiveOptions objectAtIndex:row];


}

// Do something with the selected row.
-(void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component{

}
Was it helpful?

Solution

Here is an example of UITableView making use of UIPickerView embedded in the cells.

I am not changing the color of the picker view, cells, height of cells - I will leave that up to you to do something that suits your needs.

But the main idea here is to show you how you could have picker view in cells and be able to get value accordingly.

@interface YourController ()
@property (strong, nonatomic) NSMutableArray *pickerViewsArray;
@end

@implementation YourController

- (id)initWithStyle:(UITableViewStyle)style
{
    self = [super initWithStyle:style];
    if (self)
    {
        // Custom initialization
    }
    return self;
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.pickerViewsArray = [[NSMutableArray alloc] init];
    [self createYourPickerViews];
}

- (void)createYourPickerViews
{
    for(int x = 0; x < 10; x++) //number of picker views
    {
        UIPickerView *pickerView = [[UIPickerView alloc] initWithFrame:(CGRect){{0, 0}, 150, 10}];
        pickerView.delegate = self;
        pickerView.dataSource = self;
        pickerView.tag = x;
        [self.pickerViewsArray addObject:pickerView];
    }
}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    // Return the number of sections.
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{

    // Return the number of rows in the section.
    return [self.pickerViewsArray count]; //As mentioned before, this is important.
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    if(cell == nil)
    {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
        cell.selectionStyle = UITableViewCellSelectionStyleNone;
    }

    // Configure the cell...
    cell.textLabel.text = [NSString stringWithFormat:@"%i", indexPath.row];
    [cell.contentView addSubview:(UIPickerView*)[self.pickerViewsArray objectAtIndex:indexPath.row]];

    return cell;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return 150.0f; //just some arbitrary value, change it to suit your needs.
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{

}

- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
    //tag corresponds to row on tables view.
    NSLog(@"view tag:%ld", (long)pickerView.tag);
    //row here corresponds to the value selected from picker view.
    NSLog(@"view value:%ld", (long)row);
}

- (NSString*) pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
    return [NSString stringWithFormat:@"%d", row+1];  
}

- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
    return 1;
}

- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
{
    //change this value to suit your needs.
    return 10;
}

@end

Finally make sure to do this in your .h:

@interface YourController : UITableViewController <UITableViewDataSource, UITableViewDelegate, UIPickerViewDelegate, UIPickerViewDataSource>

With this you could be able to retrieve the value from picker view from each different cell accordingly.

For UI I leave that to you. Code is tested. Hope this helps.

Post-Comment (Update 2):

I ran it again and I see what you meant, although it didn't happen to me yesterday. I made changes to the following and ran another test, this will fix it:

In cellForRowAtIndexPath:

if(cell == nil)
{
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
    cell.selectionStyle = UITableViewCellSelectionStyleNone;
    cell.textLabel.text = [NSString stringWithFormat:@"%i", indexPath.row];
    [cell.contentView addSubview:[self.pickerViewsArray objectAtIndex:indexPath.row]];
}

Another Update (concerning UI: how to hide and un-hide picker when tapped on cells):

Modify this to:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    UIPickerView *temp;
    temp = [self.pickerViewsArray objectAtIndex:indexPath.row];

    if(!temp.isUserInteractionEnabled)
    {
        [UIView animateWithDuration:1.0f animations:^
        {
            temp.userInteractionEnabled = YES;
            temp.layer.opacity = 1.0f;
        }
        completion:^(BOOL finished)
        {
        }];
    }
    else
    {
        [UIView animateWithDuration:1.0f animations:^
        {
            temp.userInteractionEnabled = NO;
            temp.layer.opacity = 0.0f;
        }
        completion:^(BOOL finished)
        {
        }];
    }
}

And in:

- (void)createYourPickerViews
{
    for(int x = 0; x < 10; x++)
    {
        UIPickerView *pickerView = [[UIPickerView alloc] initWithFrame:(CGRect){{0, 0}, 150, 10}];
        pickerView.delegate = self;
        pickerView.dataSource = self;
        pickerView.layer.opacity = 0.0f;  ======>>> //Add this
        pickerView.userInteractionEnabled = NO; =======>>> //And this
        pickerView.tag = x;
        [self.pickerViewsArray addObject:pickerView];
    }
}

Again, code is tested. Hope this (finally) helps.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top