问题:NSAttributedString需要一个NSRange,而我正在使用一个使用范围的Swift字符串

let text = "Long paragraph saying something goes here!"
let textRange = text.startIndex..<text.endIndex
let attributedString = NSMutableAttributedString(string: text)

text.enumerateSubstringsInRange(textRange, options: NSStringEnumerationOptions.ByWords, { (substring, substringRange, enclosingRange, stop) -> () in

    if (substring == "saying") {
        attributedString.addAttribute(NSForegroundColorAttributeName, value: NSColor.redColor(), range: substringRange)
    }
})

产生以下错误:

错误:'Range'不能转换为'NSRange' attributedString。addAttribute(NSForegroundColorAttributeName,值:NSColor.redColor(),范围:substringRange)


当前回答

Swift 5解决方案

将Range转换为NSRange

由于'encodedOffset'已弃用,所以现在为了转换String。Index to Int我们需要Range< string的原始字符串的引用。推导出索引>。

一个方便的详细扩展NSRange可以如下所示:

extension NSRange {

    public init(range: Range<String.Index>, 
                originalText: String) {

        let range_LowerBound_INDEX = range.lowerBound
        let range_UpperBound_INDEX = range.upperBound

        let range_LowerBound_INT = range_LowerBound_INDEX.utf16Offset(in: originalText)
        let range_UpperBound_INT = range_UpperBound_INDEX.utf16Offset(in: originalText)

        let locationTemp = range_LowerBound_INT
        let lengthTemp = range_UpperBound_INT - range_LowerBound_INT

        self.init(location: locationTemp,
                  length: lengthTemp)
    }
}

而简写扩展如下所示

extension NSRange {

    public init(range: Range<String.Index>, 
                originalText: String) {

        self.init(location: range.lowerBound.utf16Offset(in: originalText),
                  length: range.upperBound.utf16Offset(in: originalText) - range.lowerBound.utf16Offset(in: originalText))
    }
}

现在我们可以使用任何Range将其转换为NSRange,如下所示,分享我自己的需求,这让我写了上面的扩展

我使用下面的字符串扩展从字符串中找到特定单词的所有范围

extension String {
        
    func ranges(of substring: String, options: CompareOptions = [], locale: Locale? = nil) -> [Range<Index>] {
        var ranges: [Range<Index>] = []
        while let range = range(of: substring, options: options, range: (ranges.last?.upperBound ?? self.startIndex)..<self.endIndex, locale: locale) {
            ranges.append(range)
        }
        return ranges
    }
}

我的要求是改变字符串中特定单词的颜色,因此我写了这个扩展,它做的工作

extension NSAttributedString {

    static func colored(originalText:String,
                        wordToColor:String,
                        currentColor:UIColor,
                        differentColor:UIColor) -> NSAttributedString {
        
        let attr = NSMutableAttributedString(string: originalText)
        
        attr.beginEditing()
        
        attr.addAttribute(NSAttributedString.Key.foregroundColor,
                          value: currentColor,
                          range: NSRange(location: 0, length: originalText.count))
        
        // FOR COVERING ALL THE OCCURENCES
        for eachRange in originalText.ranges(of: wordToColor) {
            attr.addAttribute(NSAttributedString.Key.foregroundColor,
                              value: differentColor,
                              range: NSRange(range: eachRange, originalText: originalText))
        }
        
        attr.endEditing()
        
        return attr
    }

}

最后,我使用它从我的主要代码如下

let text = "Collected".localized() + "  +  " + "Cancelled".localized() + "  +  " + "Pending".localized()
myLabel.attributedText = NSAttributedString.colored(originalText: text,
                                                    wordToColor: "+",
                                                    currentColor: UIColor.purple,
                                                    differentColor: UIColor.blue)

结果如下所示,将+符号的颜色从主文本的紫色改为蓝色。

希望这能帮助到需要帮助的人。谢谢!

其他回答

可能的解决方案

Swift提供了distance()来测量开始和结束之间的距离,可以用来创建一个NSRange:

let text = "Long paragraph saying something goes here!"
let textRange = text.startIndex..<text.endIndex
let attributedString = NSMutableAttributedString(string: text)

text.enumerateSubstringsInRange(textRange, options: NSStringEnumerationOptions.ByWords, { (substring, substringRange, enclosingRange, stop) -> () in
    let start = distance(text.startIndex, substringRange.startIndex)
    let length = distance(substringRange.startIndex, substringRange.endIndex)
    let range = NSMakeRange(start, length)

//    println("word: \(substring) - \(d1) to \(d2)")

        if (substring == "saying") {
            attributedString.addAttribute(NSForegroundColorAttributeName, value: NSColor.redColor(), range: range)
        }
})

Swift 5解决方案

将Range转换为NSRange

由于'encodedOffset'已弃用,所以现在为了转换String。Index to Int我们需要Range< string的原始字符串的引用。推导出索引>。

