Multiple Row Selection in UIPickerView

28,251

Solution 1

You can do it without UITableView, using just UITapGestureRecognizer :)

Also, add NSMutableArray *selectedItems somewhere in your .h file.

- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view {
    UITableViewCell *cell = (UITableViewCell *)view;

    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
        [cell setBackgroundColor:[UIColor clearColor]];
        [cell setBounds: CGRectMake(0, 0, cell.frame.size.width -20 , 44)];
        UITapGestureRecognizer *singleTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(toggleSelection:)];
        singleTapGestureRecognizer.numberOfTapsRequired = 1;
        [cell addGestureRecognizer:singleTapGestureRecognizer];
    }

    if ([selectedItems indexOfObject:[NSNumber numberWithInt:row]] != NSNotFound) {
        [cell setAccessoryType:UITableViewCellAccessoryCheckmark];
    } else {
        [cell setAccessoryType:UITableViewCellAccessoryNone];
    }
    cell.textLabel.text = [datasource objectAtIndex:row];
    cell.tag = row;

    return cell;
}

- (void)toggleSelection:(UITapGestureRecognizer *)recognizer {
    NSNumber *row = [NSNumber numberWithInt:recognizer.view.tag];
    NSUInteger index = [selectedItems indexOfObject:row];
    if (index != NSNotFound) {
        [selectedItems removeObjectAtIndex:index];
        [(UITableViewCell *)(recognizer.view) setAccessoryType:UITableViewCellAccessoryNone];
    } else {
        [selectedItems addObject:row];
        [(UITableViewCell *)(recognizer.view) setAccessoryType:UITableViewCellAccessoryCheckmark];
    }
}

Solution 2

Implemented a quick hack to get the UIPickerView multiple-selection-behavior (like in Mobile Safari) without adding other views in front of the pickerview, if anyone's interested: https://github.com/alexleutgoeb/ALPickerView

Improvements are highly appreciated!

Solution 3

The following code works for iOS 10. I didn't have the chance to test it on older versions but I think it should work. The idea is similar to @suda's one but it adds a single tap recognizer directly to the picker view instead of adding the tap recognizer to each row, as this does not work on iOS7+.

For brevity, I did not include the complete implementation of the UIPickerViewDataSource and UIPickerViewDelegate protocols, only the relevant parts to implement the multiple selection.

// 1. Conform to UIGestureRecognizerDelegate protocol
@interface MyViewController () <UIPickerViewDataSource, UIPickerViewDelegate, UIGestureRecognizerDelegate>

@property (nonatomic, strong) NSMutableArray *selectedArray;    // To store which rows are selected
@property (nonatomic, strong) NSArray *dataArray;           // Picker data
@property (weak, nonatomic) IBOutlet UIPickerView *pickerView;

@end

@implementation MyViewController

- (void)viewDidLoad 
{    
    [super viewDidLoad];

    self.selectedArray = [NSMutableArray array]; 
    self.dataArray = @[@"Option 1", @"Option 2", @"Option 3", @"Option 4"];

    // 2. Add tap recognizer to your picker view
    UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(pickerTapped:)];
    tapRecognizer.delegate = self;
    [self.pickerView addGestureRecognizer:tapRecognizer];
}

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    return true;
}

- (void)pickerTapped:(UITapGestureRecognizer *)tapRecognizer
{
    // 3. Find out wich row was tapped (idea based on https://stackoverflow.com/a/25719326) 
    if (tapRecognizer.state == UIGestureRecognizerStateEnded) {
        CGFloat rowHeight = [self.pickerView rowSizeForComponent:0].height;
        CGRect selectedRowFrame = CGRectInset(self.pickerView.bounds, 0.0, (CGRectGetHeight(self.pickerView.frame) - rowHeight) / 2.0 );
        BOOL userTappedOnSelectedRow = (CGRectContainsPoint(selectedRowFrame, [tapRecognizer locationInView:self.pickerView]));
        if (userTappedOnSelectedRow) {
            NSInteger selectedRow = [self.pickerView selectedRowInComponent:0];
            NSUInteger index = [self.selectedArray indexOfObject:[NSNumber numberWithInteger:selectedRow]];

            if (index != NSNotFound) {
                NSLog(@"Row %ld OFF", (long)selectedRow);
                [self.selectedArray removeObjectAtIndex:index];
            } else {
                NSLog(@"Row %ld ON",  (long)selectedRow);
                [self.selectedArray addObject:[NSNumber numberWithInteger:selectedRow]];
            }
            // I don't know why calling reloadAllComponents sometimes scrolls to the first row
            //[self.pickerView reloadAllComponents];
            // This workaround seems to work correctly:
            self.pickerView.dataSource = self;            
            NSLog(@"Rows reloaded");
        }
    }
}

- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view 
{    
    // 4. Customize your Picker row as needed. 
    // This is a very simple view just to make the point

    UILabel *pickerLabel = (UILabel *)view;

    if (pickerLabel == nil) {
        pickerLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0, 0.0, 400, 32)];
    }

    BOOL isSelected = [self.selectedArray indexOfObject:[NSNumber numberWithInteger:row]] != NSNotFound;
    NSString *text = [self.dataArray objectAtIndex:row];
    text = [text stringByAppendingString:isSelected ? @"☒ " : @"☐ "];
    [pickerLabel setText:text];

    return pickerLabel;
}


// Do not forget to add the remaining methods to conform the UIPickerViewDataSource and UIPickerViewDelegate protocols!

@end

Solution 4

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer 
{
       return true;
}

And you need to override this method,other else form ios9, tap gesture recognization wouldn't work.

Share:
28,251

Related videos on Youtube

Ank
Author by

Ank

Updated on January 23, 2020

Comments

  • Ank
    Ank about 4 years

    I want to select multiple rows in a UIPickerView, so I had the idea to show my data in a table and add this table as subview to the picker. I have tried this but didn't succeed.

    Any suggestions how to do this?

  • KTang
    KTang over 6 years
    OMG! thank you so much man @Paglian! You saved my day! I was thought about how to disable the scrolls while calling reloadAllComponent for 5 hours! And you save me with just 1 line of code!!!!!!

Related