Replace UITextViews text with attributed string - ios

I have a UITextView and when the user is entering text into it, I want to format the text on the fly. Something like syntax highlighting...
For that I'd like to use UITextView...
Everything works fine expect one problem: I take the text from the text view and make an NSAttributedString from of it. I make some edits to this attributed string and set it back as the textView.attributedText.
This happens everytime the user types. So I have to remember the selectedTextRange before the edit to the attributedText and set it back afterwards so that the user can continue typing at the place he was typing before. The only problem is that once the text is long enough to require scrolling, the UITextView will now start scrolling to the top if I type slowly.
Here is some sample code:
- (void)formatTextInTextView:(UITextView *)textView
{
NSRange selectedRange = textView.selectedRange;
NSString *text = textView.text;
// This will give me an attributedString with the base text-style
NSMutableAttributedString *attributedString = [self attributedStringFromString:text];
NSError *error = nil;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"#(\\w+)" options:0 error:&error];
NSArray *matches = [regex matchesInString:text
options:0
range:NSMakeRange(0, text.length)];
for (NSTextCheckingResult *match in matches)
{
NSRange matchRange = [match rangeAtIndex:0];
[attributedString addAttribute:NSForegroundColorAttributeName
value:[UIColor redColor]
range:matchRange];
}
textView.attributedText = attributedString;
textView.selectedRange = selectedRange;
}
Is there any solution without using CoreText directly? I like the UITextViews ability to select text and so on....

I am not sure that this is correct solution, but it works.
Just disable scrolling before formatting text and enable it after formatting
- (void)formatTextInTextView:(UITextView *)textView
{
textView.scrollEnabled = NO;
NSRange selectedRange = textView.selectedRange;
NSString *text = textView.text;
// This will give me an attributedString with the base text-style
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:text];
NSError *error = nil;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"#(\\w+)" options:0 error:&error];
NSArray *matches = [regex matchesInString:text
options:0
range:NSMakeRange(0, text.length)];
for (NSTextCheckingResult *match in matches)
{
NSRange matchRange = [match rangeAtIndex:0];
[attributedString addAttribute:NSForegroundColorAttributeName
value:[UIColor redColor]
range:matchRange];
}
textView.attributedText = attributedString;
textView.selectedRange = selectedRange;
textView.scrollEnabled = YES;
}

Used Sergeys's answer myself and ported it to Swift 2:
func formatTextInTextView(textView: UITextView) {
textView.scrollEnabled = false
let selectedRange = textView.selectedRange
let text = textView.text
// This will give me an attributedString with the base text-style
let attributedString = NSMutableAttributedString(string: text)
let regex = try? NSRegularExpression(pattern: "#(\\w+)", options: [])
let matches = regex!.matchesInString(text, options: [], range: NSMakeRange(0, text.characters.count))
for match in matches {
let matchRange = match.rangeAtIndex(0)
attributedString.addAttribute(NSForegroundColorAttributeName, value: UIColor.redColor(), range: matchRange)
}
textView.attributedText = attributedString
textView.selectedRange = selectedRange
textView.scrollEnabled = true
}

Swift 2.0:
let myDisplayTxt:String = "Test String"
let string: NSMutableAttributedString = NSMutableAttributedString(string: self.myDisplayTxt)
string.addAttribute(NSForegroundColorAttributeName, value: UIColor.redColor(), range: NSMakeRange(0, 5))
string.addAttribute(String(kCTForegroundColorAttributeName), value: UIColor.redColor().CGColor as AnyObject, range: NSMakeRange(0, 5))
self.sampleTextView.attributedText = string

In Swift 4:
func createAttributedText() {
let stringText = "Test String"
let stringCount = stringText.count
let string: NSMutableAttributedString = NSMutableAttributedString(string: stringText)
string.addAttribute(NSForegroundColorAttributeName, value: UIColor.red, range: NSMakeRange(0, stringCount))
self.textView.attributedText = string
}

Related

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

