Chirag Jain
Chirag Jain

Reputation: 2418

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?

Upvotes: 43

Views: 38449

Answers (9)

seymatanoglu
seymatanoglu

Reputation: 151

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

Upvotes: 0

Suragch
Suragch

Reputation: 512666

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

  • enter image description here
  • enter image description here

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:

  • enter image description here
  • enter image description here

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

  • enter image description here
  • enter image description here

See also

Upvotes: 41

Syed Zahid Shah
Syed Zahid Shah

Reputation: 391

        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

Upvotes: 2

Viktor
Viktor

Reputation: 43

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

Upvotes: 1

Jonny
Jonny

Reputation: 16338

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

Upvotes: 1

Wes
Wes

Reputation: 81

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

Upvotes: 8

Duck
Duck

Reputation: 36013

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];

Upvotes: 2

Darius Miliauskas
Darius Miliauskas

Reputation: 3524

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:.

Upvotes: 2

Artal
Artal

Reputation: 9143

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"];

Upvotes: 41

Related Questions