Replace substring of NSAttributedString with another NSAttributedString

42,736

Solution 1

  1. Convert your attributed string into an instance of NSMutableAttributedString.

  2. The mutable attributed string has a mutableString property. According to the documentation:

    "The receiver tracks changes to this string and keeps its attribute mappings up to date."

    So you can use the resulting mutable string to execute the replacement with replaceOccurrencesOfString:withString:options:range:.

Solution 2

Here is how you can change the string of NSMutableAttributedString, while preserving its attributes:

Swift:

// first we create a mutable copy of attributed text 
let originalAttributedText = nameLabel.attributedText?.mutableCopy() as! NSMutableAttributedString

// then we replace text so easily
let newAttributedText = originalAttributedText.mutableString.setString("new text to replace")

Objective-C:

NSMutableAttributedString *newAttrStr = [attribtedTxt.mutableString setString:@"new string"];

Solution 3

In my case, the following way was the only (tested on iOS9):

NSAttributedString *attributedString = ...;
NSAttributedString *anotherAttributedString = ...; //the string which will replace

while ([attributedString.mutableString containsString:@"replace"]) {
        NSRange range = [attributedString.mutableString rangeOfString:@"replace"];
        [attributedString replaceCharactersInRange:range  withAttributedString:anotherAttributedString];
    }

Of course it will be nice to find another better way.

Solution 4

Swift 4: Updated sunkas excellent solution to Swift 4 and wrapped in "extension". Just clip this into your ViewController (outside the class) and use it.

extension NSAttributedString {
    func stringWithString(stringToReplace: String, replacedWithString newStringPart: String) -> NSMutableAttributedString
    {
        let mutableAttributedString = mutableCopy() as! NSMutableAttributedString
        let mutableString = mutableAttributedString.mutableString
        while mutableString.contains(stringToReplace) {
            let rangeOfStringToBeReplaced = mutableString.range(of: stringToReplace)
            mutableAttributedString.replaceCharacters(in: rangeOfStringToBeReplaced, with: newStringPart)
        }
        return mutableAttributedString
    }
}

Solution 5

With Swift 4 and iOS 11, you can use one of the 2 following ways in order to solve your problem.


#1. Using NSMutableAttributedString replaceCharacters(in:with:) method

NSMutableAttributedString has a method called replaceCharacters(in:with:). replaceCharacters(in:with:) has the following declaration:

Replaces the characters and attributes in a given range with the characters and attributes of the given attributed string.

func replaceCharacters(in range: NSRange, with attrString: NSAttributedString)

The Playground code below shows how to use replaceCharacters(in:with:) in order to replace a substring of an NSMutableAttributedString instance with a new NSMutableAttributedString instance:

import UIKit

// Set initial attributed string
let initialString = "This is the initial string"
let attributes = [NSAttributedStringKey.foregroundColor : UIColor.red]
let mutableAttributedString = NSMutableAttributedString(string: initialString, attributes: attributes)

// Set new attributed string
let newString = "new"
let newAttributes = [NSAttributedStringKey.underlineStyle : NSUnderlineStyle.styleSingle.rawValue]
let newAttributedString = NSMutableAttributedString(string: newString, attributes: newAttributes)

// Get range of text to replace
guard let range = mutableAttributedString.string.range(of: "initial") else { exit(0) }
let nsRange = NSRange(range, in: mutableAttributedString.string)

// Replace content in range with the new content
mutableAttributedString.replaceCharacters(in: nsRange, with: newAttributedString)

#2. Using NSMutableString replaceOccurrences(of:with:options:range:) method

NSMutableString has a method called replaceOccurrences(of:with:options:range:). replaceOccurrences(of:with:options:range:) has the following declaration:

Replaces all occurrences of a given string in a given range with another given string, returning the number of replacements.

func replaceOccurrences(of target: String, with replacement: String, options: NSString.CompareOptions = [], range searchRange: NSRange) -> Int

The Playground code below shows how to use replaceOccurrences(of:with:options:range:) in order to replace a substring of an NSMutableAttributedString instance with a new NSMutableAttributedString instance:

import UIKit

// Set initial attributed string
let initialString = "This is the initial string"
let attributes = [NSAttributedStringKey.foregroundColor : UIColor.red]
let mutableAttributedString = NSMutableAttributedString(string: initialString, attributes: attributes)

