4

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];
}
jszumski
  • 7,430
  • 11
  • 40
  • 53
Wang Liang
  • 941
  • 2
  • 15
  • 34
  • If a question is not getting attention you need to start bounty for it. Can you show your implementation of UIPicker delegates? – Anupdas Apr 21 '13 at 13:31
  • Sure I can, but it is a lot of code in these methods since the logic is kind of complicated, I'm afraid a bunch of strange code would scare people away,,,=.= Do I really need to? – Wang Liang Apr 21 '13 at 13:49
  • Make sure that your dataSource always have valid Data and you are not missing any special conditions in your logic. Showing your code can avoid many wild guesses. – Anupdas Apr 21 '13 at 13:57
  • @Anupdas thanks for your reminding! I've added my code in the question! Please take a look! – Wang Liang Apr 21 '13 at 15:15
  • I have worked out a demo for you. Please check if that what you want and compare it with yours. https://dl.dropboxusercontent.com/u/35012982/Demo%20Projects/TestMultiPickerView.zip – Anupdas Apr 25 '13 at 08:06
  • Why don't you disallow touch on the dependent picker when user is interacting with the primary picker? – maroux Apr 26 '13 at 09:50

2 Answers2

7

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

Anupdas
  • 10,211
  • 2
  • 35
  • 60
1

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!

Community
  • 1
  • 1
waylonion
  • 6,866
  • 8
  • 51
  • 92