用另一个NSAttributedString替换NSAttributedString的子字符串

69

我想要替换一个子字符串(例如@"replace")的 NSAttributedString 为另一个 NSAttributedString

我正在寻找与 NSStringstringByReplacingOccurrencesOfString:withString: 相当的方法,用于 NSAttributedString


要在给定范围内替换文本(而不是每个出现的实例),请参见@Darius下面给出的答案。 - Will Von Ullrich
10个回答

83
  1. 将您的属性字符串转换为 NSMutableAttributedString 的实例。

  2. 可变的属性字符串具有一个 mutableString 属性。根据文档:

    “接收器跟踪此字符串的更改并保持其属性映射最新。”

    因此,您可以使用结果可变字符串来执行替换操作,使用 replaceOccurrencesOfString:withString:options:range: 方法进行替换。


这个私有API不是只读的可变字符串属性吗? - Michal Shatz
@MichalShatz 不可以。只读属性意味着您无法将不同的对象分配给它(即,您无法调用setMutableString:)。但是,在原地修改可变字符串对象完全没有问题。这就是此属性存在的全部原因。 - Ole Begemann
1
这真的是一个答案吗?我在第二个参数(withString)中遇到了一个转换问题,它是一个attributedString。 - Knight0fDragon
17
方法"replaceOccurrencesOfString:withString:options:range:"适用于NSMutableString,而不适用于NSAttributedString或NSMutableAttributedString。如果您将其转换为NSMutableString并在其中进行替换,转换后将失去其属性。问题是如何获得NSAttributedString作为结果,而不仅仅是字符串,因此答案完全无关紧要,但它得到了很多赞。 - Darius Miliauskas
Darius Miliauskas,你知道任何解决方法吗? - shinoys222
6
这个例子可以正常工作,它是用来替换换行符的: [[result mutableString] replaceOccurrencesOfString:@"\n" withString:@" " options:NSCaseInsensitiveSearch range:NSMakeRange(0, result.length)]; - mofojed

21

以下是如何更改NSMutableAttributedString的字符串,同时保留其属性:

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

你的例子可能甚至无法编译。在NSMutableAttributedString中存储C字符串?替换没有效果,因为你是在不带引用的副本(mutableString)上进行的? - Cœur
这段代码不起作用。let str = NSMutableAttributedString(string: "HERE") str.addAttribute(.foregroundColor, value: NSColor.blue, range: NSMakeRange(1, 1)); print(str); str.mutableString.setString("THERE"); print(str)。运行此代码会打印出:第一次是 H{ }E{ NSColor = "sRGB IEC61966-2.1 colorspace 0 0 1 1"; }RE{ },第二次是 THERE{ }。属性消失了。 - Peter R

20

在我的情况下,以下方法是唯一可行的(已在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];
    }

当然,找到另一种更好的方式会很不错。


2
这更适合在给定范围内替换字符串,而不是出现次数,因为字符串可能包含重复的子字符串。 - Will Von Ullrich

19

Swift 4: 更新了sunkas出色的解决方案并将其包装在“扩展”中。只需将其剪切到您的ViewController(类外部),然后使用它即可。

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

只需将此代码片段剪切到您的ViewController(类之外),然后使用它。- 这样就是放置扩展的最佳位置了吧? - StackUnderflow
我并没有说那个,我只是说它会按照那种方式工作,而且确实如此。你把所有的扩展放在哪里? - gundrabur
3
你必须小心说话,这里有很多开发者会养成不好的习惯。如果你有NSAttributedString的扩展程序,那就创建一个名为“NSAttributedString+Extensions.swift”的文件,放在名为“Extensions”的文件夹中。 - StackUnderflow

17

在Swift 4和iOS 11中,您可以使用以下2种方式之一来解决问题。


#1. 使用NSMutableAttributedStringreplaceCharacters(in:with:)方法

NSMutableAttributedString有一个被称为replaceCharacters(in:with:)的方法。 replaceCharacters(in:with:)具有以下声明:

用给定的属性字符串替换给定范围内的字符和属性。

func replaceCharacters(in range: NSRange, with attrString: NSAttributedString)
下面的 Playground 代码展示了如何使用 replaceCharacters(in:with:) 方法,将 NSMutableAttributedString 实例中的子字符串替换为一个新的 NSMutableAttributedString 实例:
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. 使用 NSMutableStringreplaceOccurrences(of:with:options:range:) 方法

NSMutableString 有一个名为 replaceOccurrences(of:with:options:range:) 的方法。该方法的声明如下:

将给定范围内所有出现的给定字符串替换为另一个给定字符串,并返回替换次数。

