簡體   English   中英

來自 Swift Range 的 NSRange?

[英]NSRange from Swift Range?

問題:當我使用使用 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)
    }
})

產生以下錯誤:

錯誤:'Range' 不能轉換為 'NSRange'attributedString.addAttribute(NSForegroundColorAttributeName, value: NSColor.redColor(), range: substringRange)

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)

輸出:

😄😄😄Long paragra{
}ph say{
    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)

輸出:

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

答案很好,但是使用 Swift 4,您可以稍微簡化一下代碼:

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

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

小心,因為必須解開range函數的結果。

可能的解決方案

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

對我來說,這非常有效:

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

斯威夫特 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用法,它需要您將目標參數作為字符串,如果您手頭沒有目標字符串,您可以直接創建轉換

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被認為是不推薦使用的,並且 Range 在沒有 String 本身的實例的情況下無法轉換為 NSRange,因為為了計算偏移量我們需要源字符串以 UTF-8 編碼,在計算偏移量之前應將其轉換為 UTF-16。 所以目前最好的方法是使用通用的init

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

截屏: 在此處輸入圖片說明

保留現有屬性的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
  }
}
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
}

我喜歡 Swift 語言,但是將NSAttributedString與與NSRange不兼容的 Swift Range一起NSRange讓我的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 范圍,然后獲取從字符串開頭到子字符串開頭和結尾的距離。

然后使用這些值來計算子字符串的開始和長度。 然后我們可以將這些值應用到 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 解決方案

將范圍轉換為 NSRange

由於不推薦使用'encodedOffset' ,所以現在為了將String.Index轉換為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)

結果如下,+號的顏色從紫色的正文顏色變為藍色。

在此處輸入圖片說明

希望這可以幫助有需要的人。 謝謝!

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)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM