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

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)
}

Related

UILabel display emojis

I have a UILabel which is displaying emojis incorrectly.
Here is a screenshot from iOS app:
And here is a screenshot from Android app which is displaying the same text with the emoji correctly.
I have tried answers from here but they did not help.
Example string : "تم البيع والله يبارك للمشتري♥️"
Here is the code:
NSMutableAttributedString *str = [[NSMutableAttributedString alloc] initWithString:comment.body];
UIFont *cellFont = [UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline];
NSDictionary *attributesDictionary;
NSMutableParagraphStyle *paragraphStyle =
[[NSMutableParagraphStyle alloc] init];
paragraphStyle.lineSpacing = 10;
paragraphStyle.alignment = NSTextAlignmentRight;
paragraphStyle.allowsDefaultTighteningForTruncation = true;
attributesDictionary = #{
NSParagraphStyleAttributeName : paragraphStyle,
NSFontAttributeName : cellFont,
NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
NSCharacterEncodingDocumentAttribute: #(NSUTF8StringEncoding)
};
[str addAttributes:attributesDictionary
range:NSMakeRange(0, str.length)];
cell.commentTextLabel.attributedText = str;
Any help would be appreciated.
Use the emoji's unicode.
in your case, the unicode for the red heart is: U+2764 U+FE0F .
Objective C code :
cell.commentTextLabel.text = #"تم البيع والله يبارك للمشتري \U00002764 \U0000FE0F";
Swift code:
cell.commentTextLabel.text = "تم البيع والله يبارك للمشتري \u{2764} \u{FE0F}"
For more emojis, In Xcode go Edit -> Emoji & Symbols, choose an emoji then right click on it and click Copy Character Info Button. Paste it and you will get for the red heart:
❤️
red heart
Unicode: U+2764 U+FE0F, UTF-8: E2 9D A4 EF B8 8F
I was able to solve this issue by using the following code:
NSData *data = [comment.body dataUsingEncoding:NSUTF16StringEncoding allowLossyConversion:false];
NSMutableAttributedString *str = [[NSMutableAttributedString alloc ] initWithData:data options:#{NSDocumentTypeDocumentAttribute:NSHTMLTextDocumentType} documentAttributes:nil error:nil];
UIFont *cellFont = [UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline];
NSDictionary *attributesDictionary;
NSMutableParagraphStyle *paragraphStyle =
[[NSMutableParagraphStyle alloc] init];
paragraphStyle.lineSpacing = 10;
paragraphStyle.alignment = NSTextAlignmentRight;
attributesDictionary = #{
NSParagraphStyleAttributeName : paragraphStyle,
NSFontAttributeName : cellFont
};
[str addAttributes:attributesDictionary range:NSMakeRange(0, str.length)];
cell.commentTextLabel.attributedText = str;
In here comment.body is the string fetched from server which contains the emoji.
The result was like this:
Thanks to Larme for his help in the comments.

Attribute the word within a specific range using NSRegularExpression in Swift

