iosobjective-ccrashuipickerviewnszombies

UIPickerView crashes when scrolling 2 components together


Actually, I asked a same question before, but I have not found a way to fix it yet, so here I comes again.

My situation:

I have a UIPickerView with 2 related components, and the number and content of the rows in the 2nd or right components changes with the currently selected row in the 1st or left component, which is a quite common and useful function of UIPickerView.

Now, if I use 2 thumbs to scroll both components together, my app gets crashed very easily and soon with the information below:

*** Terminating app due to uncaught exception of class '_NSZombie_NSException'
libc++abi.dylib: terminate called throwing an exception

The ironic thing is that, I can't debug it.

Actually, if I add a break point in the pickerView:didSelectRow:inComponent method, I can't even scroll both components quickly together since it would stop at the break point every time I just put my finger on the screen.

I can only guess the reason is that when the 1st component is being scrolled, the supposed number of rows in 2nd component keeps changing, but meantime, the UIPickerView is asking for the title and number for the 2nd component, then it crashes.

But I haven't found any method that can be used to judge whether a component is being scrolled. So I can't find the correct time to reject the request of the pickerView's delegate and dataSource for the 2nd component.

Did anyone meet similar situation?

Thanks for your help! I'd appreciate it a lot!


In my code, the bug is about the typePicker, so I deleted all other codes that is not related to typePicker.

Here, type and detailTypes are two entities in Core Data, and a to-many relationship called details reaches from type to detailsTypes.

If the type is "Nations", for example, the detailTypes would be "the U.S.", "France", "China", and so on.

So in the typePicker, the 1st or left components show all type entities, and the 2nd or right components show the corresponding detailTypes entities according to the currently selected type in 1st component.

And the 1st row of both components in typePicker is always "None", to allow users not to select a specific type, which is why there is many "+1" in the code.

- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
if (pickerView == self.typePicker)
        return 2;
    return 1;
}
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
    NSUInteger majorTypeRow = [self.typePicker selectedRowInComponent:0] - 1;
    if (pickerView == self.typePicker) {
        if (component == 0)
            return [self.types count] + 1;
        else {
            if (majorTypeRow == -1) // no major type entities
                return 1;
            NSString *majorTypeName = [[self.types objectAtIndex:majorTypeRow] name];
            NSArray *detailType = [self.detailTypes objectForKey:majorTypeName];
            if (detailType) 
                return [detailType count] + 1;
            else {
                NSManagedObject *majorType = [self.types objectAtIndex:majorTypeRow];
                NSSet *minorTypes = [majorType valueForKey:@"details"];
                NSSortDescriptor *sd = [[NSSortDescriptor alloc] initWithKey:@"order" ascending:NO];
                NSArray *sortDescriptors = [NSArray arrayWithObjects:sd, nil];
                NSArray *array = [minorTypes sortedArrayUsingDescriptors:sortDescriptors];
                [self.detailTypes setObject:array forKey:majorTypeName];
                [sd release];
                return [array count] + 1;
        }
    }
}

- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {
    NSUInteger majorTypeRow = [self.typePicker selectedRowInComponent:0] - 1;
    row--;
    if (pickerView == self.typePicker) {
        if (component == 0)
            [pickerView reloadComponent:1]; // I believe this is where the bug starts, but I can't find the exact line of code that causes the crash
        else {
            if (row == -1)
                self._detailType = nil;
            else {
                NSString *majorTypeName = [[self.types objectAtIndex:majorTypeRow] name];
                NSArray *dt = [self.detailTypes objectForKey:majorTypeName];
                if (dt)
                    self._detailType = [dt objectAtIndex:row];
            }
        }
    }
    NSIndexPath *path = [self.tableView indexPathForSelectedRow];
    [self.tableView reloadData];
    [self.tableView selectRowAtIndexPath:path animated:YES scrollPosition:UITableViewScrollPositionNone];
}

Solution

  • The crash is due to this line in your delegates

    NSUInteger majorTypeRow = [self.typePicker selectedRowInComponent:0] - 1;
    

    As other pointed out by others you need to keep two seperate arrays or objects which can provide the dataSource for the respective components.

    You have a majorType and detailType. You can have an array of majorTypes and a selectedMajorType. Keeping an extra selectedMajorType removes the need to use the selectedRowInComponent: and no longer crashes the app.

    #pragma mark - UIPickerViewDataSource
    
    - (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
    {
        return 2;
    }
    
    - (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
    {
        NSInteger rows = 0;
        if (component) {
             rows = [self.selectedMajorType.minorTypes count];
        }else{
            rows = [self.majorTypes count];
        }
        return rows+1;
    }
    

    UIPickerViewDelegate implementation

    - (NSString *)pickerView:(UIPickerView *)pickerView
                 titleForRow:(NSInteger)row
                forComponent:(NSInteger)component
    {
        row--;
        NSString *title = @"None";
        if (component) {
            NSArray *minorTypes = [self.selectedMajorType.minorTypes allObjects];
            if (row >= 0 && row < [minorTypes count]) {
                MinorType *minorType = minorTypes[row];
                title = minorType.name;
            }
        }else{
            if(row>=0 && row < [self.majorTypes count]){
                MajorType *majorType = self.majorTypes[row];
                title =  majorType.name;
            }
        }
        return title;
    }
    
    - (void)pickerView:(UIPickerView *)pickerView
          didSelectRow:(NSInteger)row
           inComponent:(NSInteger)component
    {
        row--;
        if (component==0) {
            if (row >= 0 && row < [self.majorTypes count]) {
                 self.selectedMajorType = self.majorTypes[row];
            }else {
               self.selectedMajorType = nil;
            }
    
            [pickerView reloadComponent:1];
        }
    }
    

    Demo Source Code