You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS多文本字段联动UIPickerView时NSRangeException异常求助

Fixing NSRangeException in Your UIPickerView + TextField Setup

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?

  1. Shared Array Modification: You're removing items from QArray every 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.
  2. Invalid Saved Indices: The selectedIndex you store in backUpArray is tied to the original array's state. Once you modify QArray, that index no longer points to the correct item (or any item at all), leading to out-of-bounds access.
  3. 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 originalQArray and originalQidArray never 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 18:47:45