I have some pure Html string and some of them has title tags <b>Title</b>.
facilities: "<b>Facilities</b><br/>24-hour security, Barbecue area, Car park, Clubhouse, Function room, Gym, Outdoor swimming pool, Playground, Swimming pool<br/><br/><b>Rooms</b><br/>Dining room, Ensuites, Living room, Maid\'s room, Utility room<br/><br/><b>Outdoor</b><br/>Balcony<br/><br/><b>View</b><br/>City, Open<br/><br/><b>Direction</b><br/>South East"
So i use NSRegularExpression pattern to extract titles from string and store in an array of strings. And later, i make these titles bold (attributed string) and display. So this is how i do that:
var titlesArray = [String]()
let regex = try! NSRegularExpression(pattern: "<b>(.*?)</b>", options: [])
let basicDescription = facilities as NSString
regex.enumerateMatchesInString(facilities, options: [], range: NSMakeRange(0, facilities.characters.count)) { result, flags, stop in
if let range = result?.rangeAtIndex(1) {
titlesArray.append(basicDescription.substringWithRange(range))
}
}
let convertedDescription = facilities.html2String as NSString
let attributedString = NSMutableAttributedString(string: convertedDescription as String, attributes: [NSFontAttributeName:UIFont.systemFontOfSize(14.0)])
let boldFontAttribute = [NSFontAttributeName: UIFont.boldSystemFontOfSize(15.0)]
if titlesArray.count > 0 {
for i in 0..<titlesArray.count {
attributedString.addAttributes(boldFontAttribute, range: convertedDescription.rangeOfString(titlesArray[i]))
}
}
So, everything is alright. But the problem is, sometimes i receive Html tagged strings which has duplicate words where one of them is title with title tag, another one is just a simple word which i do not need to bold. But this function will look for that word and bold it inside for loop and ignore the real title which comes after the simple word.
This is what i get:
So here, how can i ignore the first "Outdoor" and bold the second one which i want. Thank you for any help.
In Objective-C, shouldn't be too hard to translate in Swift (since it seems you already know some of the methods).
attr1 is rendered with init(data:, options:, documentAttributes:). I didn't add any other effects (like preferred size for bold/normal, color, you just have to enumerate on it and change the effects)
attr2 is rendered more the way you wanted with your regex. It just doesn't take in account all tags, just the bold, and I hardly coded the replacement for new lines (<br/> into \n). But that could be something to use. I didn't do more tests on your regex (the while loop could be stucked?)
NSString *str = #"<b>Facilities</b><br/>24-hour security, Barbecue area, Car park, Clubhouse, Function room, Gym, Outdoor swimming pool, Playground, Swimming pool<br/><br/><b>Rooms</b><br/>Dining room, Ensuites, Living room, Maid\'s room, Utility room<br/><br/><b>Outdoor</b><br/>Balcony<br/><br/><b>View</b><br/>City, Open<br/><br/><b>Direction</b><br/>South East";
NSError *errorAttr1 = nil;
NSAttributedString *attr1 = [[NSAttributedString alloc] initWithData:[str dataUsingEncoding:NSUTF8StringEncoding] options:#{NSDocumentTypeDocumentAttribute:NSHTMLTextDocumentType} documentAttributes:nil error:&errorAttr1];
if (errorAttr1)
{
NSLog(#"Error AttributedStr Conversion with initWithData:options:documentsAttributes:error: %#", errorAttr1);
}
else
{
NSLog(#"attr1: %#", attr1);
[_tv1 setAttributedText:attr1];
}
str = [str stringByReplacingOccurrencesOfString:#"<br/>" withString:#"\n"];
NSError *errorRegex = nil;
NSString *openingTag = #"<b>";
NSString *closingTag = #"</b>";
NSString *pattern = [NSString stringWithFormat:#"%#(.*?)%#", openingTag, closingTag];
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:&errorRegex];
if (errorRegex)
{
NSLog(#"Error regex: %#", errorRegex);
return;
}
NSDictionary *boldAttributes = #{NSForegroundColorAttributeName:[UIColor darkGrayColor],
NSFontAttributeName:[UIFont boldSystemFontOfSize:15]};
NSDictionary *normalAttributes = #{NSForegroundColorAttributeName:[UIColor darkGrayColor],
NSFontAttributeName:[UIFont systemFontOfSize:14]};
NSMutableAttributedString *attr2 = [[NSMutableAttributedString alloc] initWithString:str attributes:normalAttributes]; //Add the initial attributes there
//Now we'll add the specific attribues
NSTextCheckingResult *match = [regex firstMatchInString:[attr2 string] options:0 range:NSMakeRange(0, [attr2 length])];
while (match)
{
NSRange range = [match range];
NSString *foundStr = [[attr2 string] substringWithRange:range];
NSAttributedString *temp = [[NSAttributedString alloc] initWithString:[foundStr substringWithRange:NSMakeRange([openingTag length], [foundStr length]-[openingTag length]-[closingTag length])] attributes:boldAttributes];
[attr2 replaceCharactersInRange:range withAttributedString:temp];
match = [regex firstMatchInString:[attr2 string] options:0 range:NSMakeRange(0, [attr2 length])];
}
NSLog(#"attr2: %#", attr2);
[_tv2 setAttributedText:attr2];
_tv1 and _tv2 are two UITextView (IBOulet).
It rendered: (_tv1 is the one on top, _tv2 is the second one).
I dont know what exactly you are trying to do, but did tou consider using an initializer that takes HTML?
An example code for Playgrounds would be:
if let url = Bundle.main.url(forResource: "file", withExtension: "html") {
do {
let data = try Data(contentsOf: url)
let attributedString = try NSAttributedString(data: data, options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType], documentAttributes: nil)
let labelRect = CGRect(x: 0, y: 0, width: 500, height: 250)
let label = UILabel(frame: labelRect)
label.numberOfLines = 2
label.attributedText = attributedString
} catch {
print(error.localizedDescription)
}
}
With this HTML:
<style>
body {
font-size: 20;
font-family: sans-serif;
}
</style>
<p><b>Hello</b> world</p>
<p>Good morning!</p>
The output looks like:

Display edited NSString in order

I have been working on this for a few days with help from this great community.
I have a NSArray that I need to edit NSStrings within. I have managed to detect a marker in the string and make it bold. However now I am trying to display the strings in the order that they are within the NSArray whilst maintaining the Bold that was added to the specific strings.
I can display the individual Bold String 'string' but I need it to be in order that it is within the array. I know of stringByAppendingString but this would put it at the end.
Any directions would be brilliant.
for (NSString *testWord in legislationArray) {
if ([testWord rangeOfString:#"BOLDME"].location != NSNotFound) {
//Remove Marker
NSString *stripped = [testWord stringByReplacingOccurrencesOfString:#"BOLDME" withString:#""];
//Get string and add bold
NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString:stripped];
NSRange selectedRange = [stripped rangeOfString:(stripped)];
[string beginEditing];
[string addAttribute:NSFontAttributeName
value:[UIFont fontWithName:#"Helvetica-Bold" size:18.0]
range:selectedRange];
[string endEditing];
//Where to go now with string?
}
}
cell.dynamicLabel.text = [legislationArray componentsJoinedByString:#"\n"];
EDIT
Based on the answers below I got it working however the bold method invokes this error:
componentsJoinedByString return a NSString, when you want a NSAttributedString.
Plus, you're setting your text to a receiver that awaits a NSString (cell.dynamicLabel.text), where what you want should be cell.dynamicLabel.attributedText.
Since there is no equivalent to componentsJoinedByString for a NSAttributedString return, you have to do it the oldway, with a for loop, starting with initializing a NSMutableAttributedString, and adding to it each components (that you may "transform") to it.
Here is a example and related question.
Just use additional array. Change your code to
NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] init];
for (NSString *testWord in legislationArray) {
if ([testWord rangeOfString:#"BOLDME"].location != NSNotFound) {
//Remove Marker
NSString *stripped = [testWord stringByReplacingOccurrencesOfString:#"BOLDME" withString:#""];
//Get string and add bold
NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString:stripped];
NSRange selectedRange = [stripped rangeOfString:(stripped)];
[string beginEditing];
[string addAttribute:NSFontAttributeName
value:[UIFont fontWithName:#"Helvetica-Bold" size:18.0]
range:selectedRange];
[string endEditing];
//Where to go now with string?
[attrString appendAttributedString:string];
}
else
{
[attrString appendAttributedString:[[NSAttributedString alloc] initWithString:testWord]];
}
// NEW LINE
[attrString appendAttributedString:[[NSAttributedString alloc] initWithString:#"\n"]];
}
cell.dynamicLabel.attributedText = attrString;
UPDATE:
Your additional issue is not a error - this is a way how XCode shows attributed strings in debug window:

NSDocumentTypeDocumentAttribute doesn't work with NSFontAttributeName

Story
App receives strings with html tags that were edited in CMS. App receives that strings and put to UILabel. Not so long ago html tags were added to this strings. Obviously strings with html tags looks fine in site.
I made an investigation and found that we can use attributed strings for UILabel.
//attributes dictionary
NSDictionary *attrs =
#{
NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
};
NSAttributedString* attrString = [[NSAttributedString alloc] initWithData:[textString dataUsingEncoding:NSUnicodeStringEncoding]
options:attrs
documentAttributes:nil
error:nil];
Ok, now tags are enabled. But I lost my font, text looks like... default html text! That strings take their font's from css, but I receive bare strings with html tags. Just add NSFontAttributeName: [UIFont systemFontOfSize:10.f] to attrs dictionary. But without any success.
I made second try with NSMutableAttributedString:
//attributes dictionary
NSDictionary *attrs =
#{
NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType,
};
NSMutableAttributedString* attrString = [[NSMutableAttributedString alloc] initWithData:[textString dataUsingEncoding:NSUnicodeStringEncoding]
options:attrs
documentAttributes:nil
error:nil];
//and set font later
[attrString setAttributes:#{NSFontAttributeName:font} range:NSMakeRange(0, attrString.length)];
And now I've got label without any html tags, but this last step overrides all changes(bold, italic, etc.) from tags.
Question
How can we use html tags in attributed strings but set our font for them?
I am also looking for solution of this issue.
So far I Solved this by using the following code. Here I know HTML always contains Times new roman font. But If anybody find any dynamic solution please provide happily.
NSRange range = NSMakeRange(0, [parsedAttributedString length]);
[parsedAttributedString enumerateAttribute:NSFontAttributeName inRange:range options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:^(id value, NSRange range, BOOL *stop)
{
UIFont* currentFont = value;
UIFont *replacementFont = nil;
if ([currentFont.fontName rangeOfString:#"BoldMT" options:NSLiteralSearch].location != NSNotFound)
{
replacementFont = [UIFont fontWithName:#"HelveticaNeue-Bold" size:14.0f];
}
else if ([currentFont.fontName rangeOfString:#"BoldItalicMT" options:NSLiteralSearch].location != NSNotFound)
{
replacementFont = [UIFont fontWithName:#"HelveticaNeue-BoldItalic" size:14.0f];
}
else if ([currentFont.fontName rangeOfString:#"ItalicMT" options:NSLiteralSearch].location != NSNotFound)
{
replacementFont = [UIFont fontWithName:#"HelveticaNeue-Italic" size:14.0f];
}
else
{
replacementFont = [UIFont fontWithName:#"HelveticaNeue-Thin" size:14.0f];
}
[parsedAttributedString addAttribute:NSFontAttributeName value:replacementFont range:range];
}];

Change attributes of substrings in a NSAttributedString

This question may be a duplicate of this one. But the answers don't work for me and I want to be more specific.
I have a NSString, but I need a NS(Mutable)AttributedString and some of the words in this string should be given a different color. I tried this:
NSString *text = #"This is the text and i want to replace something";
NSDictionary *attributes = # {NSForegroundColorAttributeName : [UIColor redColor]};
NSMutableAttributedString *subString = [[NSMutableAttributedString alloc] initWithString:#"AND" attributes:attributes];
NSMutableAttributedString *newText = [[NSMutableAttributedString alloc] initWithString:text];
newText = [[newText mutableString] stringByReplacingOccurrencesOfString:#"and" withString:[subString mutableString]];
The "and" should be uppercase an red.
The documentation says that mutableString keeps the attribute mappings. But with my replacing-thing, I have no more attributedString on the right side of the assignment (in the last line of my code-snippet).
How can I get what I want? ;)
#Hyperlord's answer will work, but only if there is one occurence of the word "and" in the input string. Anyway, what I would do is use NSString's stringByReplacingOccurrencesOfString: initially to change every "and" to an "AND", then use a little regex to detect matches in attributed string, and apply NSForegroundColorAttributeName at that range. Here's an example:
NSString *initial = #"This is the text and i want to replace something and stuff and stuff";
NSString *text = [initial stringByReplacingOccurrencesOfString:#"and" withString:#"AND"];
NSMutableAttributedString *mutableAttributedString = [[NSMutableAttributedString alloc] initWithString:text];
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"(AND)" 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];
[mutableAttributedString addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:subStringRange];
}];
And finally, just apply the attributed string to your label.
[myLabel setAttributedText:mutableAttributedString];
I think you should create a NSMutableAttributedString using the existing NSString and then add the style attributes with the appropriate NSRange in order to colorize the parts you want to emphasize for example:
NSString *text = #"This is the text and i want to replace something";
NSMutableAttributedString *mutable = [[NSMutableAttributedString alloc] initWithString:text];
[mutable addAttribute: NSForegroundColorAttributeName value:[UIColor redColor] range:[text rangeOfString:#"and"]];
Be aware: this is just from my head and not tested at all ;-)
Please try this code in Swift 2
var someStr = "This is the text and i want to replace something"
someStr.replaceRange(someStr.rangeOfString("and")!, with: "AND")
let attributeStr = NSMutableAttributedString(string: someStr)
attributeStr.setAttributes([NSForegroundColorAttributeName: UIColor.yellowColor()], range: NSMakeRange(17, 3) )
testLbl.attributedText = attributeStr
Here's another implementation (in Swift) that's helpful if you're doing some more complex manipulations (such as adding/deleting characters) with your attributed string:
let text = "This is the text and i want to replace something"
let mutAttrStr = NSMutableAttributedString(string: text)
let pattern = "\\band\\b"
let regex = NSRegularExpression(pattern: pattern, options: .allZeros, error: nil)
while let result = regex!.firstMatchInString(mutAttrStr.string, options: .allZeros, range:NSMakeRange(0, count(mutAttrStr.string)) {
let substring = NSMutableAttributedString(attributedString: mutAttrStr.attributedSubstringFromRange(result.range))
// manipulate substring attributes here
substring.addAttribute(NSForegroundColorAttributeName, value: UIColor.redColor(), range NSMakeRange(0, count(substring.string))
mutAttrStr.replaceCharactersInRange(result.range, withAttributedString: substring)
}
Your final attributed string should be:
let finalAttrStr = mutAttrStr.copy() as! NSAttributedString

Resources