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