UILabel Bold / Highlight All occurrences SubString - ios

I have multiple UILabels within a Custom Table cell. These labels contain varied text or varied length.
As it stands i have UILabel Subclassed allowing me to implement these methods
- (void)boldRange:(NSRange)range {
if (![self respondsToSelector:#selector(setAttributedText:)]) {
return;
}
NSMutableAttributedString *attributedText;
if (!self.attributedText) {
attributedText = [[NSMutableAttributedString alloc] initWithString:self.text];
} else {
attributedText = [[NSMutableAttributedString alloc]initWithAttributedString:self.attributedText];
}
[attributedText setAttributes:#{NSFontAttributeName:[UIFont boldSystemFontOfSize:self.font.pointSize]} range:range];
self.attributedText = attributedText;
NSLog(#"%#", NSStringFromRange(range));
}
- (void)boldSubstring:(NSString*)substring {
NSRange range = [self.text rangeOfString:substring];
[self boldRange:range];
}
This allows me to call [cell.StoryLabel boldSubstring:#"test"]; which will BOLD the first occurrence of the word 'test'.
What i am after is the ability to either create new subclass methods or extend the ones i already have, to allow me to replace ALL occurrences of a specified word within the label.
I have looked into a number of methods including 3rd party frameworks. The trouble i have is this is a learning process for me. I would be far more beneficial for me to try and complete this myself.
Thanks in advance!

rangeOfString returns the first occurrence, that's normal behavior.
From the Doc:
Finds and returns the range of the first occurrence of a given string
within the receiver.
You could use a NSRegularExpression, and use matchesInString:options:range to get a NSArray of NSTextCheckingResult (that have a NSRange property), an use a for loop to bold it.
This should do the trick:
- (void)boldSubstring:(NSString*)substring
{
if (![self respondsToSelector:#selector(setAttributedText:)])
{
return;
}
NSError *error;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern: substring options:NSRegularExpressionCaseInsensitive error:&error];
if (!error)
{
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:[self text]];
NSArray *allMatches = [regex matchesInString:[self text] options:0 range:NSMakeRange(0, [[self text] length])];
for (NSTextCheckingResult *aMatch in allMatches)
{
NSRange matchRange = [aMatch range];
[attributedString setAttributes:#{NSFontAttributeName:[UIFont boldSystemFontOfSize:self.font.pointSize]} range: matchRange];
}
[self setAttributedText:attributedString];
}
}

Related

How to make the highlighted matches (NSRegularExpression)?

I want to make the matches were blue in TextView.
NSString *text = #"Except as contained in #wep this notice, the name of a copyright #ololo holder shall not be used in #pewpewpew advertising or otherwise to #promote";
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"#\\w*" options:0 error:NULL];
NSArray *matches = [regex matchesInString:text options:0 range:NSMakeRange(0, [text length])];
NSLog(#"count %d", matches.count);
for (NSTextCheckingResult *match in matches) {
NSRange matchRange = [match range];
NSString *match = [text substringWithRange:matchRange];
NSLog(#"Matched string: %#", match);
}
self.myTextView.text = text;
you have to create NSAttributedString and then apply font as per range and set attributedText to your UITextView not a simple text.
your code should look like below.
NSMutableAttributedString *attrib = [[NSMutableAttributedString alloc]initWithString:text];
for (NSTextCheckingResult *match in matches) {
NSRange matchRange = [match range];
NSString *match = [text substringWithRange:matchRange];
NSLog(#"Matched string: %#", match);
// set your rgb color here which you want i added blue color.
[attrib addAttribute:NSForegroundColorAttributeName
value:[UIColor blueColor]
range:matchRange];
}
// set attributed text not a normal text.
self.myTextView.attributedText = attrib;
Maybe this will help you.
self.myTextView.attributedText = // your attributed string here

Detect underscore from a particular word of a UITextView's text

I want to detect "_" from a particular word of a UITextView's text.
I already tried this :
But this calculates the range of first underscore only.
NSMutableAttributedString *string = [[NSMutableAttributedString alloc]initWithString:textView.text];
NSRange range=[textView.text rangeOfString:#"_"];
[string addAttribute:NSForegroundColorAttributeName value:[UIColor clearColor] range:range];
textView.attributedText=string;
My output is coming like this
Tadeusz_Kościuszko was a military leader who became a national hero in
Poland. A poster for the Paris premiere of Jules_Massenet's 1910 opera
Don_Quichotte.
And I want something like this:
Tadeusz Kościuszko was a military leader who became a national hero in
Poland. A poster for the Paris premiere of Jules Massenet's 1910 opera
Don Quichotte.
Also I want to clear color the underscores just from the names. Not from the other text of UITextView.
I dont want to remove it from the whole text of UITextView but just from the particular words.
Anyone having suggestions?
Thanks.
textView.text = [textView.text stringByReplacingOccurrencesOfString:#"_" withString:#" "];
NSMutableAttributedString *string = [[NSMutableAttributedString alloc]initWithString:textView.text];
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"(_)" options:kNilOptions error:nil];
NSRange range = NSMakeRange(0,string.length);
[regex enumerateMatchesInString:string options:kNilOptions range:range usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
NSRange subStringRange = [result rangeAtIndex:1];
[string addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:subStringRange];
}];
textView.attributedText=string;
try this -
NSString *str = #"Tadeusz_Kościuszko was a military leader who became a national hero in Poland. A poster for the Paris premiere of Jules_Massenet's 1910 opera Don_Quichotte.";
NSString *newStr = [str stringByReplacingOccurrencesOfString:#"_" withString:#" "];
NSLog(#"NEW STR ::::: %#",newStr);
NSString *str = #"This_is_a_string";
str = [str stringByReplacingOccurrencesOfString:#"_"
withString:#" "];
// str = "This is a string"
More on NSString
Found the workaround :)
NSString *strTemp = textView.text;
NSMutableAttributedString *mutString = [[NSMutableAttributedString alloc]initWithString:strTemp];
NSString *substring = #"_";
NSRange searchRange = NSMakeRange(0,mutString.length);
NSRange wholeTextRange = [strTemp rangeOfString:strTemp];
[mutString addAttribute:NSForegroundColorAttributeName value:[UIColor blueColor] range:wholeTextRange];
NSRange foundRange;
while (searchRange.location < mutString.length)
{
searchRange.length = mutString.length-searchRange.location;
foundRange = [strTemp rangeOfString:substring options:nil range:searchRange];
if (foundRange.location != NSNotFound) {
// found an occurrence of the substring! do stuff here
[mutString addAttribute:NSForegroundColorAttributeName value:[UIColor clearColor] range:foundRange];
searchRange.location = foundRange.location+foundRange.length;
} else {
// no more substring to find
[txtComment setAttributedText:mutString];
break;
}
}
If you want to selectively remove underscores you need to know the range of text from which you want to remove them.
Then you can use a regular expression to get an array of ranges which match the underscore in that known range.
NSError *error;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"_" options:0 error:&error];
NSArray *matchingRangesInSelectedRange = [regex matchesInString:yourString options:0 range:knownRangeToCheckForMatches];
You can then iterate over the ranges in the array and do whatever changes you wish.
If you know which strings should not be cleaned or which that should not be cleaned you could do something like this:
NSString *stringWithUnderScore = #"A rather long text with some_odd_underscores but also names like André_Franquin & Albert_Uderzo";
NSSet *stringsThatShouldKeepUnderscore = [NSSet setWithObjects:#"some_odd_underscores", nil];
NSArray *singleStrings = [stringWithUnderScore componentsSeparatedByString:#" "];
NSMutableString *newCompleteString = [[NSMutableString alloc] init];
for (NSString *singleString in singleStrings) {
if (![stringsThatShouldKeepUnderscore containsObject:singleString]){
NSString *fixedString = [singleString stringByReplacingOccurrencesOfString:#"_" withString:#" "];
[newCompleteString appendString: fixedString];
} else{
[newCompleteString appendString:singleString];
}
[newCompleteString appendString:#" "];
}
NSLog(#"new string:%#",newCompleteString);
Edit: If you do not have this information you could try to make some kind of parser checking if each of the strings separated by "_" have capital-letters, indicating a name. Not fail-safe, but without knowledge about the text I can't really see any better option.

Highlight selected character dynamically in iOS

I want to highlight selected character in iOS, as you an see in iPhone iOS 7 Notes app.
When you search for particular text the search character will be highlighted in the result displayed in UITableView.
Example:
"This is my name"
h - should 've blue color
y - should 've red color
The character customisation should be dynamic. I hope I've briefed enough.
Looking for excellent response buddies!
Try having a look at TextKit
This is a nice website that shows you the basics
I hope I understood your question..
You can easily achieve this by setting an attributed string to the text label in a UITableViewCell that shows an entry of your search results.
You would need to calculate ranges of the substrings that should be highlighted. You can do it using regular expressions as I wrote in my sample code. This way you can support multiple occurrences of the substring in a string.
Then, when you have the ranges you just apply special attributes and set the cell label's attributedText property.
The code could look like this:
NSString *searchTerm = ...;
NSString *text = #"This is a sample text that is super cool";
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:text];
NSString *pattern = [NSString stringWithFormat:#"(%#)", searchTerm];
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern
options:kNilOptions
error:nil];
NSRange range = NSMakeRange(0, text.length);
[regex enumerateMatchesInString:text
options:kNilOptions
range:range
usingBlock:^(NSTextCheckingResult *result,
NSMatchingFlags flags,
BOOL *stop)
{
NSRange subStringRange = [result rangeAtIndex:1];
[attributedString addAttribute:NSForegroundColorAttributeName
value:[UIColor blueColor]
range:subStringRange];
}];
Then it's easy. You just set the UILabel's attributedText when creating a cell in tableView:cellForRowAtIndexPath: method.
This should point you to the right direction.
Use NsMutableAttributedString to highlight characters
NSMutableAttributedString *str = [[NSMutableAttributedString alloc] initWithString:display];
[str addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:NSMakeRange([displayString length], ([details.notificationCount length]+2))];
displayLabel.attributedText = str;

CGRect for selected UITextRange adjustment for multiline text?

I've used this answer in order to create a CGRect for a certain range of text.
In this UITextView I've set it's attributedText (so I've got a bunch of styled text with varying glyph sizes).
This works great for the first line of text that's left aligned, but it has some really strange results when working with NSTextAlignmentJustified or NSTextAlignmentCenter.
It also doesn't calculate properly when the lines wrap around or (sometimes) if there are \n line breaks.
I get stuff like this (this is center aligned):
When instead I expect this:
This one has a \n line break - the first two code bits were highlighted successfully, but the last one more code for you to see was not because the text wrapping isn't factored into the x,y calculations.
Here's my implementation:
- (void)formatMarkdownCodeBlockWithAttributes:(NSDictionary *)attributesDict
withHighlightProperties:(NSDictionary *)highlightProperties
forFontSize:(CGFloat)pointSize
{
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"`.+?`" options:NO error:nil];
NSArray *matchesArray = [regex matchesInString:[self.attributedString string] options:NO range:NSMakeRange(0, self.attributedString.length)];
for (NSTextCheckingResult *match in matchesArray)
{
NSRange range = [match range];
if (range.location != NSNotFound) {
self.textView.attributedText = self.attributedString;
CGRect codeRect = [self frameOfTextRange:range forString:[[self.attributedString string] substringWithRange:range] forFontSize:pointSize];
UIView *highlightView = [[UIView alloc] initWithFrame:codeRect];
highlightView.layer.cornerRadius = 4;
highlightView.layer.borderWidth = 1;
highlightView.backgroundColor = [highlightProperties valueForKey:#"backgroundColor"];
highlightView.layer.borderColor = [[highlightProperties valueForKey:#"borderColor"] CGColor];
[self.contentView insertSubview:highlightView atIndex:0];
[self.attributedString addAttributes:attributesDict range:range];
//strip first and last `
[[self.attributedString mutableString] replaceOccurrencesOfString:#"(^`|`$)" withString:#" " options:NSRegularExpressionSearch range:range];
}
}
}
- (CGRect)frameOfTextRange:(NSRange)range forString:(NSString *)string forFontSize:(CGFloat)pointSize
{
self.textView.selectedRange = range;
UITextRange *textRange = [self.textView selectedTextRange];
CGRect rect = [self.textView firstRectForRange:textRange];
//These three lines are a workaround for getting the correct width of the string since I'm always using the monospaced Menlo font.
rect.size.width = ((pointSize / 1.65) * string.length) - 4;
rect.origin.x+=2;
rect.origin.y+=2;
return rect;
}
Oh, and in case you want it, here's the string I'm playing with:
*This* is **awesome** #mention `code` more \n `code and code` #hashtag [markdown](http://google.com) __and__ #mention2 {#FFFFFF|colored text} This**will also** work but ** will not ** **work** Also, some `more code for you to see`
Note: Please don't suggest I use TTTAttributedLabel or OHAttributedLabel.
I think all your problems are because of incorrect order of instructions.
You have to
Set text aligment
Find required substrings and add specific attributes to them
And only then highlight strings with subviews.
Also you will not need to use "a workaround for getting the correct width of the string since I'm always using the monospaced Menlo font" in such a case.
I have simplified your code a little to make it more understandable.
Result:
- (void)viewDidLoad
{
[super viewDidLoad];
NSDictionary *basicAttributes = #{ NSFontAttributeName : [UIFont boldSystemFontOfSize:18],
NSForegroundColorAttributeName : [UIColor blackColor] };
NSDictionary *attributes = #{ NSFontAttributeName : [UIFont systemFontOfSize:15],
NSForegroundColorAttributeName : [UIColor darkGrayColor]};
_textView.attributedText = [[NSAttributedString alloc] initWithString:
#"*This* is **awesome** #mention `code` more \n `code and code` #hashtag [markdown](http://google.com) __and__ #mention2 {#FFFFFF|colored text} This**will also** work but ** will not ** **work** Also, some `more code for you to see`" attributes:attributes];
_textView.textAlignment = NSTextAlignmentCenter;
[self formatMarkdownCodeBlockWithAttributes:basicAttributes];
}
- (void)formatMarkdownCodeBlockWithAttributes:(NSDictionary *)attributesDict
{
NSMutableString *theString = [_textView.attributedText.string mutableCopy];
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"`.+?`" options:NO error:nil];
NSArray *matchesArray = [regex matchesInString:theString options:NO range:NSMakeRange(0, theString.length)];
NSMutableAttributedString *theAttributedString = [_textView.attributedText mutableCopy];
for (NSTextCheckingResult *match in matchesArray)
{
NSRange range = [match range];
if (range.location != NSNotFound) {
[theAttributedString addAttributes:attributesDict range:range];
}
}
_textView.attributedText = theAttributedString;
for (NSTextCheckingResult *match in matchesArray)
{
NSRange range = [match range];
if (range.location != NSNotFound) {
CGRect codeRect = [self frameOfTextRange:range];
UIView *highlightView = [[UIView alloc] initWithFrame:codeRect];
highlightView.layer.cornerRadius = 4;
highlightView.layer.borderWidth = 1;
highlightView.backgroundColor = [UIColor yellowColor];
highlightView.layer.borderColor = [[UIColor redColor] CGColor];
[_textView insertSubview:highlightView atIndex:0];
}
}
}
- (CGRect)frameOfTextRange:(NSRange)range
{
self.textView.selectedRange = range;
UITextRange *textRange = [self.textView selectedTextRange];
CGRect rect = [self.textView firstRectForRange:textRange];
return rect;
}
I just had to do something similar to this. Assuming you are using iOS 7:
// Build the range that you want for your text
NSRange range = NSMakeRange(location, length);
// Get the substring of the attributed text at that range
NSAttributedString *substring = [textView.attributedText attributedSubstringFromRange:range];
// Find the frame that would enclose the substring of text.
CGRect frame = [substring boundingRectWithSize:maxSize
options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
context:nil];
This should use the NSTextAlignment assigned to the attributed string.
As #Avt answered https://stackoverflow.com/a/22572201/3549781 this question. I'm just answering for the newline problem. This newline problem occurs on iOS 7+ even if you use
[self.textView selectedTextRange] or [self.textView positionFromPosition: offset:]
We just have to ensure the layout of the textView before calling firstRectForRange by
[self.textView.layoutManager ensureLayoutForTextContainer:self.textView.textContainer];
Courtesy : https://stackoverflow.com/a/25983067/3549781
P.S : At first I added this as a comment to the question. As most people don't read comments I added this as an answer.

UITextView: assigning attributedText on iOS 6 leads to unexpected result

I'm trying to implement keywords highlighting using UITextView control.
Here is what's performing in UITextView delegate method:
- (void)textViewDidChange:(UITextView *)textView {
NSAttributedString *attrStr = textView.attributedText;
NSString * string = [attrStr string];
NSRegularExpression* regex = NameRegularExpression();
NSArray * matches = [regex matchesInString:string options:0 range:NSMakeRange(0, [string length])];
NSMutableAttributedString *attrMutableStr = [[NSMutableAttributedString alloc] initWithString:string];
for (NSTextCheckingResult* match in matches ) {
[attrMutableStr addAttribute:NSForegroundColorAttributeName value:[UIColor blueColor] range:match.range];
}
textView.attributedText = attrMutableStr;
textView.contentSize = CGSizeMake(textView.frame.size.width, textView.contentSize.height);
}
User input parses with regexp and all extracted elements are highlighted. It works perfect on iOS 7 but absolutely crazy on iOS 6:
Any suggestions of what am I doing wrong?
By the way, it happens even if just assign exactly the same attributedString back:
- (void)textViewDidChange:(UITextView *)textView {
NSAttributedString *attrStr = textView.attributedText;
textView.attributedText = attrStr;
}

Resources