NSRange从Swift Range?

问题: 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)

159069 次浏览

可能的解决方案

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 String范围和NSString范围不兼容。 例如,像😄这样的表情符号是一个Swift字符,但却是两个NSString

.字符(所谓的UTF-16代理对) 因此,您建议的解决方案将产生意想不到的结果,如果字符串 包含此类字符。例子:< / p >
let text = "😄😄😄Long paragraph saying!"
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)


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

输出:

😄😄😄Long paragra{
}ph say{
NSColor = "NSCalibratedRGBColorSpace 1 0 0 1";
}ing!{
}

如你所见,“ph say”被标记为属性,而不是“said”。

由于NS(Mutable)AttributedString最终需要NSStringNSRange,因此它实际上是 最好先将给定的字符串转换为NSString。然后substringRangeNSRange,你不必再转换范围:

let text = "😄😄😄Long paragraph saying!"
let nsText = text as NSString
let textRange = NSMakeRange(0, nsText.length)
let attributedString = NSMutableAttributedString(string: nsText)


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


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

输出:

😄😄😄Long paragraph {
}saying{
NSColor = "NSCalibratedRGBColorSpace 1 0 0 1";
}!{
}

Swift 2更新:

let text = "😄😄😄Long paragraph saying!"
let nsText = text as NSString
let textRange = NSMakeRange(0, nsText.length)
let attributedString = NSMutableAttributedString(string: text)


