Change attributes of substrings in a NSAttributedString - ios

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

Related

Objective-C: Equivalent to stringWithFormat for NSMutableAttributedString

I am trying to add a hyperlink in a string. I have a localized string and I put %# to format my string. When I add the attributed string into my string, the attributed format gives the raw result which is NSLink = "https://www.example.com". I could not find an attributed string formatter the same as the string formatter. How can I achieve the same behaviour in my case?
Code:
NSMutableAttributedString * str = [[NSMutableAttributedString alloc] initWithString:#"Example"];
[str addAttribute: NSLinkAttributeName value: #"https:/www.example.com" range: NSMakeRange(0, str.length)];
NSMutableAttributedString *originalStr = [[NSMutableAttributedString alloc] initWithString: self.pageDescriptions[3].localized];
pageContentViewController.messageText = [NSString stringWithFormat:self.pageDescriptions[index].localized, str];
stringWithFormat: is only doing substitutions of formatters. So if your formatters are simple enough, like a %#, you can just search for it with rangeOfString: and do the substitution yourself with replaceCharactersInRange:withAttributedString:.
NSMutableAttributedString *strWithLink = [[NSMutableAttributedString alloc] initWithString:#"Example"];
[strWithLink addAttribute:NSLinkAttributeName value:#"https:/www.example.com" range:NSMakeRange(0, strWithLink.length)];
NSMutableAttributedString *strWithFormat = [[NSMutableAttributedString alloc] initWithString:#"hello %# world"];
[strWithFormat replaceCharactersInRange:[strWithFormat.string rangeOfString:#"%#"] withAttributedString:strWithLink];
The result here in strWithFormat kept the attributes of strWithLink.
Note: this won't work correctly if your format is complex, like with %%# %# %%#, because it will replace the first occurrence of %#, while stringWithFormat: would have replaced the middle occurrence.
There isn't one. You create the NSAttributedString by hand, or you create an extension that does what you want if you do this a lot.

Replace character in NSMutableAttributedString

This works for a regular NSString:
NSString *newString = [myString stringByReplacingOccurrencesOfString:#"," withString:#""];
But there is no such method for NSMutableAttributedString. How could I remove all instances of a comma in an NSMutableAttributedString?
let attrString = NSMutableAttributedString(string: "Hello <b>friend<b>")
attrString.mutableString.replaceOccurrencesOfString("<b>", withString: "", options: NSStringCompareOptions.CaseInsensitiveSearch, range: NSRange(location: 0, length: attrString.length))
Try this :)
Do it before you create the attributed string, if you can or depending on how you source it. If you can't then you can use replaceCharactersInRange:withString: (or replaceCharactersInRange:withAttributedString:), but you need to know the range so you need to search and iterate yourself.
NSString *newString= "I want to ,show, you how to achieve this";
NSMutableAttributedString *displayText = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat:#"%#",newString]];
[[displayText mutableString] replaceOccurrencesOfString:#"," withString:#"" options:NSCaseInsensitiveSearch range:NSMakeRange(0, displayText.string.length)];
You can initialize the attributed string with the stripped string with the designed init. No?
The code could be applied from my answer here:
NSAttributedString *attributedString = ...;
NSAttributedString *anotherAttributedString = ...; //the string or characters which will replace
while ([attributedString.mutableString containsString:#"replace"]) {
NSRange range = [attributedString.mutableString rangeOfString:#"replace"];
[attributedString replaceCharactersInRange:range withAttributedString:anotherAttributedString];
}

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

Display edited NSString in order

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

Replace UITextViews text with attributed string

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
}

Resources