Swift Range的NSRange?


175

问题:我在使用使用Range的Swift String时,NSAttributedString需要一个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

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

产生以下错误:

错误:“范围”不可转换为“ NSRange” attributedString.addAttribute(NSForegroundColorAttributeName,值:NSColor.redColor(),范围:substringRange)



2
@Suhaib则相反。
geoff

Answers:


262

快速String范围和NSString范围不是“兼容的”。例如,像😄这样的表情符号算作一个Swift字符,但算作两个NSString 字符(所谓的UTF-16代理对)。

因此,如果字符串包含此类字符,则建议的解决方案将产生意外结果。例:

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)

输出:

para长paragra {
} ph说{
    NSColor =“ NSCalibratedRGBColorSpace 1 0 0 1”;
} ing!{
}

如您所见,“ ph say”已被标记为属性,而不是“ saying”。

由于NS(Mutable)AttributedString最终需要使用NSStringand和an NSRange,因此最好将给定的字符串转换为NSStringfirst。然后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)

输出:

paragraph长款{
}说{
    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>和之间进行转换的方法NSRangeNSString不再需要转换为:

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)

74
对于想在OSX上键入表情符号字符的任何人-Control-Command-空格键弹出一个字符选择器
Jay

2
如果我要匹配多个单词,这将不起作用,而且我不确定要匹配的整个字符串是什么。假设我要从API返回一个字符串,并在另一个字符串中使用它,并且希望对API的字符串加下划线,那么我不能保证子字符串不会同时出现在API的字符串和另一个字符串中串!有任何想法吗?
simonthumper 2015年

NSMakeRange更改了str.substringWithRange(Range <String.Index>(开始:str.startIndex,结束:str.endIndex))//“你好,操场”这更改
HariKrishnan.P

(或)强制转换字符串--- let substring =(字符串为NSString).substringWithRange(NSMakeRange(start,length))
HariKrishnan.P

2
你提到Range<String.Index>NSString不兼容。他们的同行也不兼容吗?即NSRangeString不兼容?原因是Apple的API之一专门将两者结合在一起:match(in:options:range :)
有意义的2007年

56

对于您所描述的情况,我发现它可以工作。它相对较短且甜美:

 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)

11
attributedString.addAttribute不会迅速范围内工作
Paludis

7
@Paludis,您是正确的,但是此解决方案未尝试使用Swift范围。它正在使用NSRangestrNSString,因此str.RangeOfString()返回NSRange
tjpaul

3
您还可以通过将第2行和第3行替换为以下内容来删除第2行中的重复字符串:let str = attributedString.string as NSString
Jason Moore

2
这是本地化的噩梦。
苏珊(Sulthan)

29

答案很好,但是使用Swift 4,您可以稍微简化代码:

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

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

注意,range必须解开函数的结果。


10

可能的解决方案

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

2
注意:如果在字符串中使用表情符号之类的字符,则可能会中断-请参阅Martin的回复。
2014年

7

对我来说,这很完美:

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

5

斯威夫特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 <String.Index>到NSRange进行转换,而无需Swift的String实例。

不需要一般的init用法,这需要您将目标参数指定为String,并且如果手头没有目标字符串,则可以直接创建转换

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。因此,目前最好的方法是使用通用的init


的使用encodedOffset认为是有害的,将被弃用
马丁R

3

斯威夫特4

我认为有两种方法。

1. NSRange(范围:)

2. NSRange(位置:,长度:)

样例代码:

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

屏幕截图: 在此处输入图片说明


的使用encodedOffset认为是有害的,将被弃用
马丁R

1

保留现有属性的Swift 3 Extension Variant

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

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

0

我喜欢Swift语言,但是NSAttributedStringRange不兼容的Swift 一起使用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

-3
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)

6
如何稍微解释一下您的答案,并且最好正确地格式化代码?
SamB
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.