* I got it figured out thanks to Paulw11 and sjeohp comments. I was using the amount of text to change the cell height and didnt correct the objectAtIndex in that method when i changed to using the array over static text. *
I have a chat app that im trying to use a tableView to show the chat interaction. Im saving the "source" and the contents into an array and trying to get the tableview to reload that anytime a message is sent or received but it crashes everytime on the reloadData call.
In the ViewDidLoad:
chatLog = [[NSMutableArray alloc]init];
and then other methods:
-(IBAction)sendMessagePressed:(UIBarButtonItem *)sender {
NSLog(#"Send Pressed");
// if the message bar isnt blank send the message
if ( ![messageInputBar.text isEqualToString:#""] ) {
NSString* text = [NSString stringWithFormat:#"You Wrote:\n%#", messageInputBar.text];
NSString* source = #"self";
NSArray* sent = [[NSArray alloc]initWithObjects:source, text, nil];
messageInputBar.text = #"";
[self CloseMessageKeys];
[chatLog addObject:sent];
// this is where it crashes. this code was working with static text before
// i implemented the mutable array
[chatTable reloadData];
NSData* messageData = [messageInputBar.text dataUsingEncoding:NSUTF8StringEncoding];
NSArray* connectedPeers = _app_Delegate.mpc_Handler.mpc_session.connectedPeers;
NSError* error;
[_app_Delegate.mpc_Handler.mpc_session sendData:messageData toPeers:connectedPeers withMode:MCSessionSendDataReliable error:&error];
if (error) {
NSLog(#"%#", [error localizedDescription]);
}
}
}
This is the TableView cellForRowAtIndexPath:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if ( [[[chatLog objectAtIndex:indexPath.row]objectAtIndex:0]isEqualToString:#"self"]) {
OutgoingTableViewCell* cell = [chatTable dequeueReusableCellWithIdentifier:#"outgoingCell" forIndexPath:indexPath];
NSString* text = [[chatLog objectAtIndex:indexPath.row]objectAtIndex:1];
cell.txtLabel.text = text;
[[cell txtLabel] setNumberOfLines:0]; // unlimited number of lines
[[cell txtLabel] setLineBreakMode: NSLineBreakByWordWrapping];
[[cell txtLabel] setFont:[UIFont systemFontOfSize: 14.0]];
cell.backgroundColor = [UIColor greenColor];
[self showTheBottom];
return cell;
} else {
IncomingTableViewCell* cell = [chatTable dequeueReusableCellWithIdentifier:#"incomingCell" forIndexPath:indexPath];
NSString* text = [[chatLog objectAtIndex:indexPath.row]objectAtIndex:1];
cell.txtLabel.text = text;
[[cell txtLabel] setNumberOfLines:0]; // unlimited number of lines
[[cell txtLabel] setLineBreakMode: NSLineBreakByWordWrapping];
[[cell txtLabel] setFont:[UIFont systemFontOfSize: 14.0]];
cell.backgroundColor = [UIColor blueColor];
[self showTheBottom];
return cell;
}
}
length is not a method on NSArray. Use [array count]
I got it figured out thanks to #Paulw11 and #sjeohp comments. I was using the amount of text to change the cell height and didnt correct the objectAtIndex in that method when i changed to using the array over static text. *
in another method I had:
NSAttributedString *aString = [NSAttributedString alloc] initWithString:[chatLog objectAtIndex:indexPath.row];
and it needed to be:
NSAttributedString *aString = [[NSAttributedString alloc] initWithString:[[chatLog objectAtIndex:indexPath.row]objectAtIndex:1]];
Thanks to #Paulw11 and #sjeohp for helping me figure out the exception breakpoint feature in Xcode
Your dequeueReusableCell code is fail. you need check if it nil then init cell. And check about numberOfRowsInSection code to return chatLog.count
Related
I've been trying to parse an array and split it into separate strings. I have managed to successfully get this working by individually adding a fixed objectAtIndex when I set the strings.
Example:
NSString *weightString = [[[results valueForKey:#"Endurance"] objectAtIndex:7]objectAtIndex:2];
However, when I try and set the strings in a tableView didSelectRowAtIndexPath using any objectAtIndex the string always returns null. I need to be able to click on the tableView row using objectAtIndex:indexPath.row. I can't work out where I am going wrong. Feel free to request more code or queries.
Query:
PFQuery *arrayQuery = [PFQuery queryWithClassName:#"Exercises"];
[arrayQuery selectKeys:#[#"Endurance"]];
[arrayQuery orderByAscending:#"ExerciseName"];
[arrayQuery findObjectsInBackgroundWithBlock:^(NSArray *results, NSError *error) {
if (!error) { self.arrayResults = [results valueForKey:#"Endurance"];
cellForRowAtIndexPath:
- (TableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"list";
TableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
UILabel *cellTitle;
cell.cellTitle.text = [[browseAllArray objectAtIndex: indexPath.row] objectForKey:#"ExerciseName"];
if (cell == nil) {
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
cell = [[TableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
//Exercise Bar Label
cellTitle = [[UILabel alloc] initWithFrame:CGRectMake(80, 20, 220, 60)];
cellTitle.font = [UIFont fontWithName:#"ethnocentric" size:14];
cellTitle.textColor = [UIColor blackColor];
cellTitle.numberOfLines = 2;
cellTitle.textAlignment = NSTextAlignmentCenter;
cell.cellTitle.text = [[browseAllArray objectAtIndex: indexPath.row] objectForKey:#"ExerciseName"];
[self.tableView reloadData];
}
cellTitle.text = [[browseAllArray objectAtIndex:indexPath.row]objectForKey:#"ExerciseName"];
return cell;
I have managed to solve my problem by changing the string in the didSelectRowAtIndexPath method.
Instead of using:
NSString *weightString = [[[results valueForKey:#"Endurance"] objectAtIndex:indexPath.row]objectAtIndex:2];
I used:
NSString *weightString = [[arrayResults objectAtIndex:indexPath.row] objectAtIndex:2];
I decided to try and use the array instead of results string as it wouldn't let me pull the information from the string. I would recommend following #Larme 's steps above as they definitely sent me in the right direction.
If people are trying to follow my steps make sure you remove the valueForKey as this was throwing a fatal error.
I am developing an IOS App. A row consists of an TextField and some textviews. When the value of TextField is changed for any row the number in the TextField should be multiplied with the number in a Label og the same row and displayed in another Label in the same row. What actually happens is that the values are multiplied with the labels of another cell which is apparently random.. The image shows what is actually happening. This problem has me sorely puzzled for days and I am at my wits end.
I Tried Fetching Row From Tableview using didSelectRowAtIndex and
indexPath.row.The Result Are are confusing as row index of first Two rows is being Showns as 0.
IndexPath correct in cellForRowAtIndexPath but is wrong in Textfield Method [ - (void) changedvalue: (UITextField *)textfield ]. What Could Be Worng ?
Any help or advice is greatly appreciated. Thanks in advance.
//Code
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return _fetchedobj.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *MyIdentifier = #"MyReuseIdentifier";
UITableViewCell *cell = [myTable dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier];
}
data = [_fetchedobj objectAtIndex:indexPath.row];
_quantity = [[UITextField alloc] initWithFrame:CGRectMake(5.0f,28.0f,50.0f , 30.0f)];
[_quantity setTag:indexPath.row];
[_quantity setFont:[UIFont systemFontOfSize:14.0f]];
[_quantity setTextAlignment:NSTextAlignmentCenter];
_quantity.backgroundColor = [UIColor whiteColor];
[_quantity setAdjustsFontSizeToFitWidth:YES];
[_quantity addTarget:self action:#selector(changedvalue:) forControlEvents:UIControlEventAllEditingEvents];
NSString *q_value = [data valueForKey:#"quantity"];
NSInteger q_value1 = [q_value integerValue];
[_quantity setText:[NSString stringWithFormat:#"%li",(long)q_value1]];
[cell addSubview:_quantity];
_total1 = [[UILabel alloc ]initWithFrame:CGRectMake(50.0f, 58.0f, 80.0f, 25.0f)];
[_total1 setFont:[UIFont systemFontOfSize:14.0f]];
[_total1 setTag:1];
[_total1 setTextAlignment:NSTextAlignmentCenter];
[_total1 setAdjustsFontSizeToFitWidth:YES];
_total1.backgroundColor = [UIColor whiteColor];
NSString *totalAmount = [data valueForKey:#"amount"];
NSInteger total_Amount = [totalAmount integerValue];
[_total1 setText:[NSString stringWithFormat:#"%li",(long)total_Amount]];
[cell addSubview:_total1];
- (void) changedvalue: (UITextField *)textfield
{
NSLog(#"%ld",(long)[textfield tag]);
NSString *text = [textfield text];
NSInteger text1 = [text integerValue];
NSString *changevalue = [dict objectForKey:#"_max_purchases_per_user"];
NSInteger valueChange = [changevalue integerValue];
NSString *userPurchase = [dict objectForKey:#"_max_purchases"];
NSInteger itemPurchase = [userPurchase integerValue];
NSString *amount = [data valueForKey:#"amount"];
NSInteger amount1 = [amount integerValue];
NSInteger minValue=MIN(valueChange, itemPurchase);
if (text1 > minValue) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:[textfield tag] inSection:0];
UITableViewCell *cell = (UITableViewCell *) [myTable cellForRowAtIndexPath:indexPath];
// UITextField * field=cell.subviews[1];
// field.text = [NSString stringWithFormat:#"%li",minValue * amount1];
NSLog(#"%#",cell);
cell.textLabel.text = [NSString stringWithFormat:#"%li",minValue * amount1];
//[_quantity setText:[NSString stringWithFormat:#"%li",(long)minValue]];
//[ _total1 setText:[NSString stringWithFormat:#"%li",minValue * amount1]];
}else{
[_total1 setText:[NSString stringWithFormat:#"%li",text1 * amount1]];
}
// NSString *Amount =[data valueForKey:#"amount"];
// NSNumber *aNum = [NSNumber numberWithInteger:[Amount integerValue]];
//
// [_total1 setText:[NSString stringWithFormat:#"%ld",([i integerValue] * [aNum integerValue])]];
}
You urgently need to take into account that cells are reused. That's what the "dequeueWithReuseIdentifier" does. So if you have 100 rows, and there are up to 10 visible at a time, you will have 10 rows using the same cell.
You must absolutely NOT create new textfields when dequeueWithReuseIdentifier doesn't return nil. It returns a cell that has already all the textfields created for it, from the previous time that the cell was used for a different or the same row.
And using indexPath.row as the index is a recipe for disaster if the user can add or remove rows. Say you are displaying row 10, then I delete row 8. Your cell now is in row 9, but has a tag that says row 10.
When my UITableView loads for the first time, everything in the code below functions correctly. However, if it reloads for whatever reason (refresh, etc.), it starts assigning a cell.bestMatchLabel.text value of #"Best Match" to random cells, rather than only the first one as I specified in the code. Why is calling reload on my table causing the below code to not run correctly?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//load top 3 data
NSDictionary *currentSectionDictionary = _matchCenterArray[indexPath.section];
NSArray *top3ArrayForSection = currentSectionDictionary[#"Top 3"];
// if no results for that item
if (top3ArrayForSection.count-1 < 1) {
// Initialize cell
static NSString *CellIdentifier = #"MatchCenterCell";
EmptyTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
// if no cell could be dequeued create a new one
cell = [[EmptyTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
// title of the item
cell.textLabel.text = #"No items found, but we'll keep a lookout for you!";
cell.textLabel.font = [UIFont systemFontOfSize:12];
cell.detailTextLabel.text = [NSString stringWithFormat:#""];
[cell.imageView setImage:[UIImage imageNamed:#""]];
return cell;
}
// if results for that item found
else {
// Initialize cell
static NSString *CellIdentifier = #"MatchCenterCell";
MatchCenterCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
// if no cell could be dequeued create a new one
cell = [[MatchCenterCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
tableView.separatorColor = [UIColor clearColor];
if (indexPath.row == 0) {
cell.bestMatchLabel.text = #"Best Match";
cell.bestMatchLabel.font = [UIFont systemFontOfSize:12];
cell.bestMatchLabel.textColor = [UIColor colorWithRed:0.18 green:0.541 blue:0.902 alpha:1];
[cell.contentView addSubview:cell.bestMatchLabel];
}
// title of the item
cell.textLabel.text = _matchCenterArray[indexPath.section][#"Top 3"][indexPath.row+1][#"Title"];
cell.textLabel.font = [UIFont systemFontOfSize:14];
// price + condition of the item
NSString *price = [NSString stringWithFormat:#"$%#", _matchCenterArray[indexPath.section][#"Top 3"][indexPath.row+1][#"Price"]];
NSString *condition = [NSString stringWithFormat:#"%#", _matchCenterArray[indexPath.section][#"Top 3"][indexPath.row+1][#"Item Condition"]];
cell.detailTextLabel.text = [NSString stringWithFormat:#"%# - %#", price, condition];
cell.detailTextLabel.textColor = [UIColor colorWithRed:0.384 green:0.722 blue:0.384 alpha:1];
// Load images using background thread to avoid the laggy tableView
[cell.imageView setImage:[UIImage imageNamed:#"Placeholder.png"]];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
// Download or get images here
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:_matchCenterArray[indexPath.section][#"Top 3"][indexPath.row+1][#"Image URL"]]];
// Use main thread to update the view. View changes are always handled through main thread
dispatch_async(dispatch_get_main_queue(), ^{
// Refresh image view here
[cell.imageView setImage:[UIImage imageWithData:imageData]];
cell.imageView.layer.masksToBounds = YES;
cell.imageView.layer.cornerRadius = 2.5;
[cell setNeedsLayout];
});
});
return cell;
}
}
That is because table dequeue same cell for multiple indexes as you scroll down, so you need to add else statement in the following code
if (indexPath.row == 0) {
cell.bestMatchLabel.text = #"Best Match";
cell.bestMatchLabel.font = [UIFont systemFontOfSize:12];
cell.bestMatchLabel.textColor = [UIColor colorWithRed:0.18 green:0.541 blue:0.902 alpha:1];
[cell.contentView addSubview:cell.bestMatchLabel];
[cell.bestMatchLabel setHidden:NO];
} else {
[cell.bestMatchLabel setHidden:YES];
}
but a better approach for this case is to use different cell identifier for that row and only add the bestMatchLabel once when first creating the cell
I want to be able to change the text in my UITableView cells when they are tapped on (selected/highlighted). The content from each cell is derived from an NSAttributedString. Here is that code:
-(NSAttributedString*)formattedSubject:(int)state {
if(formattedSubject!=nil) return formattedSubject;
NSDictionary *boldStyle = [[NSDictionary alloc] init];
if(state==1) {
boldStyle = #{NSFontAttributeName:[UIFont fontWithName:#"Helvetica-Bold" size:16.0],NSForegroundColorAttributeName:[UIColor colorWithRed:0.067 green:0.129 blue:0.216 alpha:1.0]};
}
else {
boldStyle = #{NSFontAttributeName:[UIFont fontWithName:#"Helvetica-Bold" size:16.0],NSForegroundColorAttributeName:[UIColor whiteColor]};
}
NSDictionary* normalStyle = #{NSFontAttributeName: [UIFont fontWithName:#"Helvetica" size:14.0]};
NSMutableAttributedString* articleAbstract = [[NSMutableAttributedString alloc] initWithString:subject];
[articleAbstract setAttributes:boldStyle range:NSMakeRange(0, subject.length)];
[articleAbstract appendAttributedString:[[NSAttributedString alloc] initWithString:#"\n"]];
int startIndex = [articleAbstract length];
NSTimeInterval _interval=[datestamp doubleValue];
NSDate *date = [NSDate dateWithTimeIntervalSince1970:_interval];
NSDateFormatter *_formatter=[[NSDateFormatter alloc]init];
[_formatter setDateFormat:#"MM/dd/yy"];
NSString* description = [NSString stringWithFormat:#"By %# on %#",author,[_formatter stringFromDate:date]];
[articleAbstract appendAttributedString:[[NSAttributedString alloc] initWithString: description]];
[articleAbstract setAttributes:normalStyle range:NSMakeRange(startIndex, articleAbstract.length - startIndex)];
formattedSubject = articleAbstract;
return formattedSubject;
}
As you can see, I would like the boldStyle text to be a certain color (given by [UIColor colorWithRed:0.067 green:0.129 blue:0.216 alpha:1.0]) when the state is "1", and white otherwise. I've currently tried the following modifications in cellForRowAtIndexPath, as well as didSelectRowAtIndexPath:
cellForRowAtIndexPath:
NSIndexPath *path = [tableView indexPathForSelectedRow];
if(path) {
cell.textLabel.attributedText = [news formattedSubject:1];
}
else {
cell.textLabel.attributedText = [news formattedSubject:0];
}
What I am doing here is checking if a cell is selected, and if so then I changed that cell's "attributedText" to the "0" format. However, this didn't work, and I suspected it was because the modification had to be made when the cell was actually selected. So I tried the following in didSelectRowAtIndexPath:
didSelectRowAtIndexPath:
News *news = newsArray[indexPath.row];
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
cell.textLabel.attributedText = [news formattedSubject:0];
However, it seems like the change is never made. I put an NSLog in the "else" part of the boldStyle if/else statement, and it was never written to the console.
Neither of my attempts at a solution have worked, and I've run out of ideas. How can I get the NSAttributedString to change when a cell is highlighted?
Update: It's also interesting that normalStyle text, which is by default black, does turn white when the cell is highlighted/selected when I add cell.textLabel.highlightedTextColor = [UIColor whiteColor]; in cellForRowAtIndexPath, but boldStyle text does not.
I have problem the same, and..
cell.textLabel.enable = NO;
It just resolved
In your cellForRowAtIndexPath method you are checking if a selectedIndexPath in this tableView exists.
NSIndexPath *path = [tableView indexPathForSelectedRow];
if(path) {
I think you want to compare your indexPath and your selectedIndexPath. You should use isEqual: for that.
NSIndexPath *selectedIndexPath = [tableView indexPathForSelectedRow];
if([selectedIndexPath isEqual:indexPath]) {
I have a custom UITableView cell set up in my UITableView like this:
- (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):
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):
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:
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; 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
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.
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
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;