Search code examples
iosxcodeuipickerview

custom UIPickerView - limit user to spinning one wheel at a time


I have a custom UIPickerView with three components that are dependent on one another (i.e., the second one shows values depending on what has been selected in the first one and the third is dependent on the first and second). I am getting the values that are shown in the UIPickerView out of an NSDictionary.

Everything works nicely, except, when I spin two of the components at the same time, the app sometimes crashes (no time to reload data). This is what my pickerView:didSelectRow:inComponent looks like:

- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component 
{
    NSLog(@"Selecting row %d component %d", row, component);

    // When A is changed, we need to reload B and C
    if (component == 0) {

        [pickerView reloadComponent:1];
        [pickerView selectRow:0 inComponent:1 animated:YES];

        // need to reload the C after reloading B
        [self pickerView:pickerView didSelectRow:0 inComponent:1];

    }
    else if (component == 1) {
        [pickerView reloadComponent:2];
        [pickerView selectRow:0 inComponent:2 animated:YES];
    }
    [self updateSelection];
}

Is there a way to prevent the user from spinning more than one component of the picker at a time to prevent a crash?

Thanks!


Solution

  • Good holy Jesus you call didSelectRow:component: recursively!

    I know that the way you handle parameters prevents it from going more than two levels deep, but still, don't do that. Ever.

    There's no way to prevent the user from spinning more than one component at a time. It is the joy of multitouch and iOS to set everything in motion and watch it all settle down into a consistent state. And because the spinner components take time to settle, the user can set them all atwitter using just one finger. You must learn to write your code accordingly.

    In this case, you want to delete all calls to didSelectRow:component: from within itself - it is to be called by the system only. You're on the right track with your use of reloadComponent:, it's just that you're not reloading enough of them:

    -(void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
        NSLog(@"Selecting row %d component %d", row, component);
    
        [self updateSelection];
    
        // When A is changed, we need to reload B and C
        if (component == 0) {
            [pickerView reloadComponent:1];
            [pickerView reloadComponent:2];
        } else if (component == 1) {
            [pickerView reloadComponent:2];
        }
    }
    

    I don't know what your updateSelection does - presumably it sets the data model based on the value of pickerView. This is fine - but it needs to happen at the top of your method, so that when whichever of your titleForRow or viewForRow is called, it has the proper data to choose from. Also, from within any of those three (update,titleFor, viewFor) do not assert values into the picker! Just read from it.

    I think this is a good starting point for you, but you will have to tweak things a little here and there as you get closer to your goal. I think you will find that the key to Pickers is that they only assert didSelectRow:component: when one of the component spinner things settles down on a value, and they're really good about continuing to spin if you reload them. Give this a shot and see if you can make some progress, send word back if you get stuck.