How the find the all the ranges of all strings between quotation marks

I have been working on this problem for a couple of days but can't find a solution to it. I have a very long string that contains a lot of quotes and I want to be able to find all of the strings (the ranges of this strings) so that I can bold them using NSMutableAttributedString
Example
This is a string "with quoted" text and there is "some more" quoted text.
I want to be able to turn this string into the following:
This is a string "with quoted" text and there is "some more" quoted text.
This is what I have so far but it won't do the rest of the string:
- (void)stringBetweenString:(NSString*)start andString:(NSString*)end inString:(NSMutableAttributedString *)text
{
NSRange startRange = [[text string] rangeOfString:start];
if (startRange.location != NSNotFound)
{
NSRange targetRange;
targetRange.location = startRange.location + startRange.length;
targetRange.length = [text length] - targetRange.location;
NSRange endRange = [[text string] rangeOfString:end options:0 range:targetRange];
if (endRange.location != NSNotFound)
{
targetRange.length = endRange.location - targetRange.location;
[text addAttribute:NSFontAttributeName value:[NSFont fontWithName:#"Helvetica-Bold" size:12.0] range:targetRange];
}
}
}
If text is too long this could be little bit slow however it works. Working on Regex solution.
NSString *string = #"Example This is a string \"with quoted1\" text and there is \"some more1\" quoted text. I want to be able to turn this string into the following: This is a string \"with quoted2\" text and there is \"some more2\" quoted text.";
NSMutableAttributedString *attString = [[NSMutableAttributedString alloc] initWithString:string attributes:nil];
int leftFromLeft = 0;
while ([string rangeOfString:#"\""].location != NSNotFound) {
NSRange quoteLocationFirst = [string
rangeOfString:#"\""
options:0
range:NSMakeRange(leftFromLeft, string.length - leftFromLeft)
];
leftFromLeft = quoteLocationFirst.location + quoteLocationFirst.length;
NSRange quoteLocationSecond = [string
rangeOfString:#"\""
options:0
range:NSMakeRange(leftFromLeft, string.length - leftFromLeft)
];
NSRange quotedTextRange = NSMakeRange(
quoteLocationFirst.location,
quoteLocationSecond.location - quoteLocationFirst.location + 1
);
UIFont *font = [UIFont fontWithName:#"Helvetica-Bold" size:30.0f];
[attString addAttribute:NSFontAttributeName value:font range:quotedTextRange];
NSLog(#"%# \r\n\r\n", [string substringWithRange:quotedTextRange]);
leftFromLeft = quoteLocationSecond.location + quoteLocationSecond.length;
if ([string rangeOfString:#"\"" options:0 range:NSMakeRange(leftFromLeft, string.length - leftFromLeft)].location == NSNotFound) {
string = #"";
}
}
Edit
Regex solution appears to be better/faster.
NSString *string = #"Example This is a string \"with quoted1\" text and there is \"some more1\" quoted text. I want to be able to turn this string into the following: This is a string \"with quoted2\" text and there is \"some more2\" quoted text. Example This is a string \"with quoted3\" text and there is \"some more3\" quoted text. I want to be able to turn this string into the following: This is a string \"with quoted4\" text and there is \"some more4\" quoted text.";
NSMutableAttributedString *attString = [[NSMutableAttributedString alloc] initWithString:string attributes:nil];
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"\"([^\"]*)\"" options:NSRegularExpressionCaseInsensitive error:nil];
NSArray *arrayOfAllMatches = [regex matchesInString:string options:0 range:NSMakeRange(0, string.length)];
for (NSTextCheckingResult *match in arrayOfAllMatches) {
UIFont *font = [UIFont fontWithName:#"Helvetica-Bold" size:30.0f];
[attString addAttribute:NSFontAttributeName value:font range:match.range];
//NSLog(#"%#", [string substringWithRange:match.range]);
}

UITextView: assigning attributedText on iOS 6 leads to unexpected result

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

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

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