Bold & Non-Bold Text In A Single UILabel? - ios

How would it be possible to include both bold and non-bold text in a uiLabel?
I'd rather not use a UIWebView.. I've also read this may be possible using NSAttributedString but I have no idea how to use that. Any ideas?
Apple achieves this in several of their apps;
Examples Screenshot:
Thanks!
- Dom

Update
In Swift we don't have to deal with iOS5 old stuff besides syntax is shorter so everything becomes really simple:
Swift 5
func attributedString(from string: String, nonBoldRange: NSRange?) -> NSAttributedString {
let fontSize = UIFont.systemFontSize
let attrs = [
NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: fontSize),
NSAttributedString.Key.foregroundColor: UIColor.black
]
let nonBoldAttribute = [
NSAttributedString.Key.font: UIFont.systemFont(ofSize: fontSize),
]
let attrStr = NSMutableAttributedString(string: string, attributes: attrs)
if let range = nonBoldRange {
attrStr.setAttributes(nonBoldAttribute, range: range)
}
return attrStr
}
Swift 3
func attributedString(from string: String, nonBoldRange: NSRange?) -> NSAttributedString {
let fontSize = UIFont.systemFontSize
let attrs = [
NSFontAttributeName: UIFont.boldSystemFont(ofSize: fontSize),
NSForegroundColorAttributeName: UIColor.black
]
let nonBoldAttribute = [
NSFontAttributeName: UIFont.systemFont(ofSize: fontSize),
]
let attrStr = NSMutableAttributedString(string: string, attributes: attrs)
if let range = nonBoldRange {
attrStr.setAttributes(nonBoldAttribute, range: range)
}
return attrStr
}
Usage:
let targetString = "Updated 2012/10/14 21:59 PM"
let range = NSMakeRange(7, 12)
let label = UILabel(frame: CGRect(x:0, y:0, width:350, height:44))
label.backgroundColor = UIColor.white
label.attributedText = attributedString(from: targetString, nonBoldRange: range)
label.sizeToFit()
Bonus: Internationalisation
Some people commented about internationalisation. I personally think this is out of scope of this question but for instructional purposes this is how I would do it
// Date we want to show
let date = Date()
// Create the string.
// I don't set the locale because the default locale of the formatter is `NSLocale.current` so it's good for internationalisation :p
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .short
let targetString = String(format: NSLocalizedString("Update %#", comment: "Updated string format"),
formatter.string(from: date))
// Find the range of the non-bold part
formatter.timeStyle = .none
let nonBoldRange = targetString.range(of: formatter.string(from: date))
// Convert Range<Int> into NSRange
let nonBoldNSRange: NSRange? = nonBoldRange == nil ?
nil :
NSMakeRange(targetString.distance(from: targetString.startIndex, to: nonBoldRange!.lowerBound),
targetString.distance(from: nonBoldRange!.lowerBound, to: nonBoldRange!.upperBound))
// Now just build the attributed string as before :)
label.attributedText = attributedString(from: targetString,
nonBoldRange: nonBoldNSRange)
Result (Assuming English and Japanese Localizable.strings are available)
Previous answer for iOS6 and later (Objective-C still works):
In iOS6 UILabel, UIButton, UITextView, UITextField, support attributed strings which means we don't need to create CATextLayers as our recipient for attributed strings. Furthermore to make the attributed string we don't need to play with CoreText anymore :) We have new classes in obj-c Foundation.framework like NSParagraphStyle and other constants that will make our life easier. Yay!
So, if we have this string:
NSString *text = #"Updated: 2012/10/14 21:59"
We only need to create the attributed string:
if ([_label respondsToSelector:#selector(setAttributedText:)])
{
// iOS6 and above : Use NSAttributedStrings
// Create the attributes
const CGFloat fontSize = 13;
NSDictionary *attrs = #{
NSFontAttributeName:[UIFont boldSystemFontOfSize:fontSize],
NSForegroundColorAttributeName:[UIColor whiteColor]
};
NSDictionary *subAttrs = #{
NSFontAttributeName:[UIFont systemFontOfSize:fontSize]
};
// Range of " 2012/10/14 " is (8,12). Ideally it shouldn't be hardcoded
// This example is about attributed strings in one label
// not about internationalisation, so we keep it simple :)
// For internationalisation example see above code in swift
const NSRange range = NSMakeRange(8,12);
// Create the attributed string (text + attributes)
NSMutableAttributedString *attributedText =
[[NSMutableAttributedString alloc] initWithString:text
attributes:attrs];
[attributedText setAttributes:subAttrs range:range];
// Set it in our UILabel and we are done!
[_label setAttributedText:attributedText];
} else {
// iOS5 and below
// Here we have some options too. The first one is to do something
// less fancy and show it just as plain text without attributes.
// The second is to use CoreText and get similar results with a bit
// more of code. Interested people please look down the old answer.
// Now I am just being lazy so :p
[_label setText:text];
}
There is a couple of good introductory blog posts here from guys at invasivecode that explain with more examples uses of NSAttributedString, look for "Introduction to NSAttributedString for iOS 6" and "Attributed strings for iOS using Interface Builder" :)
PS: Above code it should work but it was brain-compiled. I hope it is enough :)
Old Answer for iOS5 and below
Use a CATextLayer with an NSAttributedString ! much lighter and simpler than 2 UILabels. (iOS 3.2 and above)
Example.
Don't forget to add QuartzCore framework (needed for CALayers), and CoreText (needed for the attributed string.)
#import <QuartzCore/QuartzCore.h>
#import <CoreText/CoreText.h>
Below example will add a sublayer to the toolbar of the navigation controller. à la Mail.app in the iPhone. :)
- (void)setRefreshDate:(NSDate *)aDate
{
[aDate retain];
[refreshDate release];
refreshDate = aDate;
if (refreshDate) {
/* Create the text for the text layer*/
NSDateFormatter *df = [[NSDateFormatter alloc] init];
[df setDateFormat:#"MM/dd/yyyy hh:mm"];
NSString *dateString = [df stringFromDate:refreshDate];
NSString *prefix = NSLocalizedString(#"Updated", nil);
NSString *text = [NSString stringWithFormat:#"%#: %#",prefix, dateString];
[df release];
/* Create the text layer on demand */
if (!_textLayer) {
_textLayer = [[CATextLayer alloc] init];
//_textLayer.font = [UIFont boldSystemFontOfSize:13].fontName; // not needed since `string` property will be an NSAttributedString
_textLayer.backgroundColor = [UIColor clearColor].CGColor;
_textLayer.wrapped = NO;
CALayer *layer = self.navigationController.toolbar.layer; //self is a view controller contained by a navigation controller
_textLayer.frame = CGRectMake((layer.bounds.size.width-180)/2 + 10, (layer.bounds.size.height-30)/2 + 10, 180, 30);
_textLayer.contentsScale = [[UIScreen mainScreen] scale]; // looks nice in retina displays too :)
_textLayer.alignmentMode = kCAAlignmentCenter;
[layer addSublayer:_textLayer];
}
/* Create the attributes (for the attributed string) */
CGFloat fontSize = 13;
UIFont *boldFont = [UIFont boldSystemFontOfSize:fontSize];
CTFontRef ctBoldFont = CTFontCreateWithName((CFStringRef)boldFont.fontName, boldFont.pointSize, NULL);
UIFont *font = [UIFont systemFontOfSize:13];
CTFontRef ctFont = CTFontCreateWithName((CFStringRef)font.fontName, font.pointSize, NULL);
CGColorRef cgColor = [UIColor whiteColor].CGColor;
NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
(id)ctBoldFont, (id)kCTFontAttributeName,
cgColor, (id)kCTForegroundColorAttributeName, nil];
CFRelease(ctBoldFont);
NSDictionary *subAttributes = [NSDictionary dictionaryWithObjectsAndKeys:(id)ctFont, (id)kCTFontAttributeName, nil];
CFRelease(ctFont);
/* Create the attributed string (text + attributes) */
NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithString:text attributes:attributes];
[attrStr addAttributes:subAttributes range:NSMakeRange(prefix.length, 12)]; //12 is the length of " MM/dd/yyyy/ "
/* Set the attributes string in the text layer :) */
_textLayer.string = attrStr;
[attrStr release];
_textLayer.opacity = 1.0;
} else {
_textLayer.opacity = 0.0;
_textLayer.string = nil;
}
}
In this example I only have two different types of font (bold and normal) but you could also have different font size, different color, italics, underlined, etc.
Take a look at NSAttributedString / NSMutableAttributedString and CoreText attributes string keys.

Try a category on UILabel:
Here's how it's used:
myLabel.text = #"Updated: 2012/10/14 21:59 PM";
[myLabel boldSubstring: #"Updated:"];
[myLabel boldSubstring: #"21:59 PM"];
And here's the category
UILabel+Boldify.h
- (void) boldSubstring: (NSString*) substring;
- (void) boldRange: (NSRange) range;
UILabel+Boldify.m
- (void) boldRange: (NSRange) range {
if (![self respondsToSelector:#selector(setAttributedText:)]) {
return;
}
NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithAttributedString:self.attributedText];
[attributedText setAttributes:#{NSFontAttributeName:[UIFont boldSystemFontOfSize:self.font.pointSize]} range:range];
self.attributedText = attributedText;
}
- (void) boldSubstring: (NSString*) substring {
NSRange range = [self.text rangeOfString:substring];
[self boldRange:range];
}
Note that this will only work in iOS 6 and later. It will simply be ignored in iOS 5 and earlier.

That's easy to do in Interface Builder:
1) make UILabel Attributed in Attributes Inspector
2) select part of phrase you want to make bold
3) change its font (or bold typeface of the same font) in font selector
That's all!

There's category based on bbrame's category. It works similar, but allows you boldify same UILabel multiple times with cumulative results.
UILabel+Boldify.h
#interface UILabel (Boldify)
- (void) boldSubstring: (NSString*) substring;
- (void) boldRange: (NSRange) range;
#end
UILabel+Boldify.m
#implementation UILabel (Boldify)
- (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;
}
- (void)boldSubstring:(NSString*)substring {
NSRange range = [self.text rangeOfString:substring];
[self boldRange:range];
}
#end
With this corrections you may use it multiple times, eg:
myLabel.text = #"Updated: 2012/10/14 21:59 PM";
[myLabel boldSubstring: #"Updated:"];
[myLabel boldSubstring: #"21:59 PM"];
will result with: "Updated: 2012/10/14 21:59 PM".

It worked for me:
CGFloat boldTextFontSize = 17.0f;
myLabel.text = [NSString stringWithFormat:#"%# 2012/10/14 %#",#"Updated:",#"21:59 PM"];
NSRange range1 = [myLabel.text rangeOfString:#"Updated:"];
NSRange range2 = [myLabel.text rangeOfString:#"21:59 PM"];
NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithString:myLabel.text];
[attributedText setAttributes:#{NSFontAttributeName:[UIFont boldSystemFontOfSize:boldTextFontSize]}
range:range1];
[attributedText setAttributes:#{NSFontAttributeName:[UIFont boldSystemFontOfSize:boldTextFontSize]}
range:range2];
myLabel.attributedText = attributedText;
For Swift version: See Here

I've adopted Crazy Yoghurt's answer to swift's extensions.
extension UILabel {
func boldRange(_ range: Range<String.Index>) {
if let text = self.attributedText {
let attr = NSMutableAttributedString(attributedString: text)
let start = text.string.characters.distance(from: text.string.startIndex, to: range.lowerBound)
let length = text.string.characters.distance(from: range.lowerBound, to: range.upperBound)
attr.addAttributes([NSFontAttributeName: UIFont.boldSystemFont(ofSize: self.font.pointSize)], range: NSMakeRange(start, length))
self.attributedText = attr
}
}
func boldSubstring(_ substr: String) {
if let text = self.attributedText {
var range = text.string.range(of: substr)
let attr = NSMutableAttributedString(attributedString: text)
while range != nil {
let start = text.string.characters.distance(from: text.string.startIndex, to: range!.lowerBound)
let length = text.string.characters.distance(from: range!.lowerBound, to: range!.upperBound)
var nsRange = NSMakeRange(start, length)
let font = attr.attribute(NSFontAttributeName, at: start, effectiveRange: &nsRange) as! UIFont
if !font.fontDescriptor.symbolicTraits.contains(.traitBold) {
break
}
range = text.string.range(of: substr, options: NSString.CompareOptions.literal, range: range!.upperBound..<text.string.endIndex, locale: nil)
}
if let r = range {
boldRange(r)
}
}
}
}
May be there is not good conversion between Range and NSRange, but I didn't found something better.

Check out TTTAttributedLabel. It's a drop-in replacement for UILabel that allows you to have mixed font and colors in a single label by setting an NSAttributedString as the text for that label.

In this case you could try,
UILabel *displayLabel = [[UILabel alloc] initWithFrame:/*label frame*/];
displayLabel.font = [UIFont boldSystemFontOfSize:/*bold font size*/];
NSMutableAttributedString *notifyingStr = [[NSMutableAttributedString alloc] initWithString:#"Updated: 2012/10/14 21:59 PM"];
[notifyingStr beginEditing];
[notifyingStr addAttribute:NSFontAttributeName
value:[UIFont systemFontOfSize:/*normal font size*/]
range:NSMakeRange(8,10)/*range of normal string, e.g. 2012/10/14*/];
[notifyingStr endEditing];
displayLabel.attributedText = notifyingStr; // or [displayLabel setAttributedText: notifyingStr];

To make text bold as well as underline in a UILabel. Just add the following lines in your code.
NSRange range1 = [lblTermsAndCondition.text rangeOfString:NSLocalizedString(#"bold_terms", #"")];
NSRange range2 = [lblTermsAndCondition.text rangeOfString:NSLocalizedString(#"bold_policy", #"")];
NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithString:lblTermsAndCondition.text];
[attributedText setAttributes:#{NSFontAttributeName:[UIFont fontWithName:fontBold size:12.0]}
range:range1];
[attributedText setAttributes:#{NSFontAttributeName:[UIFont fontWithName:fontBold size:12.0]}
range:range2];
[attributedText addAttribute:(NSString*)kCTUnderlineStyleAttributeName
value:[NSNumber numberWithInt:kCTUnderlineStyleSingle]
range:range1];
[attributedText addAttribute:(NSString*)kCTUnderlineStyleAttributeName
value:[NSNumber numberWithInt:kCTUnderlineStyleSingle]
range:range2];
lblTermsAndCondition.attributedText = attributedText;

NSString *needToChangeStr=#"BOOK";
NSString *display_string=[NSString stringWithFormat:#"This is %#",book];
NSMutableAttributedString *attri_str=[[NSMutableAttributedString alloc]initWithString:display_string];
int begin=[display_string length]-[needToChangeStr length];
int end=[needToChangeStr length];
[attri_str addAttribute:NSFontAttributeName value:[UIFont fontWithName:#"HelveticaNeue-Bold" size:30] range:NSMakeRange(begin, end)];

Swift 4:
// attribute with color red and Bold
var attrs1 = [NSAttributedStringKey.font: UIFont.boldSystemFont(ofSize: 20), NSAttributedStringKey.foregroundColor: UIColor.red]
// attribute with color black and Non Bold
var attrs2 = [NSAttributedStringKey.font: UIFont(name: "Roboto-Regular", size: 20), NSAttributedStringKey.foregroundColor: UIColor.black]
var color1 = NSAttributedString(string: "RED", attributes: attrs1)
var color2 = NSAttributedString(string: " BLACK", attributes: attrs2)
var string = NSMutableAttributedString()
string.append(color1)
string.append(color2)
// print the text with **RED** BLACK
print("Final String : \(string)")

Supply the string to process as input and supply the words which should be bold/colored as input.
func attributedString(parentString:String, arrayOfStringToProcess:[String], color:UIColor) -> NSAttributedString
{
let parentAttributedString = NSMutableAttributedString(string:parentString, attributes:nil)
let parentStringWords = parentAttributedString.string.components(separatedBy: " ")
if parentStringWords.count != 0
{
let wordSearchArray = arrayOfStringToProcess.filter { inputArrayIndex in
parentStringWords.contains(where: { $0 == inputArrayIndex }
)}
for eachWord in wordSearchArray
{
parentString.enumerateSubstrings(in: parentString.startIndex..<parentString.endIndex, options: .byWords)
{
(substring, substringRange, _, _) in
if substring == eachWord
{
parentAttributedString.addAttribute(.font, value: UIFont.boldSystemFont(ofSize: 15), range: NSRange(substringRange, in: parentString))
parentAttributedString.addAttribute(.foregroundColor, value: color, range: NSRange(substringRange, in: parentString))
}
}
}
}
return parentAttributedString
}

No need for NSRange with the following code I just implemented in my project (in Swift):
//Code sets label (yourLabel)'s text to "Tap and hold(BOLD) button to start recording."
let boldAttribute = [
//You can add as many attributes as you want here.
NSFontAttributeName: UIFont(name: "HelveticaNeue-Bold", size: 18.0)!
]
let regularAttribute = [NSFontAttributeName: UIFont(name: "HelveticaNeue-Light", size: 18.0)!]
let beginningAttributedString = NSAttributedString(string: "Tap and ", attributes: regularAttribute )
let boldAttributedString = NSAttributedString(string: "hold ", attributes: boldAttribute)
let endAttributedString = NSAttributedString(string: "button to start recording.", attributes: regularAttribute )
let fullString = NSMutableAttributedString()
fullString.appendAttributedString(beginningAttributedString)
fullString.appendAttributedString(boldAttributedString)
fullString.appendAttributedString(endAttributedString)
yourLabel.attributedText = fullString

If you want to make using attributed strings easier, try using Attributed String Creator, which will generate the code for you. https://itunes.apple.com/us/app/attributed-string-creator/id730928349

AttributeString has constructors that take mark down strings, doing it this way can mean your attribute string has no other attributes, so if you are using xibs, you can have the text without the bold, set to all the other attributes you want, then in code enumerate through the attribute ranges of your mark down string and apply them to the attributed string you get from you xib file, and then reapply it to your attributed attributeString of the particular field.

Related

replace entire text string in NSAttributedString without modifying other attributes

I have a reference to NSAttributedString and i want to change the text of the attributed string.
I guess i have to created a new NSAttributedString and update the reference with this new string. However when i do this i lose the attributed of previous string.
NSAttributedString *newString = [[NSAttributedString alloc] initWithString:text];
[self setAttributedText:newString];
I have reference to old attributed string in self.attributedText. How can i retain the previous attributed in the new string?
You can use NSMutableAttributedString and just update the string, the attributes won't change.
Example:
NSMutableAttributedString *mutableAttributedString = [[NSMutableAttributedString alloc] initWithString:#"my string" attributes:#{NSForegroundColorAttributeName: [UIColor blueColor], NSFontAttributeName: [UIFont systemFontOfSize:20]}];
//update the string
[mutableAttributedString.mutableString setString:#"my new string"];
Swift
Change the text while keeping the attributes:
let myString = "my string"
let myAttributes = [NSAttributedString.Key.foregroundColor: UIColor.blue, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 40)]
let mutableAttributedString = NSMutableAttributedString(string: myString, attributes: myAttributes)
let myNewString = "my new string"
mutableAttributedString.mutableString.setString(myNewString)
The results for mutableAttributedString are
Notes
Any sub-ranges of attributes beyond index 0 are discarded. For example, if I add another attribute to the last word of the original string, it is lost after I change the string:
// additional attribute added before changing the text
let myRange = NSRange(location: 3, length: 6)
let anotherAttribute = [ NSAttributedString.Key.backgroundColor: UIColor.yellow ]
mutableAttributedString.addAttributes(anotherAttribute, range: myRange)
Results:
From this we can see that the new string gets whatever the attributes are at index 0 of the original string. Indeed, if we adjust the range to be
let myRange = NSRange(location: 0, length: 1)
we get
See also
my main answer about Swift attributed strings
I made a little extension to make this really easy:
import UIKit
extension UILabel {
func setTextWhileKeepingAttributes(string: String) {
if let newAttributedText = self.attributedText {
let mutableAttributedText = newAttributedText.mutableCopy()
mutableAttributedText.mutableString.setString(string)
self.attributedText = mutableAttributedText as? NSAttributedString
}
}
}
https://gist.github.com/wvdk/e8992e82b04e626a862dbb991e4cbe9c
This is the way using Objective-C (tested on iOS 9)
NSAttributedString *primaryString = ...;
NSString *newString = ...;
//copy the attributes
NSDictionary *attributes = [primaryString attributesAtIndex:0 effectiveRange:NSMakeRange(primaryString.length-1, primaryString.length)];
NSMutableAttributedString *newString = [[NSMutableAttributedString alloc] initWithString:newString attributes:attributes];
NSMutableAttributedString *primaryStringMutable = [[NSMutableAttributedString alloc] initWithAttributedString:primaryString];
//change the string
[primaryStringMutable setAttributedString::newString];
primaryString = [NSAttributedString alloc] initWithAttributedString:primaryStringMutable];
Check for the most important references: attributesAtIndex:effectiveRange: and setAttributedString:.
Darius answer is almost there. It contains a minor error. The correct is:
This is the way using Objective-C (tested on iOS 10)
NSAttributedString *primaryString = ...;
NSString *newString = ...;
//copy the attributes
NSRange range = NSMakeRange(primaryString.length-1, primaryString.length);
NSDictionary *attributes = [primaryString attributesAtIndex:0 effectiveRange:&range];
NSMutableAttributedString *newString = [[NSMutableAttributedString alloc] initWithString:newString attributes:attributes];
NSMutableAttributedString *primaryStringMutable = [[NSMutableAttributedString alloc] initWithAttributedString:primaryString];
//change the string
[primaryStringMutable setAttributedString::newString];
primaryString = [NSAttributedString alloc] initWithAttributedString:primaryStringMutable];
let mutableAttributedString = mySubTitleLabel.attributedText?.mutableCopy() as? NSMutableAttributedString
if let attrStr = mutableAttributedString{
attrStr.mutableString.setString("Inner space can be an example shown on the, third page of the tutorial.")
mySubTitleLabel.attributedText = attrStr;
}
I hope this code may help you, i have copied the attribute of the label to a mutableAttributedString and then set the string for it
For those of you working with UIButtons, here is an improved answer based on Wes's.
It seemed that updating a label of a button had better be done this way:
let newtext = "my new text"
myuibutton.setAttributedTitle(titlelabel.getTextWhileKeepingAttributes(string: newtext), for: .normal)
So I ended up with this extension:
import UIKit
extension UILabel {
func setTextWhileKeepingAttributes(string: String) {
if let newAttributedText = self.attributedText {
let mutableAttributedText = newAttributedText.mutableCopy()
(mutableAttributedText as AnyObject).mutableString.setString(string)
self.attributedText = mutableAttributedText as? NSAttributedString
}
}
func getTextWhileKeepingAttributes(string: String) -> NSAttributedString {
if let newAttributedText:NSAttributedString = self.attributedText {
let mutableAttributedText = newAttributedText.mutableCopy()
(mutableAttributedText as AnyObject).mutableString.setString(string)
return mutableAttributedText as! NSAttributedString
}
else {
// No attributes in this label, just create a new attributed string?
let attributedstring = NSAttributedString.init(string: string)
return attributedstring
}
}
}
Changing the text of a mutable string will not do the jobs, since it will only keep the attributes of the first character and apply this to all of the text. Which seems to be by design, since it is part of the documentation.
So if you want to copy all attributes or change the string, you need to copy all attributes manually. Then you can create a MutableAttributedString and change the text. Afterwards you apply all the attributes to the new MutableAttributedString.
I have done it this way for Xamarin (in C#), but I think you can easily understand it and adapt it for your language:
NSMutableAttributedString result = new
NSMutableAttributedString(attrStr.Value.Replace(blackSquare, bullet));
// You cannot simply replace an AttributedString's string, because that will discard attributes.
// Therefore, I will now copy all attributes manually to the new MutableAttributedString:
NSRange outRange = new NSRange(0, 0);
int attributeIndex = 0;
while (outRange.Location + outRange.Length < attrStr.Value.Length // last attribute range reached
&& attributeIndex < attrStr.Value.Length) // or last character reached
{
// Get all attributes for character at attributeIndex
var attributes = attrStr.GetAttributes(attributeIndex, out outRange);
if (attributes != null && attributes.Count > 0)
{
result.AddAttributes(attributes, outRange); // copy all found attributes to result
attributeIndex = (int)(outRange.Location + outRange.Length); // continue with the next range
}
else
{
attributeIndex++; // no attribues at the current attributeIndex, so continue with the next char
}
}
// all attributes are copied
None of the answers worked for me, but this one;
extension UILabel{
func setTextWhileKeepingAttributes(_ string: String) {
if let attributedText = self.attributedText {
let attributedString = NSMutableAttributedString(string: string,
attributes: [NSAttributedString.Key.font: font])
attributedText.enumerateAttribute(.font, in: NSRange(location: 0, length: attributedText.length)) { (value, range, stop) in
let attributes = attributedText.attributes(at: range.location, effectiveRange: nil)
attributedString.addAttributes(attributes, range: range)
}
self.attributedText = attributedString
}
}
}

Is there a unicode way to make part of a string bold?

in Localizable.strings
"rulesText" = "No illegal posting! \n No weird stuff! \n There is no tolerance for objectionable content, they will be removed!";
Can I make part of this bold? Like No weird stuff! or something in this sense using unicode characters? Or some other way?
I use it like this:
textView.text = "\n\n " + NSLocalizedString("rulesText", comment: "")
Easier way would be using NSMutableAttributedString, while using in textView.text. Here is an example:
NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc]
initWithString: NSLocalizedString("rulesText", comment: "")];
[attrString beginEditing];
[attrString addAttribute:kCTFontAttributeName
value:[[UIFont boldSystemFontOfSize:12] fontName]
range:NSMakeRange(2, 4)]; // use range as per your character index range
[attrString endEditing];
Using this it will automatically bold characters which starts from 2nd index and then 4 characters.
for example:
1234567890 - 1234567890
Hope this helps.
For SWIFT language:
let font:UIFont? = UIFont.boldSystemFontOfSize(12.0)
myMutableString.addAttribute(NSFontAttributeName, value: font!, range: NSRange(location: 2, length: 4));
http://www.raywenderlich.com/77092/text-kit-tutorial-swift
Thanks to Martins previous answer I edited his solution to match my case and it works perfectly.
Check out and upvote his solution: create an attributed string out of plain (Android formated) text in swift for iOS
So this basically changes:
<a>hey</a> to size 14 bold
<b>hey</b> to size 12 bold
<u>hey</u> to underlined
its easy to add more features to it.
//localizable.strings
"rulesText" = "\n\n<a>The following will be removed</a> \n\n<b><u>Harassment</u></b>\n\nOther Stuff"
//viewdidload
textView.font = UIFont(name: "HelveticaNeue-Light", size: 12) //This is here to set up rest of the texts font
textView.attributedText = convertText(NSLocalizedString("rulesText", comment: ""))
//method for string conversation
func convertText(inputText: String) -> NSAttributedString {
var attrString = NSMutableAttributedString(string: inputText)
let boldFont = UIFont(name: "Helvetica-Bold", size: 12)
let boldBigFont = UIFont(name: "Helvetica-Bold", size: 14)
attrString = fixText(attrString, attributeName: NSFontAttributeName, attributeValue: boldFont!, propsIndicator: "<b>", propsEndIndicator: "</b>")
attrString = fixText(attrString, attributeName: NSFontAttributeName, attributeValue: boldBigFont!, propsIndicator: "<a>", propsEndIndicator: "</a>")
attrString = fixText(attrString, attributeName: NSUnderlineStyleAttributeName, attributeValue: NSUnderlineStyle.StyleDouble.rawValue, propsIndicator: "<u>", propsEndIndicator: "</u>")
return attrString
}
func fixText(inputText:NSMutableAttributedString, attributeName:AnyObject, attributeValue:AnyObject, propsIndicator:String, propsEndIndicator:String)->NSMutableAttributedString{
var r1 = (inputText.string as NSString).rangeOfString(propsIndicator)
while r1.location != NSNotFound {
let r2 = (inputText.string as NSString).rangeOfString(propsEndIndicator)
if r2.location != NSNotFound && r2.location > r1.location {
let r3 = NSMakeRange(r1.location + r1.length, r2.location - r1.location - r1.length)
inputText.addAttribute(attributeName as String, value: attributeValue, range: r3)
inputText.replaceCharactersInRange(r2, withString: "")
inputText.replaceCharactersInRange(r1, withString: "")
} else {
break
}
r1 = (inputText.string as NSString).rangeOfString(propsIndicator)
}
return inputText
}
//Objective C format of the methods Esq in above answer to make attributed strings for NSLocalizedString
-(NSAttributedString* )convertText:(NSString*)inputText {
NSMutableAttributedString * attString = [[NSMutableAttributedString alloc] initWithString:inputText];
UIFont *makeBold = [UIFont boldSystemFontOfSize:16];
attString = [self fixText:attString andFont:makeBold andAttributeName:NSFontAttributeName andProsPoseIndicator:#"<b>" adnpropsEndIndicator:#"</b>"];
return attString;
}
-(NSMutableAttributedString *)fixText:(NSMutableAttributedString *) inputText andFont:(UIFont*)attributeValue andAttributeName: (NSString *)attributeName andProsPoseIndicator: (NSString *)propsIndicator adnpropsEndIndicator: (NSString *)propsEndIndicator{
NSRange r = [inputText.string rangeOfString:propsIndicator];
while (r.location != NSNotFound) {
NSRange r2 = [inputText.string rangeOfString:propsEndIndicator];
if (r.location != NSNotFound && r2.location >r.location) {
NSRange r3 = NSMakeRange(r.location+r2.length-1, r2.location - r.location - r.length);
[inputText addAttribute:attributeName value:attributeValue range:r3];
[inputText replaceCharactersInRange:r2 withString:#""];
[inputText replaceCharactersInRange:r withString:#""];
}else {
break;
}
r = [inputText.string rangeOfString:(propsIndicator)];
}
return inputText;
}

Underline part of a string using NSMutableAttributedString in iOS 8 is not working

I try to underline part of a string, for example, a 'string' part in 'test string' string. I'm using NSMutableAttributedString and my solution was working well on iOS7.
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc]
initWithString:#"test string"];
[attributedString addAttribute:NSUnderlineStyleAttributeName
value:#(NSUnderlineStyleSingle)
range:NSMakeRange(5, 6)];
myLabel.attributedText = attributedString;
The problem is that my solution is not working in iOS8 anymore. After spending an hour on testing multiple variants of NSMutableAttributedString, I found out that this solution works only when range starts with 0 (length can differ). What is the reason for that? How can I workaround this?
Update:
By investigating this question: Displaying NSMutableAttributedString on iOS 8 I finally found the solution!
You should add NSUnderlineStyleNone at the beginning of the string.
Swift 4.2 (none was removed):
let attributedString = NSMutableAttributedString()
attributedString.append(NSAttributedString(string: "test ",
attributes: [.underlineStyle: 0]))
attributedString.append(NSAttributedString(string: "s",
attributes: [.underlineStyle: NSUnderlineStyle.single.rawValue]))
attributedString.append(NSAttributedString(string: "tring",
attributes: [.underlineStyle: 0]))
Objective-C:
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] init];
[attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:#"test "
attributes:#{NSUnderlineStyleAttributeName: #(NSUnderlineStyleNone)}]];
[attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:#"s"
attributes:#{NSUnderlineStyleAttributeName: #(NSUnderlineStyleSingle),
NSBackgroundColorAttributeName: [UIColor clearColor]}]];
[attributedString appendAttributedString:[[NSAttributedString alloc] initWithString:#"tring"]];
Another bonus of such approach is absence of any ranges. Very nice for localized strings.
Seems like it is Apple bug :(
I found that if you apply UnderlineStyleNone to the whole string you can then selectively apply underline to a part that starts in the middle:
func underlinedString(string: NSString, term: NSString) -> NSAttributedString {
let output = NSMutableAttributedString(string: string)
let underlineRange = string.rangeOfString(term)
output.addAttribute(NSUnderlineStyleAttributeName, value: NSUnderlineStyle.StyleNone.rawValue, range: NSMakeRange(0, string.length))
output.addAttribute(NSUnderlineStyleAttributeName, value: NSUnderlineStyle.StyleSingle.rawValue, range: underlineRange)
return output
}
NSMutableAttributedString *signUpString = [[NSMutableAttributedString alloc] initWithString:#"Not a member yet?Sign Up now"];
[signUpString appendAttributedString:[[NSAttributedString alloc] initWithString:#" "attributes:#{NSUnderlineStyleAttributeName: #(NSUnderlineStyleNone)}]];
[signUpString addAttributes: #{NSForegroundColorAttributeName:UIColorFromRGB(0x43484B),NSUnderlineStyleAttributeName:[NSNumber numberWithInteger:NSUnderlineStyleSingle]} range:NSMakeRange(17,11)];
signUpLbl.attributedText = [signUpString copy];
It worked for me
It's September 2018, so this answer is not about iOS8 but it still relates to underlining part of a string.
Here is a Swift 4 extension that underlines a given term within an already composed myAttributedString
extension NSMutableAttributedString {
func underline(term: String) {
guard let underlineRange = string.range(of: term) else {
return
}
let startPosition = string.distance(from: term.startIndex, to: underlineRange.lowerBound)
let nsrange = NSRange(location: startPosition, length: term.count)
addAttribute(
.underlineStyle,
value: NSUnderlineStyle.styleSingle.rawValue,
range: nsrange)
}
}
Usage: myAttributedString.underline(term: "some term")
Swift 5 version of #Joss's answer with few modifications, by adding a returned NSMutableAttributedString, because I couldn't use the original solution without it.
extension NSMutableAttributedString {
func underline(term: String) -> NSMutableAttributedString {
guard let underlineRange = string.range(of: term) else {
return NSMutableAttributedString()
}
let startPosition = string.distance(from: term.startIndex, to: underlineRange.lowerBound)
let nsrange = NSRange(location: startPosition, length: term.count)
addAttribute(
.underlineStyle,
value: NSUnderlineStyle.single.rawValue,
range: nsrange)
return self
}
}
Usage:
let myUnderLinedText = "Hello World"
let underLinedMutableString = NSMutableAttributedString(string: myUnderLinedText, attributes: titleAttributes).underline(term: myUnderLinedText)
let values = NSMutableAttributedString(string: "**YourString**")
let range = NSRange(location: 0, length: values.length)
values.addAttribute(.underlineStyle, value: 1, range: range)
This will make the string with underlined style, also you can replace the .underlinestyle and use .link to show it as a hyperlink in blue color
I used the following extension (using exidy's function) in playground/simulator and it worked fine , you may change/add attributes depending on your needs
extension NSMutableAttributedString
{
func changeWordsColour(terms:[NSString])
{
let string = self.string as NSString
self.addAttribute(NSForegroundColorAttributeName, value: UIColor.brownColor(), range: NSMakeRange(0, self.length))
for term in terms
{
let underlineRange = string.rangeOfString(term as String)
self.addAttribute(NSForegroundColorAttributeName, value: UIColor.redColor(), range: underlineRange)
}
}
}
let myStr = NSMutableAttributedString(string: "Change Words Colour")
myStr.changeWordsColour(["change","Colour"])
Add color for underline attribute:​​​
[attributedString addAttribute:NSUnderlineColorAttributeName
value:[UIColor redColor]
range:NSMakeRange(5, 6)];

How to get a string to contain one font for one part, and another font for part 2? [duplicate]

How would it be possible to include both bold and non-bold text in a uiLabel?
I'd rather not use a UIWebView.. I've also read this may be possible using NSAttributedString but I have no idea how to use that. Any ideas?
Apple achieves this in several of their apps;
Examples Screenshot:
Thanks!
- Dom
Update
In Swift we don't have to deal with iOS5 old stuff besides syntax is shorter so everything becomes really simple:
Swift 5
func attributedString(from string: String, nonBoldRange: NSRange?) -> NSAttributedString {
let fontSize = UIFont.systemFontSize
let attrs = [
NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: fontSize),
NSAttributedString.Key.foregroundColor: UIColor.black
]
let nonBoldAttribute = [
NSAttributedString.Key.font: UIFont.systemFont(ofSize: fontSize),
]
let attrStr = NSMutableAttributedString(string: string, attributes: attrs)
if let range = nonBoldRange {
attrStr.setAttributes(nonBoldAttribute, range: range)
}
return attrStr
}
Swift 3
func attributedString(from string: String, nonBoldRange: NSRange?) -> NSAttributedString {
let fontSize = UIFont.systemFontSize
let attrs = [
NSFontAttributeName: UIFont.boldSystemFont(ofSize: fontSize),
NSForegroundColorAttributeName: UIColor.black
]
let nonBoldAttribute = [
NSFontAttributeName: UIFont.systemFont(ofSize: fontSize),
]
let attrStr = NSMutableAttributedString(string: string, attributes: attrs)
if let range = nonBoldRange {
attrStr.setAttributes(nonBoldAttribute, range: range)
}
return attrStr
}
Usage:
let targetString = "Updated 2012/10/14 21:59 PM"
let range = NSMakeRange(7, 12)
let label = UILabel(frame: CGRect(x:0, y:0, width:350, height:44))
label.backgroundColor = UIColor.white
label.attributedText = attributedString(from: targetString, nonBoldRange: range)
label.sizeToFit()
Bonus: Internationalisation
Some people commented about internationalisation. I personally think this is out of scope of this question but for instructional purposes this is how I would do it
// Date we want to show
let date = Date()
// Create the string.
// I don't set the locale because the default locale of the formatter is `NSLocale.current` so it's good for internationalisation :p
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .short
let targetString = String(format: NSLocalizedString("Update %#", comment: "Updated string format"),
formatter.string(from: date))
// Find the range of the non-bold part
formatter.timeStyle = .none
let nonBoldRange = targetString.range(of: formatter.string(from: date))
// Convert Range<Int> into NSRange
let nonBoldNSRange: NSRange? = nonBoldRange == nil ?
nil :
NSMakeRange(targetString.distance(from: targetString.startIndex, to: nonBoldRange!.lowerBound),
targetString.distance(from: nonBoldRange!.lowerBound, to: nonBoldRange!.upperBound))
// Now just build the attributed string as before :)
label.attributedText = attributedString(from: targetString,
nonBoldRange: nonBoldNSRange)
Result (Assuming English and Japanese Localizable.strings are available)
Previous answer for iOS6 and later (Objective-C still works):
In iOS6 UILabel, UIButton, UITextView, UITextField, support attributed strings which means we don't need to create CATextLayers as our recipient for attributed strings. Furthermore to make the attributed string we don't need to play with CoreText anymore :) We have new classes in obj-c Foundation.framework like NSParagraphStyle and other constants that will make our life easier. Yay!
So, if we have this string:
NSString *text = #"Updated: 2012/10/14 21:59"
We only need to create the attributed string:
if ([_label respondsToSelector:#selector(setAttributedText:)])
{
// iOS6 and above : Use NSAttributedStrings
// Create the attributes
const CGFloat fontSize = 13;
NSDictionary *attrs = #{
NSFontAttributeName:[UIFont boldSystemFontOfSize:fontSize],
NSForegroundColorAttributeName:[UIColor whiteColor]
};
NSDictionary *subAttrs = #{
NSFontAttributeName:[UIFont systemFontOfSize:fontSize]
};
// Range of " 2012/10/14 " is (8,12). Ideally it shouldn't be hardcoded
// This example is about attributed strings in one label
// not about internationalisation, so we keep it simple :)
// For internationalisation example see above code in swift
const NSRange range = NSMakeRange(8,12);
// Create the attributed string (text + attributes)
NSMutableAttributedString *attributedText =
[[NSMutableAttributedString alloc] initWithString:text
attributes:attrs];
[attributedText setAttributes:subAttrs range:range];
// Set it in our UILabel and we are done!
[_label setAttributedText:attributedText];
} else {
// iOS5 and below
// Here we have some options too. The first one is to do something
// less fancy and show it just as plain text without attributes.
// The second is to use CoreText and get similar results with a bit
// more of code. Interested people please look down the old answer.
// Now I am just being lazy so :p
[_label setText:text];
}
There is a couple of good introductory blog posts here from guys at invasivecode that explain with more examples uses of NSAttributedString, look for "Introduction to NSAttributedString for iOS 6" and "Attributed strings for iOS using Interface Builder" :)
PS: Above code it should work but it was brain-compiled. I hope it is enough :)
Old Answer for iOS5 and below
Use a CATextLayer with an NSAttributedString ! much lighter and simpler than 2 UILabels. (iOS 3.2 and above)
Example.
Don't forget to add QuartzCore framework (needed for CALayers), and CoreText (needed for the attributed string.)
#import <QuartzCore/QuartzCore.h>
#import <CoreText/CoreText.h>
Below example will add a sublayer to the toolbar of the navigation controller. à la Mail.app in the iPhone. :)
- (void)setRefreshDate:(NSDate *)aDate
{
[aDate retain];
[refreshDate release];
refreshDate = aDate;
if (refreshDate) {
/* Create the text for the text layer*/
NSDateFormatter *df = [[NSDateFormatter alloc] init];
[df setDateFormat:#"MM/dd/yyyy hh:mm"];
NSString *dateString = [df stringFromDate:refreshDate];
NSString *prefix = NSLocalizedString(#"Updated", nil);
NSString *text = [NSString stringWithFormat:#"%#: %#",prefix, dateString];
[df release];
/* Create the text layer on demand */
if (!_textLayer) {
_textLayer = [[CATextLayer alloc] init];
//_textLayer.font = [UIFont boldSystemFontOfSize:13].fontName; // not needed since `string` property will be an NSAttributedString
_textLayer.backgroundColor = [UIColor clearColor].CGColor;
_textLayer.wrapped = NO;
CALayer *layer = self.navigationController.toolbar.layer; //self is a view controller contained by a navigation controller
_textLayer.frame = CGRectMake((layer.bounds.size.width-180)/2 + 10, (layer.bounds.size.height-30)/2 + 10, 180, 30);
_textLayer.contentsScale = [[UIScreen mainScreen] scale]; // looks nice in retina displays too :)
_textLayer.alignmentMode = kCAAlignmentCenter;
[layer addSublayer:_textLayer];
}
/* Create the attributes (for the attributed string) */
CGFloat fontSize = 13;
UIFont *boldFont = [UIFont boldSystemFontOfSize:fontSize];
CTFontRef ctBoldFont = CTFontCreateWithName((CFStringRef)boldFont.fontName, boldFont.pointSize, NULL);
UIFont *font = [UIFont systemFontOfSize:13];
CTFontRef ctFont = CTFontCreateWithName((CFStringRef)font.fontName, font.pointSize, NULL);
CGColorRef cgColor = [UIColor whiteColor].CGColor;
NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
(id)ctBoldFont, (id)kCTFontAttributeName,
cgColor, (id)kCTForegroundColorAttributeName, nil];
CFRelease(ctBoldFont);
NSDictionary *subAttributes = [NSDictionary dictionaryWithObjectsAndKeys:(id)ctFont, (id)kCTFontAttributeName, nil];
CFRelease(ctFont);
/* Create the attributed string (text + attributes) */
NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithString:text attributes:attributes];
[attrStr addAttributes:subAttributes range:NSMakeRange(prefix.length, 12)]; //12 is the length of " MM/dd/yyyy/ "
/* Set the attributes string in the text layer :) */
_textLayer.string = attrStr;
[attrStr release];
_textLayer.opacity = 1.0;
} else {
_textLayer.opacity = 0.0;
_textLayer.string = nil;
}
}
In this example I only have two different types of font (bold and normal) but you could also have different font size, different color, italics, underlined, etc.
Take a look at NSAttributedString / NSMutableAttributedString and CoreText attributes string keys.
Try a category on UILabel:
Here's how it's used:
myLabel.text = #"Updated: 2012/10/14 21:59 PM";
[myLabel boldSubstring: #"Updated:"];
[myLabel boldSubstring: #"21:59 PM"];
And here's the category
UILabel+Boldify.h
- (void) boldSubstring: (NSString*) substring;
- (void) boldRange: (NSRange) range;
UILabel+Boldify.m
- (void) boldRange: (NSRange) range {
if (![self respondsToSelector:#selector(setAttributedText:)]) {
return;
}
NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithAttributedString:self.attributedText];
[attributedText setAttributes:#{NSFontAttributeName:[UIFont boldSystemFontOfSize:self.font.pointSize]} range:range];
self.attributedText = attributedText;
}
- (void) boldSubstring: (NSString*) substring {
NSRange range = [self.text rangeOfString:substring];
[self boldRange:range];
}
Note that this will only work in iOS 6 and later. It will simply be ignored in iOS 5 and earlier.
That's easy to do in Interface Builder:
1) make UILabel Attributed in Attributes Inspector
2) select part of phrase you want to make bold
3) change its font (or bold typeface of the same font) in font selector
That's all!
There's category based on bbrame's category. It works similar, but allows you boldify same UILabel multiple times with cumulative results.
UILabel+Boldify.h
#interface UILabel (Boldify)
- (void) boldSubstring: (NSString*) substring;
- (void) boldRange: (NSRange) range;
#end
UILabel+Boldify.m
#implementation UILabel (Boldify)
- (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;
}
- (void)boldSubstring:(NSString*)substring {
NSRange range = [self.text rangeOfString:substring];
[self boldRange:range];
}
#end
With this corrections you may use it multiple times, eg:
myLabel.text = #"Updated: 2012/10/14 21:59 PM";
[myLabel boldSubstring: #"Updated:"];
[myLabel boldSubstring: #"21:59 PM"];
will result with: "Updated: 2012/10/14 21:59 PM".
It worked for me:
CGFloat boldTextFontSize = 17.0f;
myLabel.text = [NSString stringWithFormat:#"%# 2012/10/14 %#",#"Updated:",#"21:59 PM"];
NSRange range1 = [myLabel.text rangeOfString:#"Updated:"];
NSRange range2 = [myLabel.text rangeOfString:#"21:59 PM"];
NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithString:myLabel.text];
[attributedText setAttributes:#{NSFontAttributeName:[UIFont boldSystemFontOfSize:boldTextFontSize]}
range:range1];
[attributedText setAttributes:#{NSFontAttributeName:[UIFont boldSystemFontOfSize:boldTextFontSize]}
range:range2];
myLabel.attributedText = attributedText;
For Swift version: See Here
I've adopted Crazy Yoghurt's answer to swift's extensions.
extension UILabel {
func boldRange(_ range: Range<String.Index>) {
if let text = self.attributedText {
let attr = NSMutableAttributedString(attributedString: text)
let start = text.string.characters.distance(from: text.string.startIndex, to: range.lowerBound)
let length = text.string.characters.distance(from: range.lowerBound, to: range.upperBound)
attr.addAttributes([NSFontAttributeName: UIFont.boldSystemFont(ofSize: self.font.pointSize)], range: NSMakeRange(start, length))
self.attributedText = attr
}
}
func boldSubstring(_ substr: String) {
if let text = self.attributedText {
var range = text.string.range(of: substr)
let attr = NSMutableAttributedString(attributedString: text)
while range != nil {
let start = text.string.characters.distance(from: text.string.startIndex, to: range!.lowerBound)
let length = text.string.characters.distance(from: range!.lowerBound, to: range!.upperBound)
var nsRange = NSMakeRange(start, length)
let font = attr.attribute(NSFontAttributeName, at: start, effectiveRange: &nsRange) as! UIFont
if !font.fontDescriptor.symbolicTraits.contains(.traitBold) {
break
}
range = text.string.range(of: substr, options: NSString.CompareOptions.literal, range: range!.upperBound..<text.string.endIndex, locale: nil)
}
if let r = range {
boldRange(r)
}
}
}
}
May be there is not good conversion between Range and NSRange, but I didn't found something better.
Check out TTTAttributedLabel. It's a drop-in replacement for UILabel that allows you to have mixed font and colors in a single label by setting an NSAttributedString as the text for that label.
In this case you could try,
UILabel *displayLabel = [[UILabel alloc] initWithFrame:/*label frame*/];
displayLabel.font = [UIFont boldSystemFontOfSize:/*bold font size*/];
NSMutableAttributedString *notifyingStr = [[NSMutableAttributedString alloc] initWithString:#"Updated: 2012/10/14 21:59 PM"];
[notifyingStr beginEditing];
[notifyingStr addAttribute:NSFontAttributeName
value:[UIFont systemFontOfSize:/*normal font size*/]
range:NSMakeRange(8,10)/*range of normal string, e.g. 2012/10/14*/];
[notifyingStr endEditing];
displayLabel.attributedText = notifyingStr; // or [displayLabel setAttributedText: notifyingStr];
To make text bold as well as underline in a UILabel. Just add the following lines in your code.
NSRange range1 = [lblTermsAndCondition.text rangeOfString:NSLocalizedString(#"bold_terms", #"")];
NSRange range2 = [lblTermsAndCondition.text rangeOfString:NSLocalizedString(#"bold_policy", #"")];
NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithString:lblTermsAndCondition.text];
[attributedText setAttributes:#{NSFontAttributeName:[UIFont fontWithName:fontBold size:12.0]}
range:range1];
[attributedText setAttributes:#{NSFontAttributeName:[UIFont fontWithName:fontBold size:12.0]}
range:range2];
[attributedText addAttribute:(NSString*)kCTUnderlineStyleAttributeName
value:[NSNumber numberWithInt:kCTUnderlineStyleSingle]
range:range1];
[attributedText addAttribute:(NSString*)kCTUnderlineStyleAttributeName
value:[NSNumber numberWithInt:kCTUnderlineStyleSingle]
range:range2];
lblTermsAndCondition.attributedText = attributedText;
NSString *needToChangeStr=#"BOOK";
NSString *display_string=[NSString stringWithFormat:#"This is %#",book];
NSMutableAttributedString *attri_str=[[NSMutableAttributedString alloc]initWithString:display_string];
int begin=[display_string length]-[needToChangeStr length];
int end=[needToChangeStr length];
[attri_str addAttribute:NSFontAttributeName value:[UIFont fontWithName:#"HelveticaNeue-Bold" size:30] range:NSMakeRange(begin, end)];
Swift 4:
// attribute with color red and Bold
var attrs1 = [NSAttributedStringKey.font: UIFont.boldSystemFont(ofSize: 20), NSAttributedStringKey.foregroundColor: UIColor.red]
// attribute with color black and Non Bold
var attrs2 = [NSAttributedStringKey.font: UIFont(name: "Roboto-Regular", size: 20), NSAttributedStringKey.foregroundColor: UIColor.black]
var color1 = NSAttributedString(string: "RED", attributes: attrs1)
var color2 = NSAttributedString(string: " BLACK", attributes: attrs2)
var string = NSMutableAttributedString()
string.append(color1)
string.append(color2)
// print the text with **RED** BLACK
print("Final String : \(string)")
Supply the string to process as input and supply the words which should be bold/colored as input.
func attributedString(parentString:String, arrayOfStringToProcess:[String], color:UIColor) -> NSAttributedString
{
let parentAttributedString = NSMutableAttributedString(string:parentString, attributes:nil)
let parentStringWords = parentAttributedString.string.components(separatedBy: " ")
if parentStringWords.count != 0
{
let wordSearchArray = arrayOfStringToProcess.filter { inputArrayIndex in
parentStringWords.contains(where: { $0 == inputArrayIndex }
)}
for eachWord in wordSearchArray
{
parentString.enumerateSubstrings(in: parentString.startIndex..<parentString.endIndex, options: .byWords)
{
(substring, substringRange, _, _) in
if substring == eachWord
{
parentAttributedString.addAttribute(.font, value: UIFont.boldSystemFont(ofSize: 15), range: NSRange(substringRange, in: parentString))
parentAttributedString.addAttribute(.foregroundColor, value: color, range: NSRange(substringRange, in: parentString))
}
}
}
}
return parentAttributedString
}
No need for NSRange with the following code I just implemented in my project (in Swift):
//Code sets label (yourLabel)'s text to "Tap and hold(BOLD) button to start recording."
let boldAttribute = [
//You can add as many attributes as you want here.
NSFontAttributeName: UIFont(name: "HelveticaNeue-Bold", size: 18.0)!
]
let regularAttribute = [NSFontAttributeName: UIFont(name: "HelveticaNeue-Light", size: 18.0)!]
let beginningAttributedString = NSAttributedString(string: "Tap and ", attributes: regularAttribute )
let boldAttributedString = NSAttributedString(string: "hold ", attributes: boldAttribute)
let endAttributedString = NSAttributedString(string: "button to start recording.", attributes: regularAttribute )
let fullString = NSMutableAttributedString()
fullString.appendAttributedString(beginningAttributedString)
fullString.appendAttributedString(boldAttributedString)
fullString.appendAttributedString(endAttributedString)
yourLabel.attributedText = fullString
If you want to make using attributed strings easier, try using Attributed String Creator, which will generate the code for you. https://itunes.apple.com/us/app/attributed-string-creator/id730928349
AttributeString has constructors that take mark down strings, doing it this way can mean your attribute string has no other attributes, so if you are using xibs, you can have the text without the bold, set to all the other attributes you want, then in code enumerate through the attribute ranges of your mark down string and apply them to the attributed string you get from you xib file, and then reapply it to your attributed attributeString of the particular field.

iOS7 TextKit: bullet point alignment

I'm writing an app for iOS 7 only and I'm trying to get decent formatting on bullet points in a non-editable UITextView.
It's easy enough to just insert a bullet point character, but of course the left indentation won't follow. What's the easiest way on iOS 7 to set a left indent after a bullet point?
Thanks in advance,
Frank
So I've looked around, and here is the extracted minimal code from Duncan's answer to make it work:
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:yourLabel.text];
NSMutableParagraphStyle *paragrahStyle = [[NSMutableParagraphStyle alloc] init];
[paragrahStyle setParagraphSpacing:4];
[paragrahStyle setParagraphSpacingBefore:3];
[paragrahStyle setFirstLineHeadIndent:0.0f]; // First line is the one with bullet point
[paragrahStyle setHeadIndent:10.5f]; // Set the indent for given bullet character and size font
[attributedString addAttribute:NSParagraphStyleAttributeName value:paragrahStyle
range:NSMakeRange(0, [self.descriptionLabel.text length])];
yourLabel.attributedText = attributedString;
And here is the result of that in my app:
Below it the code I use to set a bulleted paragraph. This comes straight out of a working app and is used to apply the style to the entire paragraph in response to a user clicking on a formatting button. I have tried to put in all the dependent methods but may have missed some.
Note that I am setting most indents in centimetres and hence the use of the conversion functions at the end of the listing.
I am also checking for the presence of a tab character (no tab key on iOS!) and automatically insert a dash and a tab.
If all you need is the paragraph style then look at the last few methods below where the firstLineIndent etc get set up.
Note that these calls all get wrapped in [textStorage beginEditing/endEditing]. Despite the (IBAction) below the method is not getting called by a UI object directly.
- (IBAction) styleBullet1:(id)sender
{
NSRange charRange = [self rangeForUserParagraphAttributeChange];
NSTextStorage *myTextStorage = [self textStorage];
// Check for "-\t" at beginning of string and add if not found
NSAttributedString *attrString = [myTextStorage attributedSubstringFromRange:charRange];
NSString *string = [attrString string];
if ([string rangeOfString:#"\t"].location == NSNotFound) {
NSLog(#"string does not contain tab so insert one");
NSAttributedString * aStr = [[NSAttributedString alloc] initWithString:#"-\t"];
// Insert a bullet and tab
[[self textStorage] insertAttributedString:aStr atIndex:charRange.location];
} else {
NSLog(#"string contains tab");
}
if ([self isEditable] && charRange.location != NSNotFound)
{
[myTextStorage setAttributes:[self bullet1Style] range:charRange];
}
}
- (NSDictionary*)bullet1Style
{
return [self createStyle:[self getBullet1ParagraphStyle] font:[self normalFont] fontColor:[UIColor blackColor] underlineStyle:NSUnderlineStyleNone];
}
- (NSDictionary*)createStyle:(NSParagraphStyle*)paraStyle font:(UIFont*)font fontColor:(UIColor*)color underlineStyle:(int)underlineStyle
{
NSMutableDictionary *style = [[NSMutableDictionary alloc] init];
[style setValue:paraStyle forKey:NSParagraphStyleAttributeName];
[style setValue:font forKey:NSFontAttributeName];
[style setValue:color forKey:NSForegroundColorAttributeName];
[style setValue:[NSNumber numberWithInt: underlineStyle] forKey:NSUnderlineStyleAttributeName];
FLOG(#" font is %#", font);
return style;
}
- (NSParagraphStyle*)getBullet1ParagraphStyle
{
NSMutableParagraphStyle *para;
para = [self getDefaultParagraphStyle];
NSMutableArray *tabs = [[NSMutableArray alloc] init];
[tabs addObject:[[NSTextTab alloc] initWithTextAlignment:NSTextAlignmentLeft location:[self ptsFromCMF:1.0] options:nil]];
//[tabs addObject:[[NSTextTab alloc] initWithType:NSLeftTabStopType location:[self ptsFromCMF:1.0]]];
[para setTabStops:tabs];
[para setDefaultTabInterval:[self ptsFromCMF:2.0]];
[para setFirstLineHeadIndent:[self ptsFromCMF:0.0]];
//[para setHeaderLevel:0];
[para setHeadIndent:[self ptsFromCMF:1.0]];
[para setParagraphSpacing:3];
[para setParagraphSpacingBefore:3];
return para;
}
- (NSMutableParagraphStyle*)getDefaultParagraphStyle
{
NSMutableParagraphStyle *para;
para = [[NSParagraphStyle defaultParagraphStyle]mutableCopy];
[para setTabStops:nil];
[para setAlignment:NSTextAlignmentLeft];
[para setBaseWritingDirection:NSWritingDirectionLeftToRight];
[para setDefaultTabInterval:[self ptsFromCMF:3.0]];
[para setFirstLineHeadIndent:0];
//[para setHeaderLevel:0];
[para setHeadIndent:0.0];
[para setHyphenationFactor:0.0];
[para setLineBreakMode:NSLineBreakByWordWrapping];
[para setLineHeightMultiple:1.0];
[para setLineSpacing:0.0];
[para setMaximumLineHeight:0];
[para setMinimumLineHeight:0];
[para setParagraphSpacing:6];
[para setParagraphSpacingBefore:3];
//[para setTabStops:<#(NSArray *)#>];
[para setTailIndent:0.0];
return para;
}
-(NSNumber*)ptsFromCMN:(float)cm
{
return [NSNumber numberWithFloat:[self ptsFromCMF:cm]];
}
-(float)ptsFromCMF:(float)cm
{
return cm * 28.3464567;
}
This is the easiest solution I've found:
let bulletList = UILabel()
let bulletListArray = ["line 1 - enter a bunch of lorem ipsum here so it wraps to the next line", "line 2", "line 3"]
let joiner = "\n"
var paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.headIndent = 10
paragraphStyle.firstLineHeadIndent = 0
let attributes = [NSParagraphStyleAttributeName: paragraphStyle]
let bulletListString = joiner.join(bulletListArray.map { "• \($0)" })
bulletList.attributedText = NSAttributedString(string: bulletListString, attributes: attributes)
the theory being each string in the array acts like a 'paragraph' and the paragraph style gets 0 indent on the first line which gets a bullet added using the map method.. then for every line after it gets a 10 px indent (adjust spacing for your font metrics)
Other answers rely on setting the indent size with a constant value. That means you'll have to manually update it if you're changing fonts, and will not work well if you're using Dynamic Type. Fortunately, measuring text is easy.
Let's say you have some text and some attributes:
NSString *text = #"• Some bulleted paragraph";
UIFont *font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
NSDictionary *attributes = #{NSFontAttributeName: font};
Here's how to measure the bullet and create a paragraph style accordingly:
NSString *bulletPrefix = #"• ";
CGSize size = [bulletPrefix sizeWithAttributes:attributes];
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
paragraphStyle.headIndent = size.width;
We insert this in our attributes and create the attributed string:
NSMutableDictionary *indentedAttributes = [attributes mutableCopy];
indentedAttributes[NSParagraphStyleAttributeName] = [paragraphStyle copy];
NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:text attributes:indentedAttributes];
Swift 5
I made an extension for NSAttributedString that adds a convenience initializer which properly indents different types of lists.
extension NSAttributedString {
convenience init(listString string: String, withFont font: UIFont) {
self.init(attributedListString: NSAttributedString(string: string), withFont: font)
}
convenience init(attributedListString attributedString: NSAttributedString, withFont font: UIFont) {
guard let regex = try? NSRegularExpression(pattern: "^(\\d+\\.|[•\\-\\*])(\\s+).+$",
options: [.anchorsMatchLines]) else { fatalError() }
let matches = regex.matches(in: attributedString.string, options: [],
range: NSRange(location: 0, length: attributedString.string.utf16.count))
let nsString = attributedString.string as NSString
let mutableAttributedString = NSMutableAttributedString(attributedString: attributedString)
for match in matches {
let size = NSAttributedString(
string: nsString.substring(with: match.range(at: 1)) + nsString.substring(with: match.range(at: 2)),
attributes: [.font: font]).size()
let indentation = ceil(size.width)
let range = match.range(at: 0)
let paragraphStyle = NSMutableParagraphStyle()
if let style = attributedString.attribute(.paragraphStyle, at: 0, longestEffectiveRange: nil, in: range)
as? NSParagraphStyle {
paragraphStyle.setParagraphStyle(style)
}
paragraphStyle.tabStops = [NSTextTab(textAlignment: .left, location: indentation, options: [:])]
paragraphStyle.defaultTabInterval = indentation
paragraphStyle.firstLineHeadIndent = 0
paragraphStyle.headIndent = indentation
mutableAttributedString.addAttribute(.font, value: font, range: range)
mutableAttributedString.addAttribute(.paragraphStyle, value: paragraphStyle, range: range)
}
self.init(attributedString: mutableAttributedString)
}
}
Example usage:
The number of spaces after each bullet etc. doesn't matter. The code will calculate the appropriate indentation width dynamically based on how many tabs or spaces you decide to have after your bullet.
If the attributed string already has a paragraph style, the convenience initializer will retain the options of that paragraph style and apply some options of its own.
Supported symbols: •, -, *, numbers followed by a period (e.g. 8.)
You all can do this simple thing using Attributes Inspector,Select Indent Field and do whatever changes you wanted to do :)
Based off thisispete's solution, updated to Swift 4.2.
Swift 4.2
let array = ["1st", "2nd", "3rd"]
let textView = UITextView()
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.firstLineHeadIndent = 0
paragraphStyle.headIndent = 12
let bulletListText = array.map { "• \($0)" }.joined(separator: "\n")
let attributes = [
NSAttributedString.Key.paragraphStyle: paragraphStyle,
NSAttributedString.Key.font: UIFont.systemFont(ofSize: 17.0)
]
textView.attributedText = NSAttributedString(string: bulletListText, attributes: attributes)
I made a swift solution (Swift 2.3 at the moment) based on Lukas implementation. I had a little issue with the lines that had no bullet points, so I made the extension so you can optionally pass a range to apply the paragraph style.
extension String{
func getAllignedBulletPointsMutableString(bulletPointsRange: NSRange = NSMakeRange(0, 0)) -> NSMutableAttributedString{
let attributedString: NSMutableAttributedString = NSMutableAttributedString(string: self)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.paragraphSpacing = 0
paragraphStyle.paragraphSpacingBefore = 0
paragraphStyle.firstLineHeadIndent = 0
paragraphStyle.headIndent = 7.5
attributedString.addAttributes([NSParagraphStyleAttributeName: paragraphStyle], range: bulletPointsRange)
return attributedString
}
}

Resources