一个方便的详细扩展NSRange可以如下所示:

extension NSRange {

    public init(range: Range<String.Index>, 
                originalText: String) {

        let range_LowerBound_INDEX = range.lowerBound
        let range_UpperBound_INDEX = range.upperBound

        let range_LowerBound_INT = range_LowerBound_INDEX.utf16Offset(in: originalText)
        let range_UpperBound_INT = range_UpperBound_INDEX.utf16Offset(in: originalText)

        let locationTemp = range_LowerBound_INT
        let lengthTemp = range_UpperBound_INT - range_LowerBound_INT

        self.init(location: locationTemp,
                  length: lengthTemp)
    }
}

而简写扩展如下所示

extension NSRange {

    public init(range: Range<String.Index>, 
                originalText: String) {

        self.init(location: range.lowerBound.utf16Offset(in: originalText),
                  length: range.upperBound.utf16Offset(in: originalText) - range.lowerBound.utf16Offset(in: originalText))
    }
}

现在我们可以使用任何Range将其转换为NSRange,如下所示,分享我自己的需求,这让我写了上面的扩展

我使用下面的字符串扩展从字符串中找到特定单词的所有范围

extension String {
        
    func ranges(of substring: String, options: CompareOptions = [], locale: Locale? = nil) -> [Range<Index>] {
        var ranges: [Range<Index>] = []
        while let range = range(of: substring, options: options, range: (ranges.last?.upperBound ?? self.startIndex)..<self.endIndex, locale: locale) {
            ranges.append(range)
        }
        return ranges
    }
}

我的要求是改变字符串中特定单词的颜色,因此我写了这个扩展,它做的工作

extension NSAttributedString {

    static func colored(originalText:String,
                        wordToColor:String,
                        currentColor:UIColor,
                        differentColor:UIColor) -> NSAttributedString {
        
        let attr = NSMutableAttributedString(string: originalText)
        
        attr.beginEditing()
        
        attr.addAttribute(NSAttributedString.Key.foregroundColor,
                          value: currentColor,
                          range: NSRange(location: 0, length: originalText.count))
        
        // FOR COVERING ALL THE OCCURENCES
        for eachRange in originalText.ranges(of: wordToColor) {
            attr.addAttribute(NSAttributedString.Key.foregroundColor,
                              value: differentColor,
                              range: NSRange(range: eachRange, originalText: originalText))
        }
        
        attr.endEditing()
        
        return attr
    }

}

最后,我使用它从我的主要代码如下

let text = "Collected".localized() + "  +  " + "Cancelled".localized() + "  +  " + "Pending".localized()
myLabel.attributedText = NSAttributedString.colored(originalText: text,
                                                    wordToColor: "+",
                                                    currentColor: UIColor.purple,
                                                    differentColor: UIColor.blue)

结果如下所示,将+符号的颜色从主文本的紫色改为蓝色。

希望这能帮助到需要帮助的人。谢谢!

答案很好,但是使用Swift 4你可以简化你的代码:

let text = "Test string"
let substring = "string"

let substringRange = text.range(of: substring)!
let nsRange = NSRange(substringRange, in: text)

小心,因为range函数必须被展开。

斯威夫特4

我认为有两种方法。

1. NSRange(范围,在:)

2. NSRange(location:, length:)

示例代码:

let attributedString = NSMutableAttributedString(string: "Sample Text 12345", attributes: [.font : UIFont.systemFont(ofSize: 15.0)])

// NSRange(range, in: )
if let range = attributedString.string.range(of: "Sample")  {
    attributedString.addAttribute(.foregroundColor, value: UIColor.orange, range: NSRange(range, in: attributedString.string))
}

// NSRange(location: , length: )
if let range = attributedString.string.range(of: "12345") {
    attributedString.addAttribute(.foregroundColor, value: UIColor.green, range: NSRange(location: range.lowerBound.encodedOffset, length: range.upperBound.encodedOffset - range.lowerBound.encodedOffset))
}

屏幕截图:

我的解决方案是一个字符串扩展,首先获得快速范围,然后获得从字符串的开始到子字符串的开始和结束的距离。

然后使用这些值计算子字符串的开始和长度。然后我们可以将这些值应用到NSMakeRange构造函数。

这个解决方案适用于由多个单词组成的子字符串,这里使用enumerateSubstrings的许多解决方案让我失望。

extension String {

    func NSRange(of substring: String) -> NSRange? {
        // Get the swift range 
        guard let range = range(of: substring) else { return nil }

        // Get the distance to the start of the substring
        let start = distance(from: startIndex, to: range.lowerBound) as Int
        //Get the distance to the end of the substring
        let end = distance(from: startIndex, to: range.upperBound) as Int

        //length = endOfSubstring - startOfSubstring
        //start = startOfSubstring
        return NSMakeRange(start, end - start)
    }

}