UITableView中的iOS 7 UITextView链接检测崩溃

我有一个自定义UITableView单元格设置在我的UITableView像这样:

 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *identifier = @"CELL_IDENTIFIER"; SGCustomCell *cell = (SGCustomCell *)[tableView dequeueReusableCellWithIdentifier:identifier]; if (!cell) cell = [[SGCustomCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier]; cell = [self customizedCell:cell withPost:[postsArray objectAtIndex:indexPath.row]]; return cell; } 

我设置了这样的单元格(具体设置UITextView.text nil – 正如在这个答案中指出):

 descriptionLabel.text = nil; descriptionLabel.text = post.postDescription; descriptionLabel.frame = CGRectMake(leftMargin - 4, currentTitleLabel.frame.origin.y + currentTitleLabel.frame.size.height + 10, self.frame.size.width - topMargin * 3, 100); [descriptionLabel sizeToFit]; 

这些单元格是100%可重用的, UITextView是这样插入的(正如你所看到的,没有什么特别的):

 descriptionLabel = [[UITextView alloc] init]; descriptionLabel.font = [UIFont fontWithName:@"HelveticaNeue" size:11]; descriptionLabel.editable = NO; descriptionLabel.scrollEnabled = NO; descriptionLabel.dataDetectorTypes = UIDataDetectorTypeLink; descriptionLabel.frame = CGRectMake(leftMargin, currentTitleLabel.frame.origin.y + currentTitleLabel.frame.size.height + 10, self.frame.size.width - topMargin * 3, 10); [self addSubview:descriptionLabel]; 

但是,当表格有大约50个单元格,当我快速滚动时,我得到以下崩溃:

 Terminating app due to uncaught exception 'NSRangeException', reason: 'NSMutableRLEArray objectAtIndex:effectiveRange:: Out of bounds' 

这是绝对荒谬的 – 我注释掉这一行 – descriptionLabel.dataDetectorTypes = UIDataDetectorTypeLink; 和应用程序停止崩溃! 我花了好几个小时试图弄清楚问题是什么,现在我简单地得到了这个。

在iOS 7.0.3上testing

当使用相同单元标识符的两个数据types的单元正在出列时发生崩溃。 这似乎是iOS中的一个错误,但苹果可能有充分的理由通过这种方式来实现它。 (记忆明智)

因此,唯一的100%防弹解决scheme是为包含数据types的单元格提供唯一的标识符。 这并不意味着你会为你的表中的所有单元格设置一个唯一的标识符,当然,因为它会占用太多的内存,而且你的表格滚动会非常慢。

您可以使用NSDataDetector来确定是否在您的文本中find匹配的types,然后只保存find的对象作为单元格标识符,如下所示:

 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { NSString *row = [self.dataSource objectAtIndex:indexPath.row]; static NSDataDetector *detector = nil; if (!detector) { NSError *error = NULL; detector = [[NSDataDetector alloc] initWithTypes:NSTextCheckingTypeLink | NSTextCheckingTypePhoneNumber error:&error]; } NSTextCheckingResult *firstDataType = [detector firstMatchInString:row options:0 range:NSMakeRange(0, [row length])]; NSString *dataTypeIdentifier = @"0"; if (firstDataType) { if (firstDataType.resultType == NSTextCheckingTypeLink) dataTypeIdentifier = [(NSURL *)[firstDataType URL] absoluteString]; else if (firstDataType.resultType == NSTextCheckingTypePhoneNumber) dataTypeIdentifier = [firstDataType phoneNumber]; } NSString *CellIdentifier = [NSString stringWithFormat:@"Cell_%@", dataTypeIdentifier]; UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier]; ... 

注意:初始化NSDataDetector *检测器为静态,而不是为每个单元初始化它可以提高性能。

我可以重现你的崩溃。 在TableViewCell子类中实现以下方法

 - (void)prepareForReuse { [super prepareForReuse]; [descriptionLabel setDataDetectorTypes: UIDataDetectorTypeNone]; } 

并添加以下调用- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath设置文本之前:

 [descriptionLabel setDataDetectorTypes: UIDataDetectorTypeLink]; 

为我工作。 也许它取消了textview中正在进行的绘图,并避免了这种方式的崩溃。

编辑:调用[descriptionLabel setDataDetectorTypes: UIDataDetectorTypeNone];[descriptionLabel setDataDetectorTypes: UIDataDetectorTypeLink]; 就在设置文本之前,似乎也修复了崩溃

如果您使用的是iOS6或更高版本,则可以使用NSDataDetector创build可归因的string,并将其用作TextView文本。 以下方法的修改版本就是我们将要使用的。 该方法需要一个string和一些已经预定义的属性(如字体和文本颜色),并将在第100个链接后停止。 但是,它有一些问题多个电话号码。 你需要定义你自己的代码,以避免URL地址。 NSDataDetector位取自Apple的NSDataDetector参考: https : //developer.apple.com/librarY/mac/documentation/Foundation/Reference/NSDataDetector_Class/Reference/Reference.html

 NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:string attributes:attributes]; __block NSUInteger count = 0; if (!_dataDetector) { NSError *error = nil; _dataDetector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeAddress | NSTextCheckingTypePhoneNumber | NSTextCheckingTypeLink error:&error]; } [_dataDetector enumerateMatchesInString:string options:0 range:NSMakeRange(0, [string length]) usingBlock:^(NSTextCheckingResult *match, NSMatchingFlags flags, BOOL *stop){ NSRange matchRange = [match range]; if ([match resultType] == NSTextCheckingTypeLink) { NSURL *url = [match URL]; if (url) { [attributedString addAttribute:NSLinkAttributeName value:url range:matchRange]; } } else if ([match resultType] == NSTextCheckingTypePhoneNumber) { NSString *phoneNumber = [NSString stringWithFormat:@"tel:%@",[match phoneNumber]]; NSURL *url = [NSURL URLWithString:phoneNumber]; if (url) { [attributedString addAttribute:NSLinkAttributeName value:url range:matchRange]; } } else if ([match resultType] == NSTextCheckingTypeAddress) { //Warning! You must URL escape this! NSString *address = [string substringWithRange:matchRange]; //Warning! You must URL escape this! NSString *urlString = [NSString stringWithFormat:@"http://maps.apple.com/?q=%@",address]; NSURL *url = [NSURL URLWithString:urlString]; if (url) { [attributedString addAttribute:NSLinkAttributeName value:url range:matchRange]; } } if (++count >= 100) *stop = YES; }]; return attributedString;