【问题标题】:NSRange from Swift Range?NSRange 来自 Swift Range?
【发布时间】:2015-01-18 09:25:20
【问题描述】:

问题: NSAttributedString 需要一个 NSRange,而我使用的是使用 Range 的 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)
    }
})

产生以下错误:

错误:“范围”不能转换为“NSRange” attributesString.addAttribute(NSForegroundColorAttributeName, value: NSColor.redColor(), range: substringRange)

【问题讨论】:

标签: ios macos swift nsrange


【解决方案1】:

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

输出:

???长段落{ }ph 说{ NSColor = "NSCalibratedRGBColorSpace 1 0 0 1"; }ing!{ }

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

由于NS(Mutable)AttributedString 最终需要一个NSString 和一个NSRange,它实际上是 最好先将给定的字符串转换为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)

输出:

???长段{ }说{ 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&lt;String.Index&gt;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&lt;String.Index&gt;,它被转换为 对应NSRange

NSRange(substringRange, in: text)

【讨论】:

  • 对于任何想在 OSX 上输入表情符号的人 - Control-Command-空格键会弹出一个字符选择器
  • 如果我匹配多个单词,这不起作用,而且我不确定要匹配的整个字符串是什么。假设我从 API 获取一个字符串并在另一个字符串中使用它,并且我希望 API 中的字符串带有下划线,我不能保证子字符串不会同时出现在 API 和其他字符串中细绳!有什么想法吗?
  • NSMakeRange Changed str.substringWithRange(Range(start: str.startIndex, end: str.endIndex)) //“你好,操场”这个变化
  • (或) 转换字符串 ---let substring = (string as NSString).substringWithRange(NSMakeRange(start, length))
  • 您提到Range&lt;String.Index&gt;NSString 不兼容。他们的同行也不能兼容吗? IE。 NSRangeString 不兼容吗?因为Apple的API之一专门结合了两者:matches(in:options:range:)
【解决方案2】:

对于您所描述的情况,我发现这是可行的。它相对简短而甜美:

 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)

【讨论】:

  • attributedString.addAttribute 不适用于快速范围
  • @Paludis,您是对的,但此解决方案并未尝试使用 Swift 范围。它使用NSRangestrNSString,因此 str.RangeOfString() 返回 NSRange
  • 您还可以删除第 2 行中的重复字符串,方法是将第 2 行和第 3 行替换为:let str = attributedString.string as NSString
  • 这是本地化的噩梦。
【解决方案3】:

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

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

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

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

【讨论】:

    【解决方案4】:

    可能的解决方案

    Swift 提供了 distance() 来测量 start 和 end 之间的距离,可以用来创建一个 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)
            }
    })
    

    【讨论】:

    • 注意:如果在字符串中使用像表情符号这样的字符,这可能会中断 - 请参阅 Martin 的回复。
    【解决方案5】:

    对我来说这很完美:

    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
    

    【讨论】:

      【解决方案6】:

      斯威夫特 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
      

      我知道在大多数情况下这个初始化就足够了。查看其用法:

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

      但是可以直接从 Range 转换到 NSRange,而不需要 Swift 的 String 实例。

      而不是一般的 init 用法,它需要您将 target 参数作为 String 和 如果您手头没有 target 字符串,您可以直接创建转换

      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 的使用是considered harmful,将是deprecated
      • 我已经修复了 encodedOffset 问题,请检查 [stackoverflow.com/a/68468499/2740218]
      • 但是你必须使用看起来像我一开始写的现有扩展的原始文本。我的想法是在没有原文的情况下进行转换。参见:public init(_ region: R, in target: S) where R : RangeExpression, S : StringProtocol, R.Bound == String.Index, S.Index == String.Index
      【解决方案7】:

      斯威夫特 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))
      }
      

      屏幕截图:

      【讨论】:

      【解决方案8】:

      Swift 5 解决方案

      将 Range 转换为 NSRange

      由于 'encodedOffset' 已被弃用,所以现在为了将 String.Index 转换为 Int 我们需要原始字符串的引用Range 的来源。

      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)
      
      

      结果如下,+号的颜色从正文的紫色变为蓝色。

      希望这可以帮助有需要的人。谢谢!

      【讨论】:

      • 这个扩展正是我所需要的;它基本上等同于 Kotlin 字符串扩展 offsetByCodePoints
      • @BryanW.Wagner 很高兴知道您觉得它很有用。
      【解决方案9】:

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

      【讨论】:

        【解决方案10】:
        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
        }
        

        【讨论】:

          【解决方案11】:

          我的解决方案是字符串扩展,首先获取快速范围,然后获取从字符串开头到子字符串开头和结尾的距离。

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

          【讨论】:

            【解决方案12】:

            我喜欢 Swift 语言,但是将 NSAttributedString 与与 NSRange 不兼容的 Swift Range 一起使用让我头疼了太久。因此,为了解决所有这些垃圾问题,我设计了以下方法来返回一个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
            

            【讨论】:

              【解决方案13】:
              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)
              

              【讨论】:

              • 稍微解释一下你的答案如何,最好正确格式化代码?
              猜你喜欢
              • 2014-09-28
              • 2022-06-10
              • 1970-01-01
              • 2018-09-26
              • 1970-01-01
              • 1970-01-01
              • 2017-02-09
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多