func replaceOccurrences(of target: String, with replacement: String, options: NSString.CompareOptions = [], range searchRange: NSRange) -> Int
下面的Playground代码展示了如何使用replaceOccurrences(of:with:options:range:)方法,将一个NSMutableAttributedString实例中的子字符串替换为新的NSMutableAttributedString实例:
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)

6

我需要用 <b> 标签加粗文本,这是我做的:

- (NSAttributedString *)boldString:(NSString *)string {
    UIFont *boldFont = [UIFont boldSystemFontOfSize:14];
    NSMutableAttributedString *attributedDescription = [[NSMutableAttributedString alloc] initWithString:string];

    NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@".*?<b>(.*?)<\\/b>.*?" options:NSRegularExpressionCaseInsensitive error:NULL];
    NSArray *myArray = [regex matchesInString:string options:0 range:NSMakeRange(0, string.length)] ;
    for (NSTextCheckingResult *match in myArray) {
        NSRange matchRange = [match rangeAtIndex:1];
        [attributedDescription addAttribute:NSFontAttributeName value:boldFont range:matchRange];
    }
    while ([attributedDescription.string containsString:@"<b>"] || [attributedDescription.string containsString:@"</b>"]) {
        NSRange rangeOfTag = [attributedDescription.string rangeOfString:@"<b>"];
        [attributedDescription replaceCharactersInRange:rangeOfTag withString:@""];
        rangeOfTag = [attributedDescription.string rangeOfString:@"</b>"];
        [attributedDescription replaceCharactersInRange:rangeOfTag withString:@""];
    }
    return attributedDescription;
}

1
谢谢你,骗子!我一直在努力为我的项目实现这样的功能,但是使用不同的标签(例如<i>)。经过几个小时的工作后,我决定尝试一下这个方法,但进行了一些修改,比如使用自定义字体。谢谢! - Glenn Posadas

4
我发现其他答案都不起作用。以下是我在一个类别扩展中替换NSAttributed字符串内容的方法:
func stringWithString(stringToReplace:String, replacedWithString newStringPart:String) -> NSMutableAttributedString
{
    let mutableAttributedString = mutableCopy() as! NSMutableAttributedString
    let mutableString = mutableAttributedString.mutableString

    while mutableString.containsString(stringToReplace) {
        let rangeOfStringToBeReplaced = mutableString.rangeOfString(stringToReplace)
        mutableAttributedString.replaceCharactersInRange(rangeOfStringToBeReplaced, withString: newStringPart)
    }
    return mutableAttributedString
}

4
NSMutableAttributedString *result = [[NSMutableAttributedString alloc] initWithString:@"I am a boy."];
[result addAttribute:NSForegroundColorAttributeName value:[UIColor blackColor] range:NSMakeRange(0, [result length])];

NSMutableAttributedString *replace = [[NSMutableAttributedString alloc] initWithString:@"a"];
[replace addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:NSMakeRange(0, [replace length])];

[result replaceCharactersInRange:NSMakeRange(5, [replace length]) withAttributedString:replace];

3

我有一个具体的需求,就像下面这样固定。这可能有助于某些人。

要求:在故事板中,将包含单词“应用程序版本:1.0”的富文本直接添加到UITextView的属性中。现在,我必须通过从info plist中读取它来使版本号动态化。

解决方案:从故事板中删除版本号1.0,仅保留“应用程序版本:”,然后添加以下代码。

NSAttributedString *attribute = self.firsttextView.attributedText;
NSMutableAttributedString *mutableAttri = [[NSMutableAttributedString alloc] initWithAttributedString:attribute];
NSString *appVersionText = @"App Version:";
if ([[mutableAttri mutableString] containsString:appVersionText]) {
    NSDictionary* infoDict = [[NSBundle mainBundle] infoDictionary];
    NSString* version = [infoDict objectForKey:@"CFBundleShortVersionString"];
    NSString *newappversion = [NSString stringWithFormat:@"%@ %@",appVersionText,version] ;
    [[mutableAttri mutableString] replaceOccurrencesOfString:appVersionText withString:newappversion options:NSCaseInsensitiveSearch range:NSMakeRange(0, mutableAttri.length)];
    self.firsttextView.attributedText = mutableAttri;
}

完成了!!已更新/修改了attributedText。


2
我为此创建了一个Swift 5的扩展程序。
extension NSMutableAttributedString {
    
    func replace(_ findString: String, with replacement: String, attributes: [NSAttributedString.Key : Any]) {
        
        let ms = mutableString
        
        var range = ms.range(of: findString)
        while range.location != NSNotFound {
            addAttributes(attributes, range: range)
            ms.replaceCharacters(in: range, with: replacement)
            
            range = ms.range(of: findString)
        }
        
    }

}

应用场景

attributedString.replace("%EMAIL%", with: email, attributes: [.font:boldFont])
        

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接