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.

  • Upload image to server - Swift 3
  • NSMutableArray collection and @Synchronized blocks
  • How to convert an PFFile to an UIImage with swift?
  • How to play a sound using Swift?
  • Is it possible for Swift to inherit from an lightweight generic Objective-c Class?
  • Can't update calabash server version
  • 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?

    6 Solutions Collect From Internet About “replace entire text string in NSAttributedString without modifying other attributes”

    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 version

    Change the text while keeping the attributes:

    let myString = "my string"
    let myAttributes = [NSForegroundColorAttributeName: UIColor.blue, NSFontAttributeName: 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 = [ NSBackgroundColorAttributeName: 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

    • my main answer about Swift attributed strings

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

    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

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