How to detect emoji and change font size - ios

I have text which contains emoji in it, we are able to display it correctly by doing encoding and decoding the string, what I need to achieve is to increase the font size of only emoji in the text like in image below,
I have got an idea to determine the range of all emoji, and supply in NSAttributedString with increased font size. Now am out of idea how can I detect range of emojis in a given string?
Thanks

I have done the same like
let string = "This is emoji Test"
let attributedEmoji = NSMutableAttributedString(string: " \u{1F600}", attributes: [NSFontAttributeName:UIFont.systemFontOfSize(60)])
let attribString = NSMutableAttributedString.init(string: string)
attribString.appendAttributedString(attributedEmoji)
lblEmoji.attributedText = attribString
You can change the font and font size to scale the emoji.
Put all possible Emoji's(Your application uses) into an array.
Search for emoji into string from array.If found apply attributed Emoji.
Write a method that accept emoji code and return attributed emoji text.
Hope this info will help you in better way.
https://github.com/woxtu/NSString-RemoveEmoji
Find out if Character in String is emoji?

you can use it directly like below or
if ([myString containsString:#"😋"])
{
NSLog(#"one");
//change the font size here.
}
else
{
NSLog(#"fk");
//change the font size here.
}
or you can use
[mystring is isEqualToString:"I believe 😋"];
try those. hope this will help to you.

I have made one demo, You can detect emoji from the string like below,
NSString *str = #"this is 😄 and test 😊";
NSArray *arr = [str componentsSeparatedByString:#" "];
for (int i = 0; i < arr.count; i++) {
NSString *temp = [arr objectAtIndex:i];
if ( ![temp canBeConvertedToEncoding:NSASCIIStringEncoding]) {
NSLog(#"%d",i);
NSLog(#"%#",temp); // temp is emoji. You can detect emoji here from your string now you can manage as per your need
}
}

Thanks to all who answered, but none was complete answer though #Raj's suggestion to look NSString-RemoveEmoji helped me to achieve the solution for this, here it is, it works for any kind of emoji
-(NSMutableAttributedString *)getAttributedEmojiString:(NSString *)inputString{
NSMutableArray *__block emojiRange=[[NSMutableArray alloc] init];
[inputString enumerateSubstringsInRange:NSMakeRange(0, [inputString length])
options:NSStringEnumerationByComposedCharacterSequences
usingBlock: ^(NSString* substring, NSRange substringRange, NSRange enclosingRange, BOOL* stop) {
if([substring isEmoji]){
[emojiRange addObject:#{#"startrange":#(substringRange.location),#"endrange":#(enclosingRange.length)}];
}
}];
NSMutableAttributedString *mutString=[[NSMutableAttributedString alloc] initWithString:inputString];
[mutString addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:16.0] range:NSMakeRange(0, mutString.length)];
[emojiRange enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[mutString addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:35.0] range:NSMakeRange([obj[#"startrange"] floatValue], [obj[#"endrange"] floatValue])];
}];
return mutString;
}
Description
First find NSRange of all the emoji in the string by using NSString-RemoveEmoji function isEmoji, and store in array.
Supply the fetched range to apply bigger FONT SIZE to characters in the range.
Finally assign the generated attributed text to the label.
self.label.attributedText=[self getAttributedEmojiString:EmojiDecoded(originalText)];
I use two macros to Encode and Decode Emoji's since I need to save these values to server and read through api, below are the macros.
#define Encoded(val) [[val dataUsingEncoding:NSUTF8StringEncoding] base64EncodedStringWithOptions:0]
#define Decoded(val) [[NSString alloc] initWithData:[[NSData alloc] initWithBase64EncodedString:val options:0] encoding:NSUTF8StringEncoding]
#define EmojiEncoded(val) [[NSString alloc] initWithData:[val dataUsingEncoding:NSNonLossyASCIIStringEncoding] encoding:NSUTF8StringEncoding]
#define EmojiDecoded(val) [[NSString alloc] initWithData:[val dataUsingEncoding:NSUTF8StringEncoding] encoding:NSNonLossyASCIIStringEncoding]
Hope it helps anyone who is looking for similar solution.
Cheers, and thanks to all.

This is somewhat late but might be useful for other folks who stumble upon this answer. The secret is to ask Core Text and it knows which characters in the NSAttributedString are emoji characters.
// Build the attributed string as needed
let ns = NSAttributedString(string: s)
// Now create a typesetter and render the line
let typesetter = CTTypesetterCreateWithAttributedString(nsa)
let line = CTTypesetterCreateLine(typesetter, CFRangeMake(0, nsa.length))
// Once you have a line you can enumerate the runs
guard let runs = CTLineGetGlyphRuns(line) as? [CTRun] else {
throw NSError(domain: "CoreText", code: -1, userInfo: nil)
}
// Each run will have a font with specific attributes
print("There are \(runs.count) run(s) in \(ns.string)")
print()
for run in runs {
let charRange = CTRunGetStringRange(run)
let x: NSAttributedString = CFAttributedStringCreateWithSubstring(nil, nsa, charRange)
print(" Chars: '\(x.string)'")
let attributes: NSDictionary = CTRunGetAttributes(run)
let font = attributes["NSFont"] as! CTFont
let traits = CTFontGetSymbolicTraits(font)
print(" Emoji == \(traits.contains(.traitColorGlyphs))")
print()
}

Related

Dynamic Number of UILabels in custom tableviewcell

I created a custom tableview cell and I want to display some strings in that cell. I fetch strings from backend thus I don't know how many labels I needed. I tried to concat strings in one label and implemented like below however I want to display strings with different attributes after char ":".
for (AttributesModel* attribute in model.attributes) {
NSString *attributeName = attribute.name;
attributeString = [[attributeString stringByAppendingString: attributeName] mutableCopy];
attributeString = [[attributeString stringByAppendingString: #" : "] mutableCopy];
for (NSDictionary *value in attribute.options) {
attributeString = [[attributeString stringByAppendingString: [value objectForKey:#"name"] ] mutableCopy];
attributeString = [[attributeString stringByAppendingString: #", "] mutableCopy];
}
attributeString = [[attributeString stringByAppendingString: #"\n"] mutableCopy];
}
I could not change attributes of strings thats located after char ":".
Are there any way to do that? Can I create dynamic number of labels in cell or only change attributes of strings that are only located after ":" ?
It sounds like you want to change the attributes such as formatting of a section of text within a UILabel. You can do this by:
Creating an attributed, mutable copy of your string (a.k.a converting an NSString into an NSMutableAttributedString).
Changing the attributes to parts of this copy.
Setting your label's attributedText property to your attributed string.
NSString *myString = #"This is my string";
NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:myString];
NSMutableAttributedString *mutableAttributedString = [attributedString mutableCopy];
// The range of text to change, i.e. start from the 5th index
// (starting from 0 like arrays), and continue for 2 characters:
NSRange rangeOfSecondWord = NSMakeRange(5, 2);
// The list of attributes to apply to that range:
NSDictionary *myAttributes = #{
NSForegroundColorAttributeName : [UIColor redColor],
};
// Actually apply the attributes:
[mutableAttributedString setAttributes:myAttributes range:rangeOfSecondWord];
// Set the text of the label to the attributed string:
myLabel.attributedText = mutableAttributedString;
For a list of attributes that you can set in your dictionary, see the Character Attributes reference.
Since you're downloading your strings, you may not know the range beforehand. Since you're concatenating them, here is how you can find the range dynamically:
NSString *stringOne = #"My name is ";
NSString *stringTwo = #"John Citizen";
NSString *joinedStrings = [stringOne stringByAppendingString:stringTwo];
NSRange rangeOfStringTwo = [joinedStrings rangeOfString:stringTwo];

Iterating format % placeholders one by one

I've got an NSAttributedString that looks like
"Somestring bold %# template %f %d blah blah blah"
I want to be able to replace the format template parts just like in [NSString stringWithFormat:...] but keeping the styling, so that the replaced strings match the styling around them (all bold in my example above).
Is there a way to iterate through each format % placeholder one by one so I can use a list of arguments to fill the string?
I don't want to build my own % implementation because I know theres a million and one different formats.
Or is there an easier solution that I'm overlooking?
Edit:
I'll explain a bit of the complete solution I'm solving:
To make it possible for my team to attribute localized strings I've already got a way to write
"key"="test //italic %#// **bold** __underline %d__";
If a specifier is between attributed tags I want that part to be attributed. Currently I can create an attributed string as seen above, the next part is to take care of the specifiers left over.
I'm doing it in the order of Parse attributes -> Apply arguments..., I could easily solve it the other way but I don't want format arguments to mess with the attributes
Big thanks to #Rick for pointing me in the right direction.
His post recommends first going over the arguments and pre-escaping any string or character objects before applying the format strings. This brought me back to another problem I was having earlier, in trying to iterate an argument list of different types (NSString, int etc), like NSLog does. I think I found that it is impossible (or at least really difficult) to do this, and the reason NSLog can is that it knows what types to expect through the format specifiers (%#, %i etc).
I realize I can actually get the same effect not by escaping the arguments, but by escaping the format string itself.
Example:
format: "test //italic %#//"
args: "text // more text"
Steps:
First replace all instances of // with //-TAG-//
Apply the arguments
Determine where styling applies between //-TAG-//
Obviously //-TAG-// can still be written in the arguments to mess up the styling, however depending on what you use as a replacement, the chances of this happening are essentially zero.
I'm doing it in the order of Parse attributes -> Apply arguments..., I
could easily solve it the other way but I don't want format arguments
to mess with the attributes
Why not simply add an escape-character? From what I understand you run the risk of the example you provided getting messed up if the first string contains a double slash?
"key"="test //italic %#// **bold** __underline %d__";
if %# is text // more text that would screw up the formatting.
If so, then simply parse every vararg of the type NSString and char to make sure that they don't contain any of the characters you reserved for your attributes. If they do, add some escape char before which you remove upon parsing the attributes.
The above example would look like this after applying the arguments:
"key"="test //italic text \/\/ more text// **bold** __underline 34__";
After which you parse the attributes, same way as before but you ignore characters preceded by \ and make sure to remove the \.
It's a bit of effort but I bet it's a lot less than implementing your own printf-style parser.
Here is working code:
#import <Foundation/Foundation.h>
#interface NSAttributedString (AttributedFormat)
- (instancetype)initWithFormat:(NSAttributedString *)attrFormat, ...;
- (instancetype)initWithFormat:(NSAttributedString *)attrFormat arguments:(va_list)arguments;
#end
#implementation NSAttributedString (AttributedFormat)
- (instancetype)initWithFormat:(NSAttributedString *)attrFormat, ... {
va_list args;
va_start(args, attrFormat);
self = [self initWithFormat:attrFormat arguments:args];
va_end(args);
return self;
}
- (instancetype)initWithFormat:(NSAttributedString *)attrFormat arguments:(va_list)arguments {
NSRegularExpression *regex;
regex = [[NSRegularExpression alloc] initWithPattern: #"(%.*?[#%dDuUxXoOfeEgGccsSpaAF])"
options: 0
error: nil];
NSString *format = attrFormat.string;
format = [regex stringByReplacingMatchesInString: format
options: 0
range: NSMakeRange(0, format.length)
withTemplate: #"\0$1\0"];
NSString *result = [[NSString alloc] initWithFormat:format arguments:arguments];
NSMutableArray *f_comps = [format componentsSeparatedByString:#"\0"].mutableCopy;
NSMutableArray *r_comps = [result componentsSeparatedByString:#"\0"].mutableCopy;
NSMutableAttributedString *output = [[NSMutableAttributedString alloc] init];
__block int consumed_length = 0;
[attrFormat enumerateAttributesInRange:NSMakeRange(0, attrFormat.length) options:0 usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop) {
NSMutableString *substr = [NSMutableString string];
while(f_comps.count > 0 && NSMaxRange(range) >= consumed_length + [(NSString *)f_comps[0] length]){
NSString *f_str = f_comps[0];
NSString *r_str = r_comps[0];
[substr appendString:r_str];
[f_comps removeObjectAtIndex:0];
[r_comps removeObjectAtIndex:0];
consumed_length += f_str.length;
}
NSUInteger idx = NSMaxRange(range) - consumed_length;
if(f_comps.count > 0 && idx > 0) {
NSString *f_str = f_comps[0];
NSString *leading = [f_str substringToIndex:idx];
[substr appendString:leading];
NSString *trailing = [f_str substringFromIndex:idx];
[f_comps replaceObjectAtIndex:0 withObject:trailing];
[r_comps replaceObjectAtIndex:0 withObject:trailing];
consumed_length += idx;
}
[output appendAttributedString:[[NSAttributedString alloc] initWithString:substr attributes:attrs]];
}];
return [self initWithAttributedString:output];
}
#end
Usage example:
NSMutableAttributedString *fmt = [[NSMutableAttributedString alloc] initWithString:#"test: "];
[fmt appendAttributedString: [[NSAttributedString alloc] initWithString: #"Some%%string"
attributes: #{
NSFontAttributeName: [UIFont systemFontOfSize:17]
}]];
[fmt appendAttributedString: [[NSAttributedString alloc] initWithString: #"bold %# template %.3f %d"
attributes: #{
NSFontAttributeName: [UIFont boldSystemFontOfSize:20],
NSForegroundColorAttributeName: [UIColor redColor]
}]];
[fmt appendAttributedString: [[NSAttributedString alloc] initWithString: #"%# blah blah blah"
attributes: #{
NSFontAttributeName: [UIFont systemFontOfSize:16],
NSForegroundColorAttributeName: [UIColor blueColor]
}]];
NSAttributedString *result = [[NSAttributedString alloc] initWithFormat:fmt, #"[foo]", 1.23, 56, #"[[bar]]"];
Result:
Maybe this still have some bugs, but it should work in most cases.
(%.*?[#%dDuUxXoOfeEgGccsSpaAF])
This regex matches "Format Specifiers". specifier begin with % and end with listed characters. and may have some modifiers between them. It's not perfect, for example this illegal format "%__s" should be ignored but my regex matches this whole string. but as long as the specifier is legal one, it should works.
My code matches it, and insert delimiters around the specifiers:
I'm %s.
I'm <delimiter>%s<delimiter>.
I use \0 as a delimiter.
I'm \0%s\0.
then interpolate it.
I'm \0rintaro\0.
then split the format and the result with the delimiter:
f_comps: ["I'm ", "%s", "."]
r_comps: ["I'm ", "rintaro", "."]
Here, total string length of f_comps is exact the same as original attributed format. Then, iterate the attributes with enumerateAttributesInRange, we can apply the attributes to the results.
I'm sorry but it's too hard to explain the jobs inside enumerateAttributesInRange, with my poor English skills :)

Easiest way to style the text of an UILabel in iOS7/iOS8

I'm learning objective c a little bit to write an iPad app. I've mostly done some html5/php projects and learned some python at university. But one thing that really blows my mind is how hard it is to just style some text in an objective C label.
Maybe I'm coming from a lazy markdown generation, but really, if I want to let an UILabel look like:
Objective: Construct an equilateral triangle from the line segment AB.
In markdown this is as simple as:
**Objective:** Construct an *equilateral* triangle from the line segment AB.
Is there really no pain free objective C way to do this ? All the tutorials I read really wanted me to write like 15 lines of code. For something as simple as this.
So my question is, what is the easiest way to do this, if you have a lot of styling to do in your app ? Will styling text become more natural with swift in iOS8 ?
You can use NSAttributedString's data:options:documentAttributes:error: initializer (first available in iOS 7.0 SDK).
import UIKit
let htmlString = "<b>Objective</b>: Construct an <i>equilateral</i> triangle from the line segment AB."
let htmlData = htmlString.dataUsingEncoding(NSUTF8StringEncoding)
let options = [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType]
var error : NSError? = nil
let attributedString = NSAttributedString(data: htmlData, options: options, documentAttributes: nil, error: &error)
if error == nil {
// we're good
}
Note: You might also want to include NSDefaultAttributesDocumentAttribute option in the options dictionary to provide additional global styling (such as telling not to use Times New Roman).
Take a look into NSAttributedString UIKit Additions Reference for more information.
I faced similar frustrations while trying to use attributed text in Xcode, so I feel your pain. You can definitely use multiple NSMutableAttributedtext's to get the job done, but this is very rigid.
UIFont *normalFont = [UIFont fontWithName:#"..." size:20];
UIFont *boldFont = [UIFont fontWithName:#"..." size:20];
UIFont *italicizedFont = [UIFont fontWithName:#"..." size:20];
NSMutableAttributedString *total = [[NSMutableAttributedString alloc]init];
NSAttributedString *string1 = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:#"Objective"] attributes:#{NSFontAttributeName:boldFont}];
NSAttributedString *string2 = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:#": Construct an "] attributes:#{NSFontAttributeName:normalFont}];
NSAttributedString *string3 = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:#"equilateral "] attributes:#{NSFontAttributeName:italicizedFont}];
NSAttributedString *string4 = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:#"triangle from the line segment AB."] attributes:#{NSFontAttributeName:normalFont}];
[total appendAttributedString:string1];
[total appendAttributedString:string2];
[total appendAttributedString:string3];
[total appendAttributedString:string4];
[self.someLabel setAttributedText: total];
Another option is to use NSRegularExpression. While this will require more lines of code, it is a more fluid way of bolding, changing color, etc from an entire string at once. For your purposes however, using the appendAttributedString will be the shortest way with a label.
UIFont *normalFont = [UIFont fontWithName:#"..." size:20];
UIFont *boldFont = [UIFont fontWithFamilyName:#"..." size: 20];
UIFont *italicizedFont = [UIFont fontWithFamilyName:#"..." size: 20];
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat: #"Objective: Construct an equilateral triangle from the line segment AB."] attributes:#{NSFontAttributeName:normalFont}];
NSError *regexError;
NSRegularExpression *regex1 = [NSRegularExpression regularExpressionWithPattern:#"Objective"
options:NSRegularExpressionCaseInsensitive error:&regexError];
NSRegularExpression *regex2 = [NSRegularExpression regularExpressionWithPattern:#"equilateral"
options:NSRegularExpressionCaseInsensitive error:&regexError];
if (!regexError)
{
NSArray *matches1 = [regex1 matchesInString:[attributedString string]
options:0
range:NSMakeRange(0, [[attributedString string] length])];
NSArray *matches2 = [regex2 matchesInString:[attributedString string]
options:0
range:NSMakeRange(0, [[attributedString string] length])];
for (NSTextCheckingResult *aMatch in matches1)
{
NSRange matchRange = [aMatch range];
[attributedString setAttributes:#{NSFontAttributeName:boldFont}
range:matchRange];
}
for (NSTextCheckingResult *aMatch in matches2)
{
NSRange matchRange = [aMatch range];
[attributedString setAttributes:#{NSFontAttributeName:italicizedFont}
range:matchRange];
}
[self.someLabel setAttributedText: attributedString];
Just to update the akashivskyy’s answer (+1) with contemporary Swift syntax:
guard let data = htmlString.data(using: .utf8) else { return }
do {
let attributedString = try NSAttributedString(
data: data,
options: [.documentType: NSAttributedString.DocumentType.html],
documentAttributes: nil
)
...
} catch {
print(error)
}

Create an NSMutableArray of NSRange's and properly read the range values later

I am trying to create an NSMutableArray of the ranges discovered from NSRegularExpression, but I cannot get the NSMutableArray to hold objects. Help?
Declare the array by: NSMutableArray *matches = [[NSMutableArray alloc]init];
At the end of my regular expression loops:
for (NSTextCheckingResult *aMatch in minedMatches) {
NSRange matchRange = [aMatch range];
[matches addObject: [NSValue valueWithRange:matchRange]];
}
In another part of my code, I have the a for loop wanting to use matches; however, it is not full:
if (matches != nil) {
for (int i = 0; i < matches.count; i++) {
[attributedString addAttribute:NSForegroundColorAttributeName value: minedColor range:[[matches objectAtIndex:i]rangeValue]];
}
}
**Note:
minedColor, minedMatches and attributedString are declared properly throughout my code. I am using addAttribute in a separate location because I need to only change the color of the text in between sections of key words such as "Go" and "end".
**Edit 1 (request for entire method)
- (void)textViewDidChange:(UITextView *)textView {
self.notepadTextView.font = [UIFont fontWithName:#"ProximaNova-Regular" size:20]; //custom font
UIFont *normalFont = [UIFont fontWithName:#"ProximaNova-Regular" size:20];//fail-safe font for attributed string
NSString *textEntryContents = [[self notepadTextView ]text]; //declares user inputted string
[gCore processSpeechText:textEntryContents]; //internal processing
NSMutableArray *mined = [gCore getHighLightContainer]; //array with strings that need to be colored
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:textEntryContents
attributes:#{NSFontAttributeName: normalFont}]; //initialize attributed string
matches = [[NSMutableArray alloc]init]; //initialize matches
UIColor *minedColor = [UIColor colorWithRed:(126.0/255.0) green:(204.0/255.0) blue:(136.0/255.0) alpha:1.0]; //initialize color for attributed string
BOOL colorChangeDidRun = '\0'; //initialize if color was changed
if ([gCore dataMiningInProgress] == YES) { //if it is the start of a section
colorChangeDidRun = NO;
if (mined != nil){ //fail-safe
for (int i = 0; i < mined.count; i++){
NSError *regexErrorMined;
NSRegularExpression *regexMined = [NSRegularExpression regularExpressionWithPattern:[NSString stringWithFormat:#"%#",mined[i]]
options:NSRegularExpressionCaseInsensitive error:&regexErrorMined];
if (!regexErrorMined) {
NSArray *minedMatches = [regexMined matchesInString:[attributedString string]
options:0
range:NSMakeRange(0, [[attributedString string] length])];
for (NSTextCheckingResult *aMatch in minedMatches) {
NSRange matchRange = [aMatch range];
[matches addObject: [NSValue valueWithRange:matchRange]]; //add range values to matches array
}
}
}
}
}
else if ([gCore dataMiningInProgress] == NO) { //if end of section
if (colorChangeDidRun == NO) { //if the color change has not happened yet
if (matches != nil) {
for (int i = 0; i < matches.count; i++) {
colorChangeDidRun = YES; //prevent color change in unnecessary spots
[attributedString addAttribute:NSForegroundColorAttributeName value: minedColor range:[[matches objectAtIndex:i]rangeValue]];
}
}
}
}
self.notepadTextView.attributedText = attributedString; //output attributed string
}
I did not post the entire method originally because it requires a lot of explaining, as I'm sure you can see. Basically, the user will input text into a text view. That text is then data mined if the words fall between "Start" and "end". These key words signal triggers that change the value of [gCore dataMiningInProgress], which is a global object.
Currently, if a user were to type "Start the cat is outside end", the words "cat" and "outside" will change color when the user inputs "end". If the user inputs more string such as: "Start the cat is now inside end", the word "cat" will automatically turn green even before the user types "end". I want to prevent this from happening. I only want the color to change during the individual sections of "start......end"
All outside variables are in working order, the only thing I cannot get thus far is the addAttribute from the array of ranges in matches because although it does not say it is nil, matches.count is 0 in the else if() conditional.
You have a very basic mistake here: it's not possible to execute both branches of if and else if in one pass. So if [gCore dataMiningInProgress] == YES then only matches will be filled with objects, and that's all. If the condition is NO, then matches is an empty array (because it wasn't filled with objects obviously).
P.S. It's no use writing if ([gCore dataMiningInProgress] == YES) ... else if ([gCore dataMiningInProgress] == NO) because if it doesn't evaluate to YES, then it's definitely NO :) So it's just an if-else construction.
Using suggestions from #kambala and #LyricalPanda, my original problem of matches being nil in the else statement was solved through a scoping issue. Although I created a property in the header file for matches and #synthesize'd it, my NSMutableArray was not being written to on a class-level scale. I changed the scope to create a global variable for matches that can now be accessed from any file. Seems like a waste of some coding power, but that was how I was able to get the MutableArray to hold objects outside of one instance. Using the #extern command, allows successful reading and writing of the array full of ranges.

How to find and apply two fonts in a label in IOS

I know to apply two fonts in a label for single word with one font and rest with another font using below code..
int lengthofname#"Anand";
UIFont *boldFont = [UIFont fontWithName:#"BodoniStd" size:15];
UIFont *regularFont = [UIFont fontWithName:#"BodoniStd-BoldItalic" size:15];
//UIColor *foregroundColor = [UIColor clearColor];
// Create the attributes
NSDictionary *attrs = [NSDictionary dictionaryWithObjectsAndKeys:
boldFont, NSFontAttributeName,nil];
//NSDictionary *attrs = [NSDictionary dictionaryWithObjectsAndKeys:
// boldFont, NSFontAttributeName,
// foregroundColor, NSForegroundColorAttributeName, nil];
NSDictionary *subAttrs = [NSDictionary dictionaryWithObjectsAndKeys:
regularFont, NSFontAttributeName, nil];
const NSRange range = NSMakeRange(0,lengthofname);
// range of " 2012/10/14 ". Ideally this should not be hardcoded const
// Create the attributed string (text + attributes)
NSMutableAttributedString *attributedText =
[[NSMutableAttributedString alloc] initWithString:#"Anand is good programmer"
attributes:attrs];
[attributedText setAttributes:subAttrs range:range];
// Set it in our UILabel and we are done!
[self.descLabel setAttributedText:attributedText];
My requirement is to find some x words in a label and then apply Bold font and rest of text with Regular font..
Please suggest any ideas. Thanks in Advance..!
In this example there highlighted words are not funded with regular expression but I believe there is some list (NSArray: STANDARD, EXPRESS, NEXT DAY, etc) of keywords. And what you have to do is enumerate that array to find the rang in text and if founded apply the different font style, something like that:
for (NSString * keyword in listOfKeywordArray) {
NSRange range = [longTextString rangeOfString:#"keyword"];
if (range.location != NSNotFound) {
//Keyword found, apply different font
// This of course needs to be changed to apply font you want
[attrString addAttribute:NSFontAttributeName value:fontName range:range];
}
}
Greg's answer is pretty close. If you have an array of keywords, you can use for.. in to loop through the array of keywords. Then you'd need to use an inner loop with rangeOfString:options:range to find all occurrences of a given keyword.
That method returns an NSRange. You could use setAttributes:range: on the range to set the text attributes of each occurrence of each keyword to use the font and style you want. (Greg's code using addAttribute only lets you set a single attribute on the text. setAttributes:range: and addAttributes:range: both let you specify a whole dictionary of attributes, like in the code you posted. Your code might look like this:
//Whatever font attributes you want to use
NSDictionary *attrs = [NSDictionary dictionaryWithObjectsAndKeys:
boldFont, NSFontAttributeName,nil];
NSArray *keywords = [#"STANDARD", #"EXPRESS", #"NEXT DAY"];
//Loop through each keyword in the array of keywords.
for (NSString aKeyword in keywords)
{
//Set the range to the whole string to start with.
NSRange range = NSMakeRange(0, longTextString.length];
//While there are still more occurrences of this keyword
//Not that the code both assigns the result to range, and evaluates the location.
//This is a bit of C language sleight-of-hand. It works, but is odd-looking code.
while ((range = [longTextString
rangeOfString: aKeyword
options: NSLiteralSearch
range: range]).location != NSNotFound)
{
//Set the attributes on this occurrence of this keyword.
[longTextString setAttributes: attrs range: range];
range.location = range.location+range.length;
range.length = longTextString - range.location;
}
Disclaimer: I typed out the code above in an editor window. It may not compile, much less run. It's intended for illustration purposes only. You will need to clean it up and adapt it to your needs.
Also note that rangeOfString:options:range is not a great choice for this problem, since it will detect word fragments in the middle of longer words. For example, if one of your keywords is "The" then it would detect "The" in the first 3 characters of "Them" and "These". It should really be rewritten to use regular expression string matching that requires a string to be a whole word.
Edit #2. I decided to actually code this up. The rangeOfString:options:range: approach is unsatisfactory because, as mentioned above, it detects word fragments inside larger words. Regular Expressions are a much better solution. Here is code that marks all occurrences of an array of words as bold:
NSMutableAttributedString *viewStyledText = [theTextView.attributedText mutableCopy];
NSString *viewText = viewStyledText.string;
if (viewText.length == 0)
return;
BOOL changed = NO;
NSArray *wordsToBold = #[#"The", #"for", #"to", #"league"];
UIFont *boldFont = [UIFont boldSystemFontOfSize: 15];
NSDictionary *attrs = [NSDictionary dictionaryWithObjectsAndKeys:
boldFont, NSFontAttributeName,nil];
//Loop through each keyword in the array of keywords.
for (NSString *wordToBold in wordsToBold)
{
//Set the range to the whole string to start with.
NSRange range = NSMakeRange(0, viewText.length);
/*
Create a regular expression string for the current word.
The "(?i) prefix tells the regular expression to be case-insenstive. Remove it if
you want your search to be case-sensitive.
The "\b" bits (with double backslashes so the output contains a backslash) cause
The regular expression to only match whole words.
*/
NSString *wordRegExString = [NSString stringWithFormat: #"(?i)\\b%#\\b", wordToBold];
//Now create a regular expression object using the current regular expression string.
NSRegularExpression *wordRegEx = [NSRegularExpression
regularExpressionWithPattern: wordRegExString
options: 0
error: nil];
//While there are still more occurrences of this keyword...
//Not that the code both assigns the result to range, and evaluates the location.
//This is a bit of C language sleight-of-hand. It works, but is odd-looking code.
while ((range = [wordRegEx rangeOfFirstMatchInString: viewText
options: 0
range: range]).location != NSNotFound)
{
//Set the attributes on this occurrence of this keyword.
changed = YES;
[viewStyledText setAttributes: attrs range: range];
range.location = range.location+range.length;
range.length = viewStyledText.length - range.location;
}
}
if (changed)
theTextView.attributedText = viewStyledText;

Resources