【问题标题】:iOS 7 UITextView link detection crash in UITableViewUITableView 中的 iOS 7 UITextView 链接检测崩溃
【发布时间】:2013-11-24 12:41:16
【问题描述】:

我在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 - 如this answer 中所述):

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 上测试

【问题讨论】:

  • 你确定不是[postsArray objectAtIndex:indexPath.row]??
  • 当然不是!仅当数据检测器类型打开时才会发生崩溃。

标签: ios cocoa-touch uitableview uitextview


【解决方案1】:

当两个具有数据类型的单元格在出队时发生崩溃 使用相同的小区标识符。 这似乎是 iOS 中的一个错误,但 Apple 可能有充分的理由以这种方式实现它。 (内存方面)

因此,唯一 100% 防弹的解决方案是为细胞提供唯一标识符 包含数据类型。 当然,这并不意味着您将为表格中的所有单元格设置唯一标识符, 因为它会占用太多内存,而且你的表格滚动会很慢。

您可以使用 NSDataDetector 来确定是否在您的文本中找到了匹配的类型, 然后才将找到的对象保存为单元格标识符,如下所示:

- (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 *detector 初始化为静态 而不是为每个单元初始化它可以提高性能。

【讨论】:

  • 哇!非常感谢你的好帖子!我只是放弃了这个问题,因为我找不到解决方案。稍后我会详细研究您的建议,谢谢您的回答!
  • @AmitP 您是否尝试设置属性文本而不是纯文本?
【解决方案2】:

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

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

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

[descriptionLabel setDataDetectorTypes: UIDataDetectorTypeLink];

为我工作。也许它会取消文本视图内正在进行的绘图并以这种方式避免崩溃。

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

【讨论】:

  • 所以这里的逻辑好像和设置descriptionLabel.text = nil的方法一样?
  • 它不能修复或防止崩溃
  • @AmitP 确认,它没有。
  • 将字段设置为 UIDataDetectorTypeNone,然后立即设置为 UIDataDetectorTypeLink,然后再设置“文本”属性对我们有用。谢谢!
【解决方案3】:

如果您使用的是 iOS6 或更高版本,您可以使用 NSDataDetector 来创建归属字符串并将其用作您的 TextView 文本。我们将使用以下方法的修改版本。该方法接受一个字符串和一些已经预定义的属性(如字体和文本颜色),并将在第 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;

【讨论】:

    猜你喜欢
    • 2013-09-28
    • 1970-01-01
    • 2016-07-07
    • 1970-01-01
    • 2013-10-07
    • 2013-10-29
    • 2012-11-01
    • 2017-06-05
    • 2014-01-12
    相关资源
    最近更新 更多