// Set new string
let newString = "new"

// Replace replaceable content in mutableAttributedString with new content
let totalRange = NSRange(location: 0, length: mutableAttributedString.string.count)
_ = mutableAttributedString.mutableString.replaceOccurrences(of: "initial", with: newString, options: [], range: totalRange)

// Get range of text that requires new attributes
guard let range = mutableAttributedString.string.range(of: newString) else { exit(0) }
let nsRange = NSRange(range, in: mutableAttributedString.string)

// Apply new attributes to the text matching the range
let newAttributes = [NSAttributedStringKey.underlineStyle : NSUnderlineStyle.styleSingle.rawValue]
mutableAttributedString.setAttributes(newAttributes, range: nsRange)
Share:
42,736
Garoal
Author by

Garoal

I have some experience in Fortran 95, java, c++, html and javascript, but I'm not an expert on any of them. Now, I'm learning how to make iPhone apps and games. After a couple of years learning Objective-C, I'm focusing now in the new programming language for iOS: swift.

Updated on July 08, 2022

Comments

  • Garoal
    Garoal almost 2 years

    I want to replace a substring (e.g. @"replace") of an NSAttributedString with another NSAttributedString.

    I am looking for an equivalent method to NSString's stringByReplacingOccurrencesOfString:withString: for NSAttributedString.

  • Michal Shatz
    Michal Shatz over 9 years
    Isn't this private API as the mutable string property is read only?
  • Ole Begemann
    Ole Begemann over 9 years
    @MichalShatz No. The property being read-only only means that you cannot assign a different object to it (i.e., you can’t call setMutableString:). But it is perfectly fine to modify the mutable string object in place. That’s the whole reason this property exists.
  • Cœur
    Cœur over 8 years
    Your example will probably not even compile. A C-string stored in an NSMutableAttributedString? A replacement with no effect because you did it on a copy (mutableString) without reference?
  • Knight0fDragon
    Knight0fDragon over 8 years
    Is this really an answer? I am getting a casting problem with the second parameter (withString) being an attributedString
  • Darius Miliauskas
    Darius Miliauskas about 8 years
    The method "replaceOccurrencesOfString:withString:options:range:" is applied for NSMutableString, not for NSAttributedString or NSMutableAttributedString. If you convert to NSMutableString make the replacements there, you will lose its attributes after the conversion. The question is about how to get NSAttributedString as the result, not just string, so the answer is totally irrelevant but it got so many upvotes.
  • shinoys222
    shinoys222 about 8 years
    Darius Miliauskas Do you know any workaround for it ?
  • mofojed
    mofojed over 7 years
    This works fine, example replacing new line chars: [[result mutableString] replaceOccurrencesOfString:@"\n" withString:@" " options:NSCaseInsensitiveSearch range:NSMakeRange(0, result.length)];
  • Will Von Ullrich
    Will Von Ullrich about 7 years
    This is more appropriate for replacing a string at a given range, rather than an occurrence, as strings may contain duplicate substrings.
  • Glenn Posadas
    Glenn Posadas almost 6 years
    Thanks for this, trickster! I've been trying so hard to implement such a function to my project, but with different tag (e.g. <i>). After some hours of work, I decided to try this out but with some few modifications, like having a custom font. Thanks!
  • StackUnderflow
    StackUnderflow over 4 years
    "Just clip this into your ViewController (outside the class) and use it." - So that is the best place to put your extensions right?
  • gundrabur
    gundrabur over 4 years
    I didn't say that, I just said it will work that way and it does. Where do you put all your extensions in?
  • StackUnderflow
    StackUnderflow over 4 years
    Well you have to be careful what you say, there are a lot of developers learning bad habits in here. If you have extensions for NSAttributedString then create a file called "NSAttributedString+Extensions.swift" and put it in a folder called "Extensions".
  • Peter R
    Peter R almost 2 years
    This does not work. let str = NSMutableAttributedString(string: "HERE") str.addAttribute(.foregroundColor, value: NSColor.blue, range: NSMakeRange(1, 1)); print(str); str.mutableString.setString("THERE"); print(str). Running this code prints: H{ }E{ NSColor = "sRGB IEC61966-2.1 colorspace 0 0 1 1"; }RE{ } the first time, and THERE{ } the second time. Attributes gone.