nsText.enumerateSubstringsInRange(textRange, options: .ByWords, usingBlock: {
(substring, substringRange, _, _) in


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

Swift 3更新:

let text = "😄😄😄Long paragraph saying!"
let nsText = text as NSString
let textRange = NSMakeRange(0, nsText.length)
let attributedString = NSMutableAttributedString(string: text)


nsText.enumerateSubstrings(in: textRange, options: .byWords, using: {
(substring, substringRange, _, _) in


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

Swift 4更新:

Swift 4 (Xcode 9), Swift标准库 提供在Range<String.Index>NSRange之间转换的方法。 不再需要转换为NSString:

let text = "😄😄😄Long paragraph saying!"
let attributedString = NSMutableAttributedString(string: text)


text.enumerateSubstrings(in: text.startIndex..<text.endIndex, options: .byWords) {
(substring, substringRange, _, _) in
if substring == "saying" {
attributedString.addAttribute(.foregroundColor, value: NSColor.red,
range: NSRange(substringRange, in: text))
}
}
print(attributedString)

这里substringRange是一个Range<String.Index>,它被转换为 对应NSRange

NSRange(substringRange, in: text)

对于你描述的这种情况,我发现这个方法很有效。它相对简短而甜蜜:

 let attributedString = NSMutableAttributedString(string: "follow the yellow brick road") //can essentially come from a textField.text as well (will need to unwrap though)
let text = "follow the yellow brick road"
let str = NSString(string: text)
let theRange = str.rangeOfString("yellow")
attributedString.addAttribute(NSForegroundColorAttributeName, value: UIColor.yellowColor(), range: theRange)
func formatAttributedStringWithHighlights(text: String, highlightedSubString: String?, formattingAttributes: [String: AnyObject]) -> NSAttributedString {
let mutableString = NSMutableAttributedString(string: text)


let text = text as NSString         // convert to NSString be we need NSRange
if let highlightedSubString = highlightedSubString {
let highlightedSubStringRange = text.rangeOfString(highlightedSubString) // find first occurence
if highlightedSubStringRange.length > 0 {       // check for not found
mutableString.setAttributes(formattingAttributes, range: highlightedSubStringRange)
}
}


return mutableString
}
let text:String = "Hello Friend"


let searchRange:NSRange = NSRange(location:0,length: text.characters.count)


let range:Range`<Int`> = Range`<Int`>.init(start: searchRange.location, end: searchRange.length)

我喜欢Swift语言,但是使用NSAttributedString与Swift Range不兼容的NSRange已经让我的头疼了太长时间。因此,为了解决所有这些问题,我设计了以下方法来返回一个NSMutableAttributedString,其中突出显示的单词设置为您的颜色。

这是工作的表情符号。如果需要,可以修改。

extension String {
func getRanges(of string: String) -> [NSRange] {
var ranges:[NSRange] = []
if contains(string) {
let words = self.components(separatedBy: " ")
var position:Int = 0
for word in words {
if word.lowercased() == string.lowercased() {
let startIndex = position
let endIndex = word.characters.count
let range = NSMakeRange(startIndex, endIndex)
ranges.append(range)
}
position += (word.characters.count + 1) // +1 for space
}
}
return ranges
}
func highlight(_ words: [String], this color: UIColor) -> NSMutableAttributedString {
let attributedString = NSMutableAttributedString(string: self)
for word in words {
let ranges = getRanges(of: word)
for range in ranges {
attributedString.addAttributes([NSForegroundColorAttributeName: color], range: range)
}
}
return attributedString
}
}

用法:

// The strings you're interested in
let string = "The dog ran after the cat"
let words = ["the", "ran"]


// Highlight words and get back attributed string
let attributedString = string.highlight(words, this: .yellow)


// Set attributed string
label.attributedText = attributedString

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

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


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

小心,因为range函数的结果必须被解包装。

斯威夫特4:

当然,我知道Swift 4已经为NSRange提供了扩展

public init<R, S>(_ region: R, in target: S) where R : RangeExpression,
S : StringProtocol,
R.Bound == String.Index, S.Index == String.Index

我知道大多数情况下init就足够了。参见用法:

let string = "Many animals here: 🐶🦇🐱 !!!"


if let range = string.range(of: "🐶🦇🐱"){
print((string as NSString).substring(with: NSRange(range, in: string))) //  "🐶🦇🐱"
}

但是转换可以直接从Range<字符串。索引>到NSRange没有Swift的字符串实例。

而不是通用的初始化使用,这需要你的目标参数为字符串和 如果手头没有目标字符串,可以直接创建转换

extension NSRange {
public init(_ range:Range<String.Index>) {
self.init(location: range.lowerBound.encodedOffset,
length: range.upperBound.encodedOffset -
range.lowerBound.encodedOffset) }
}

或者您可以为Range本身创建专门的扩展

extension Range where Bound == String.Index {
var nsRange:NSRange {
return NSRange(location: self.lowerBound.encodedOffset,
length: self.upperBound.encodedOffset -
self.lowerBound.encodedOffset)
}
}

用法:

let string = "Many animals here: 🐶🦇🐱 !!!"
if let range = string.range(of: "🐶🦇🐱"){
print((string as NSString).substring(with: NSRange(range))) //  "🐶🦇🐱"
}

if let nsrange = string.range(of: "🐶🦇🐱")?.nsRange{
print((string as NSString).substring(with: nsrange)) //  "🐶🦇🐱"
}

斯威夫特5:

由于Swift字符串默认迁移到UTF-8编码,encodedOffset的使用被认为是弃用的,如果没有String本身的实例,Range不能转换为NSRange,因为为了计算偏移量,我们需要以UTF-8编码的源字符串,并且在计算偏移量之前应该将其转换为UTF-16。因此,目前最好的方法是使用泛型初始化

斯威夫特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))
}
< p >屏幕截图: enter image description here < / p >

Swift 3扩展变体保存现有属性。

extension UILabel {
func setLineHeight(lineHeight: CGFloat) {
guard self.text != nil && self.attributedText != nil else { return }
var attributedString = NSMutableAttributedString()


if let attributedText = self.attributedText {
attributedString = NSMutableAttributedString(attributedString: attributedText)
} else if let text = self.text {
attributedString = NSMutableAttributedString(string: text)
}


let style = NSMutableParagraphStyle()
style.lineSpacing = lineHeight
style.alignment = self.textAlignment
let str = NSString(string: attributedString.string)


attributedString.addAttribute(NSParagraphStyleAttributeName,
value: style,
range: str.range(of: str as String))
self.attributedText = attributedString
}
}

对我来说,这非常有效:

let font = UIFont.systemFont(ofSize: 12, weight: .medium)
let text = "text"
let attString = NSMutableAttributedString(string: "exemple text :)")


attString.addAttributes([.font: font], range:(attString.string as NSString).range(of: text))


label.attributedText = attString

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

然后使用这些值计算子字符串的开始和长度。然后我们可以将这些值应用到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)
}


}

Swift 5解决方案

将Range转换为NSRange

由于“encodedOffset”已弃用,所以现在为了将字符串。指数转换为Int,我们需要派生出Range< String.Index>的原始字符串的引用。

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)


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

enter image description here

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