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)
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)
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)
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)
}
})
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é.
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)) // "????????????"
}
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
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
}
}
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
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))
}
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
}