NSMutableString编辑失效问题:字幕编号修复异常求助
问题分析与修复方案
你的问题根源在于错误地使用了全局查找rangeOfString:来定位要替换的字幕编号,同时修改字符串后没有同步更新扫描器的状态,导致后续操作和字符串实际内容完全脱节。
为什么会出现混乱?
- 当你用
[tempString rangeOfString:oldNumStr]时,它会返回整个字符串中第一个匹配oldNumStr的位置,而不是当前扫描器指向的那个编号。比如当处理到后面的编号1时,它会匹配到第一个字幕的1,导致错误地替换前面的编号。 - 每次修改
tempString(删除旧编号、插入新编号)后,theScanner的扫描位置还是基于原来的字符串长度计算的,这就导致扫描位置和字符串实际内容的位置完全不匹配,后续的扫描和替换操作全乱了。 - 至于编号到1000时正常,只是巧合:当所有编号都是4位时,
rangeOfString:不会误匹配更短的数字(比如1000不会和1、10、100匹配),所以全局查找刚好命中正确的位置,但这只是特殊情况,不是正确的解决思路。
修复方案一:基于扫描器位置精准定位替换范围
我们不需要全局查找,而是直接通过扫描器的位置来确定当前要替换的编号的范围,这样就能精准操作当前字幕的编号:
-(IBAction)scanToSubIndex:(id)sender { NSMutableString* tempString = [[NSMutableString alloc] initWithString:[theTextView string]]; NSScanner *theScanner = [NSScanner scannerWithString:tempString]; int currentIndex = 1; while (![theScanner isAtEnd]) { // 跳过前面的空行分隔符(第一个字幕前可能没有) [theScanner scanUpToString:@"\r\n\r\n" intoString:NULL]; if ([theScanner scanString:@"\r\n\r\n" intoString:NULL]) { // 跳过分隔符后,当前位置就是下一个字幕的编号开头 } // 记录当前编号的起始位置 NSInteger numStartLocation = [theScanner scanLocation]; // 扫描出当前的旧编号 NSString *oldNumStr; if (![theScanner scanUpToString:@"\r\n" intoString:&oldNumStr]) { break; // 没有更多编号了 } // 计算旧编号的范围:从起始位置到扫描后的位置 NSRange numRange = NSMakeRange(numStartLocation, [theScanner scanLocation] - numStartLocation); // 替换旧编号为新的顺序编号 NSString *newNumStr = [NSString stringWithFormat:@"%d", currentIndex++]; [tempString replaceCharactersInRange:numRange withString:newNumStr]; // 同步扫描器位置:因为替换后字符串长度变化了,需要重新设置扫描位置 [theScanner setScanLocation:numStartLocation + [newNumStr length]]; } // 最后更新文本视图 [theTextView setString:tempString]; NSLog(@"处理完成后的字幕:\n%@", tempString); }
修复方案二:分割字幕块逐个处理(更清晰易维护)
另一种更稳妥的方式是先把整个文本分割成单个的字幕块,然后逐个修改每个块的编号,最后再拼接起来:
-(IBAction)scanToSubIndex:(id)sender { NSString *originalText = [theTextView string]; // 按空行分割成字幕块(注意处理可能的换行符差异,比如\n\n或者\r\n\r\n) NSArray *subtitleBlocks = [originalText componentsSeparatedByString:@"\r\n\r\n"]; NSMutableArray *processedBlocks = [NSMutableArray array]; int currentIndex = 1; for (NSString *block in subtitleBlocks) { if ([block length] == 0) { [processedBlocks addObject:block]; continue; } // 分割块的第一行(编号)和后面的内容 NSRange firstLineRange = [block rangeOfString:@"\r\n"]; if (firstLineRange.location == NSNotFound) { // 没有换行的块,直接添加编号?按实际情况处理 NSString *newBlock = [NSString stringWithFormat:@"%d\r\n%@", currentIndex++, block]; [processedBlocks addObject:newBlock]; } else { NSString *contentPart = [block substringFromIndex:firstLineRange.location + firstLineRange.length]; NSString *newBlock = [NSString stringWithFormat:@"%d\r\n%@", currentIndex++, contentPart]; [processedBlocks addObject:newBlock]; } } // 拼接回完整文本 NSString *finalText = [processedBlocks componentsJoinedByString:@"\r\n\r\n"]; [theTextView setString:finalText]; NSLog(@"处理完成后的字幕:\n%@", finalText); }
为什么这两种方案能解决问题?
- 方案一通过扫描器的位置直接定位当前要修改的编号范围,避免了全局查找的误匹配,同时每次修改后同步更新扫描器的位置,保证后续操作和字符串内容一致。
- 方案二把问题拆解为处理单个字幕块,逻辑更清晰,完全避免了扫描器和字符串不同步的问题,也更容易调试和扩展。
内容的提问来源于stack exchange,提问作者Tom
相关产品推荐
相关产品推荐

