Wang Liang
Wang Liang

Reputation: 943

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];
}

Upvotes: 4

Views: 2627

Answers (2)

Anupdas
Anupdas

Reputation: 10201

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

Upvotes: 7

waylonion
waylonion

Reputation: 6976

Okay, I think I understand the problem:

The problem with scrolling two components together is that as your UIPickerView data source may be changing due to dependecies you've created with the data relationships. I think you need to create two arrays that represent the left and right respectively and repopulate based on which component you change!

The solution can be found by checking out a similar problem here! UIPickerView with two components crashes when scrolling both

Additionally, in regards to checking to see if your UIPickerView is scrolling, you can simply set up a timer. A helpful link is here: Determining if UIPickerWheel is scrolling ! By checking to see if the current rows match, you can tell (within a short interval) if the user has moved the wheel!

I hope this is clear and helpful!

Upvotes: 1

Related Questions