web-dev-qa-db-fra.com

NSRange de Swift Range?

Problème: NSAttributedString prend un NSRange pendant que j'utilise une chaîne Swift qui utilise Range

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

Produit l'erreur suivante:

error: 'Range' n'est pas convertible en 'NSRange' attributString.addAttribute (NSForegroundColorAttributeName, valeur: NSColor.redColor (), plage: substringRange)

126
Jay

Les plages String et NSString rapides ne sont pas "compatibles" . Par exemple, un emoji comme ???? compte comme un caractère Swift, mais comme deux caractères NSString.__ (une paire dite de substitution UTF-16).

Par conséquent, votre solution suggérée produira des résultats inattendus si la chaîne Contient de tels caractères. Exemple:

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)

Sortie:

 ??????????? Long paragraphe {
} Ph dire {
 NSColor = "NSCalibratedRGBColorSpace 1 0 0 1"; 
} Ing! {
} 

Comme vous le voyez, "ph say" a été marqué avec l'attribut, pas "en disant".

Puisque NS(Mutable)AttributedString nécessite finalement un NSString et un NSRange, il est en fait préférable de convertir la chaîne donnée en NSString en premier. Alors le substringRange est un NSRange et vous n'avez plus à convertir les plages:

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)

Sortie:

 ???????????? Long paragraphe {
} Disant {
 NSColor = "NSCalibratedRGBColorSpace 1 0 0 1"; ;__.}! {
} 

Mise à jour pour 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)

Mise à jour pour 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)

Mise à jour pour Swift 4:

À partir de Swift 4 (Xcode 9), la bibliothèque standard Swift Fournit une méthode pour convertir entre Range<String.Index> et NSRange. La conversion en NSString n'est plus nécessaire:

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)

Ici, substringRange est un Range<String.Index>, converti en NSRange correspondant avec

NSRange(substringRange, in: text)
212
Martin R

Pour des cas comme celui que vous avez décrit, j'ai trouvé que cela fonctionnait. C'est relativement court et doux:

 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)
45
royherma

Solution possible

Swift fournit une distance () qui mesure la distance entre le début et la fin pouvant être utilisée pour créer un 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)
        }
})
10
Jay

Les réponses sont bonnes, mais avec Swift 4, vous pourriez simplifier un peu votre code:

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

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

Attention, le résultat de la fonction range doit être déroulé.

9
George Maisuradze

Swift 4:

Bien sûr, je sais que Swift 4 a déjà une extension pour NSRange 

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

Je sais que dans la plupart des cas, cette init est suffisante. Voir son utilisation:

let string = "Many animals here: ???????????? !!!"

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

Mais la conversion peut s'effectuer directement de Range <String.Index> en NSRange sans l'instance de Swift String.

Au lieu de l'utilisation générique de init qui requiert de vous le paramètre target sous la forme String et Si vous n'avez pas la chaîne target, vous pouvez créer une conversion directement

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

ou vous pouvez créer une extension spécialisée pour Range elle-même

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

Usage:

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

ou

if let nsrange = string.range(of: "????????????")?.nsRange{
    print((string as NSString).substring(with: nsrange)) //  "????????????"
}
5
Dmitry A.

Pour moi cela fonctionne parfaitement:

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
Breno Vinícios

Variante d’extension Swift 3 qui préserve les attributs existants. 

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

J'aime la langue Swift, mais utiliser NSAttributedString avec une Swift Range incompatible avec NSRange m'a fait mal à la tête depuis trop longtemps. Donc, pour contourner tout ce bazar, j’ai imaginé les méthodes suivantes pour retourner une NSMutableAttributedString avec les mots en surbrillance définis avec votre couleur. 

Ceci fonctionne pas pour les emojis. Modifiez si vous devez.

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

Usage:

// 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
0
Brandon A

Swift 4

Je pense qu'il y a deux manières.

1. NSRange (range, in:)

2. NSRange (location :, longueur:)

Exemple de code:

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

Capture d'écran:  enter image description here

0
Den
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
orkoden