iOS多文本字段联动UIPickerView时NSRangeException异常求助
Hey there, let's break down why you're hitting that NSRangeException and fix it once and for all. The core issue here is that you're directly modifying a shared array (QArray) for all three pickers, which leads to index mismatches and array bounds errors when users go back to re-select options. Here's what's going wrong and how to fix it:
What's Causing the Crash?
- Shared Array Modification: You're removing items from
QArrayevery time a user selects an option, but when they re-edit a text field, you only add back that field's previous selection—not the ones removed by other fields. This leaves the array in an inconsistent state, with picker views trying to access indices that no longer exist. - Invalid Saved Indices: The
selectedIndexyou store inbackUpArrayis tied to the original array's state. Once you modifyQArray, that index no longer points to the correct item (or any item at all), leading to out-of-bounds access. - Inconsistent Picker Data: Your picker view methods all rely on the same
QArray, but as items get removed and partially restored, the array's length and content don't match what the picker expects.
The Solution: Use a Static Original Dataset + Dynamic Option Lists
Instead of modifying a single shared array, we'll keep an unmodified copy of your full question list, then generate dynamic option lists for each picker that exclude already selected items from the other fields.
Step 1: Add New Properties
First, add these properties to your class (either in the header or a class extension):
@property (nonatomic, strong) NSArray *originalQArray; // Unmodified full list of questions @property (nonatomic, strong) NSArray *originalQidArray; // Unmodified full list of question IDs @property (nonatomic, copy) NSString *selectedTitle1; // Selected title for bloodGroup field @property (nonatomic, copy) NSString *selectedTitle2; // Selected title for txtField1 @property (nonatomic, copy) NSString *selectedTitle3; // Selected title for txtField2 @property (nonatomic, strong) NSString *selectedValue1; // Selected ID for bloodGroup @property (nonatomic, strong) NSString *selectedValue2; // Selected ID for txtField1 @property (nonatomic, strong) NSString *selectedValue3; // Selected ID for txtField2 @property (nonatomic, strong) NSArray *currentPickerOptions; // Dynamic options for the active picker @property (nonatomic, strong) NSArray *currentPickerQids; // Dynamic IDs for the active picker
Step 2: Initialize the Original Dataset
In viewDidLoad, populate the original arrays with your full question data from the JSON response (don't modify these later!):
- (void)viewDidLoad { [super viewDidLoad]; // Populate original arrays with your JSON data (example based on your JSON) self.originalQArray = @[ @"What is your first pet's name?", @"Who was your first grade teacher?", @"What city were you born in?", @"What was your childhood nickname?", @"What was the name of your elementary school?", @"Where does your nearest relative live?", @"In what city or town was your first job?", @"Which is your first car? Edited ", @"This is a test question - answer = ddi", @"What is your Mother's maiden name?", @"What is your Last name?", @"What is the last 4 of your SSN?", @"What is your Date of Birth? (MM/DD/YYYY)" ]; self.originalQidArray = @[ @"23", @"24", @"25", @"26", @"27", @"28", @"29", @"30", @"32", @"33", @"35", @"36", @"37" ]; // Initialize selected values to empty strings self.selectedTitle1 = @""; self.selectedTitle2 = @""; self.selectedTitle3 = @""; }
Step 3: Generate Dynamic Picker Options
Update textFieldDidBeginEditing to create a dynamic list of options that excludes already selected items from other fields:
- (void)textFieldDidBeginEditing:(UITextField *)textField { if (textField == self.bloodGroup) { self.isBloodGroupFieldSelected = YES; self.isGenderGroupFieldSelected = NO; // Create options excluding selected items from txtField1 and txtField2 NSMutableArray *options = [NSMutableArray arrayWithArray:self.originalQArray]; NSMutableArray *qids = [NSMutableArray arrayWithArray:self.originalQidArray]; if (self.selectedTitle2.length > 0) { NSInteger index = [self.originalQArray indexOfObject:self.selectedTitle2]; if (index != NSNotFound) { [options removeObjectAtIndex:index]; [qids removeObjectAtIndex:index]; } } if (self.selectedTitle3.length > 0) { NSInteger index = [self.originalQArray indexOfObject:self.selectedTitle3]; if (index != NSNotFound) { [options removeObjectAtIndex:index]; [qids removeObjectAtIndex:index]; } } self.currentPickerOptions = options; self.currentPickerQids = qids; } else if (textField == self.txtField1) { self.isBloodGroupFieldSelected = NO; self.isGenderGroupFieldSelected = YES; NSMutableArray *options = [NSMutableArray arrayWithArray:self.originalQArray]; NSMutableArray *qids = [NSMutableArray arrayWithArray:self.originalQidArray]; if (self.selectedTitle1.length > 0) { NSInteger index = [self.originalQArray indexOfObject:self.selectedTitle1]; if (index != NSNotFound) { [options removeObjectAtIndex:index]; [qids removeObjectAtIndex:index]; } } if (self.selectedTitle3.length > 0) { NSInteger index = [self.originalQArray indexOfObject:self.selectedTitle3]; if (index != NSNotFound) { [options removeObjectAtIndex:index]; [qids removeObjectAtIndex:index]; } } self.currentPickerOptions = options; self.currentPickerQids = qids; } else if (textField == self.txtField2) { self.isBloodGroupFieldSelected = NO; self.isGenderGroupFieldSelected = NO; NSMutableArray *options = [NSMutableArray arrayWithArray:self.originalQArray]; NSMutableArray *qids = [NSMutableArray arrayWithArray:self.originalQidArray]; if (self.selectedTitle1.length > 0) { NSInteger index = [self.originalQArray indexOfObject:self.selectedTitle1]; if (index != NSNotFound) { [options removeObjectAtIndex:index]; [qids removeObjectAtIndex:index]; } } if (self.selectedTitle2.length > 0) { NSInteger index = [self.originalQArray indexOfObject:self.selectedTitle2]; if (index != NSNotFound) { [options removeObjectAtIndex:index]; [qids removeObjectAtIndex:index]; } } self.currentPickerOptions = options; self.currentPickerQids = qids; } [self.myPickerView reloadAllComponents]; }
Step 4: Update Picker View Data Source Methods
Modify these methods to use the dynamic currentPickerOptions array instead of the shared QArray:
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView { return 1; } - (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component { return self.currentPickerOptions.count; } - (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component { return self.currentPickerOptions[row]; } - (UIView *)pickerView:(UIPickerView *)thePickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view { UILabel *lblTitle = (view ?: [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 310, 50)]); lblTitle.numberOfLines = 2; lblTitle.textColor = [UIColor blackColor]; lblTitle.font = [UIFont systemFontOfSize:20]; lblTitle.text = self.currentPickerOptions[row]; return lblTitle; }
Step 5: Update the Done Method
Modify the done method to update only the selected values (no more modifying the original array):
- (void)done:(id)sender { NSInteger row = [self.myPickerView selectedRowInComponent:0]; // Guard against out-of-bounds access just in case if (row >= self.currentPickerOptions.count) { [self resignAllTextFields]; return; } if (self.isBloodGroupFieldSelected) { self.selectedTitle1 = self.currentPickerOptions[row]; self.selectedValue1 = self.currentPickerQids[row]; self.bloodGroup.text = self.selectedTitle1; } else if (self.isGenderGroupFieldSelected) { self.selectedTitle2 = self.currentPickerOptions[row]; self.selectedValue2 = self.currentPickerQids[row]; self.txtField1.text = self.selectedTitle2; } else { self.selectedTitle3 = self.currentPickerOptions[row]; self.selectedValue3 = self.currentPickerQids[row]; self.txtField2.text = self.selectedTitle3; } [self resignAllTextFields]; } // Helper method to clean up responder status - (void)resignAllTextFields { [self.myPickerView selectRow:0 inComponent:0 animated:YES]; [self.bloodGroup resignFirstResponder]; [self.txtField1 resignFirstResponder]; [self.txtField2 resignFirstResponder]; }
Why This Works
- Static Original Data: The
originalQArrayandoriginalQidArraynever change, so we always have a reliable source to generate dynamic lists from. - Dynamic Option Lists: Each time a user edits a text field, we build a new list that excludes only the items already selected by the other two fields. This ensures no index mismatches and no missing items when re-editing.
- No Shared Array Modification: We're no longer modifying a single array for all pickers, so there's no chance of leaving the array in an inconsistent state.
内容的提问来源于stack exchange,提问作者iphone

