iOS 7 UITextView link detection crash in UITableView

前端 未结 3 2018
梦毁少年i
梦毁少年i 2020-12-31 11:26

I have a custom UITableView cell set up in my UITableView like this:

- (UITableViewCell *)tableView:(UITableView *)tableView cellFo         


        
相关标签:
3条回答
  • 2020-12-31 12:04

    Providing you are using iOS6 or above, you can use an NSDataDetector to make an attributable string and use that as your TextView text. 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. It has some problems multiple phone numbers, though. You need to define your own code for URL escapping the address. 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

    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;
    
    0 讨论(0)
  • 2020-12-31 12:17

    I could reproduce your crash. Implementing the following method within the TableViewCell subclass

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

    and add following call within - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath before setting the text:

    [descriptionLabel setDataDetectorTypes: UIDataDetectorTypeLink];
    

    worked for me. Maybe it cancels ongoing drawing inside the textview and is avoiding the crash that way.

    edit: Calling [descriptionLabel setDataDetectorTypes: UIDataDetectorTypeNone]; and [descriptionLabel setDataDetectorTypes: UIDataDetectorTypeLink]; just before setting the text also seems to fix the crash

    0 讨论(0)
  • 2020-12-31 12:22

    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. (memory wise)

    And so the only 100% bullet proof solution is to provide a unique identifier for cells containing data types. 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:

    - (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.

    0 讨论(0)
提交回复
热议问题