I have a custom UITableView
cell set up in my UITableView
like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellFo
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;
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
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.