简体   繁体   English

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

[英]iOS 7 UITextView link detection crash in UITableView

I have a custom UITableView cell set up in my UITableView like this: 我在我的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;
}

I set up the cell like this (specifically setting the UITextView.text to nil - as noted in this answer ): 我像这样设置单元格(特别是将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];

The cells are 100% reusable and UITextView is inited like this (as you see, nothing special): 单元格是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];

But when the table has around 50 cells and when I scroll it fast I get the following crash: 但是当桌子有大约50个单元格时,当我快速滚动它我得到以下崩溃:

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

Which is absolutely ridiculous - I comment out this line - descriptionLabel.dataDetectorTypes = UIDataDetectorTypeLink; 这绝对是荒谬的 - 我注释掉这一行 - descriptionLabel.dataDetectorTypes = UIDataDetectorTypeLink; and the app stops crashing! 并且应用程序停止崩溃! I've spent hours trying to figure out what the problem was and now I simply get this. 我花了好几个小时试图弄清问题是什么,现在我只是得到了这个。

Tested on iOS 7.0.3 在iOS 7.0.3上测试

The crash happens when two cells with data type are being dequeued while using the same cell identifier. 当使用相同的单元标识符时,两个具有数据类型的单元格正在出列时发生崩溃。 It seems to be a bug in iOS, but Apple may have good reasons to implement it this way. 这似乎是iOS中的一个错误,但Apple可能有充分的理由以这种方式实现它。 (memory wise) (记忆明智)

And so the only 100% bullet proof solution is to provide a unique identifier for cells containing data types. 因此,唯一的100%防弹解决方案是为包含数据类型的单元格提供唯一标识符。 This doesn't mean you will set a unique identifier to all cells in your table, of course, as it will eat up too much memory and your table scroll will be really slow. 这并不意味着您将为表中的所有单元格设置唯一标识符,当然,因为它会占用太多内存而您的表滚动会非常慢。

You can use NSDataDetector to determine if a matched type was found on your text, and only then save the found object as the cell identifier, like so: 您可以使用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];
...

Note: Initializing NSDataDetector *detector as static rather than initialize it for each cell improves performance. 注意:将NSDataDetector *检测器初始化为静态而不是为每个单元初始化它可以提高性能。

I could reproduce your crash. 我可以重现你的崩溃。 Implementing the following method within the TableViewCell subclass 在TableViewCell子类中实现以下方法

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

and add following call within - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath before setting the text: 并在以下内容中添加以下调用- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath在设置文本之前:

[descriptionLabel setDataDetectorTypes: UIDataDetectorTypeLink];

worked for me. 为我工作。 Maybe it cancels ongoing drawing inside the textview and is avoiding the crash that way. 也许它取消了在textview中持续绘制并避免崩溃的方式。

edit: Calling [descriptionLabel setDataDetectorTypes: UIDataDetectorTypeNone]; 编辑:调用[descriptionLabel setDataDetectorTypes: UIDataDetectorTypeNone]; and [descriptionLabel setDataDetectorTypes: UIDataDetectorTypeLink]; [descriptionLabel setDataDetectorTypes: UIDataDetectorTypeLink]; just before setting the text also seems to fix the crash 在设置文本之前,似乎也解决了崩溃问题

Providing you are using iOS6 or above, you can use an NSDataDetector to make an attributable string and use that as your TextView text. 如果您使用的是iOS6或更高版本,则可以使用NSDataDetector创建一个可归属的字符串,并将其用作TextView文本。 A modified version of the following method is what we are going to be using. 我们将要使用以下方法的修改版本。 The method takes a string and some already predefined attributes (like font and text color), and will stop after the 100th link. 该方法采用字符串和一些已预定义的属性(如字体和文本颜色),并在第100个链接后停止。 It has some problems multiple phone numbers, though. 但是,它有多个电话号码的问题。 You need to define your own code for URL escapping the address. 您需要为URL转发地址定义自己的代码。 The the NSDataDetector bit was taken from Apple's NSDataDetector reference: https://developer.apple.com/librarY/mac/documentation/Foundation/Reference/NSDataDetector_Class/Reference/Reference.html 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;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM