Multiple Row Selection in UIPickerView
Asked Answered
N

6

13

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?

Nereid answered 25/5, 2010 at 8:13 Comment(0)
J
14

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];
    }
}
Jackhammer answered 22/1, 2012 at 12:34 Comment(5)
In this example, you never set the recognizer.view.tag. Maybe it needs to be revisedInsensate
I have three pickers and i need this kind of multiple selection for only one picker.. What should i return for other pickers?Scopp
You can return UITableViewCell but without adding gesture recognizer.Jackhammer
This looks slick but doesn't seem to work for me. No way I can get the recognizer to call my method. Doesn't seem to receive taps. Anyone get it to work? iOS 7.Potboiler
Hi Suda, In iOS7, toggleSelection method not called so please help me why gestureDelegate is not called? Any idea..Herbivore
G
7

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!

Gowon answered 17/1, 2011 at 22:27 Comment(0)
J
2

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
Jesseniajessey answered 18/8, 2017 at 17:4 Comment(1)
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!!!!!!Bracy
B
1
- (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.

Bog answered 16/11, 2015 at 11:55 Comment(1)
please use the code format ( 4 spaces ) before your code example, to make your answer clearerOutsize
L
0

same resolve in swift 4 for this case

  //set up tap gesture
        let tapGestureRecognaizer = UITapGestureRecognizer(target: self, action: #selector(pickerTapp))
        tapGestureRecognaizer.delegate = self
        tapGestureRecognaizer.numberOfTapsRequired = 2
        self.pickerView.addGestureRecognizer(tapGestureRecognaizer)

@objc func pickerTapp(tapGesture: UITapGestureRecognizer) {
    if tapGesture.state == .ended {
        // looking for frame selection row
        let selectedItem = dataSource2[pickerView.selectedRow(inComponent: 1)]
        let rowHeight = self.pickerView.rowSize(forComponent: 1).height
        let rowWidth = self.pickerView.rowSize(forComponent: 1).width
        let selectRowFrame = CGRect(x: CGFloat(Int(self.pickerView.frame.width) / self.pickerView.numberOfComponents), y: (self.pickerView.frame.height - rowHeight) / 2, width: rowWidth, height: rowHeight)

        let userTappedOnSelectedRow = selectRowFrame.contains(tapGesture.location(in: self.pickerView))
        // if tap to selection row ....
        if userTappedOnSelectedRow {
            if selectedArray.contains(selectedItem) {
                var index = 0
                for item in selectedArray {
                    if item == selectedItem {
                        selectedArray.remove(at: index)
                    } else {
                        index += 1
                    }
                }
            } else {
                selectedArray.append(selectedItem)
            }
        }

        //reload Data
        self.pickerView.dataSource = self
        self.pickerView.selectRow(selectedRow, inComponent: 1, animated: false)
        self.pickerView(self.pickerView, didSelectRow: selectedRow, inComponent: 1)
    }

full resolve: https://github.com/akonoplev/Features/blob/master/MultiSelectUIPickerView/MultiSelectUIPickerView/ViewController.swift

Lollar answered 10/9, 2018 at 16:33 Comment(1)
Please put the solution to the question in your actual answer. If the linked website changes your answer will unfortunately become useless.Hegelianism
N
0

Here's my take on this problem: https://github.com/aselivanov/UIMultiPicker. I wanted exactly the same picker as mobile Safari uses to handle <select multiple>. It looks very similar to UIPickerView (I bet they share same code).

Nankeen answered 22/1, 2019 at 11:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.