ios - NSRange de Swift Range?
macos (11)
Las respuestas están bien, pero con Swift 4 podría simplificar un poco su código:
let text = "Test string"
let substring = "string"
let substringRange = text.range(of: substring)!
let nsRange = NSRange(substringRange, in: text)
Tenga cuidado, ya que el resultado de la función de
range
debe desenvolverse.
Problema: NSAttributedString toma un NSRange mientras uso una cadena Swift que usa 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)
}
})
Produce el siguiente error:
error: ''Rango'' no es convertible a ''NSRange'' atribuidoString.addAttribute (NSForegroundColorAttributeName, valor: NSColor.redColor (), rango: substringRange)
Me encanta el lenguaje Swift, pero usar
NSAttributedString
con un Swift
Range
que no es compatible con
NSRange
ha dolido la cabeza durante demasiado tiempo.
Entonces, para evitar toda esa basura,
NSMutableAttributedString
los siguientes métodos para devolver una
NSMutableAttributedString
con las palabras resaltadas establecidas con su color.
Esto no funciona para emojis. Modifique si debe hacerlo.
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
}
}
Uso:
// 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
Para casos como el que describiste, encontré que esto funciona. Es relativamente corto y dulce:
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)
Para mí esto funciona perfectamente:
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
Swift 4
Creo que hay dos formas.
1. NSRange (rango, en:)
2. NSRange (ubicación :, longitud:)
Código de muestra:
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))
}
String
rangos de
String
rápida y los rangos de
NSString
no son "compatibles".
Por ejemplo, un emoji como π cuenta como un personaje Swift, pero como dos caracteres
NSString
(un par sustituto llamado UTF-16).
Por lo tanto, su solución sugerida producirá resultados inesperados si la cadena contiene dichos caracteres. Ejemplo:
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)
Salida:
πππLong paragra{ }ph say{ NSColor = "NSCalibratedRGBColorSpace 1 0 0 1"; }ing!{ }
Como puede ver, "ph say" ha sido marcado con el atributo, no "diciendo".
Dado que
NS(Mutable)AttributedString
finalmente requiere un
NSString
y un
NSRange
, en realidad es mejor convertir primero la cadena dada a
NSString
.
Entonces
substringRange
es un
NSRange
y ya no tiene que convertir los rangos:
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)
Salida:
πππLong paragraph { }saying{ NSColor = "NSCalibratedRGBColorSpace 1 0 0 1"; }!{ }
Actualización para 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)
Actualización para 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)
Actualización para Swift 4:
A partir de Swift 4 (Xcode 9), la biblioteca estándar de Swift proporciona un método para convertir entre
Range<String.Index>
y
NSRange
.
La conversión a
NSString
ya no es necesaria:
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)
Aquí
substringRange
es un
Range<String.Index>
, y que se convierte en el
NSRange
correspondiente con
NSRange(substringRange, in: text)
Solución posible
Swift proporciona la distancia () que mide la distancia entre el inicio y el final que se puede usar para crear 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)
}
})
Swift 4:
Claro, sé que Swift 4 ya tiene una extensión para NSRange
public init<R, S>(_ region: R, in target: S) where R : RangeExpression,
S : StringProtocol,
R.Bound == String.Index, S.Index == String.Index
Sé que en la mayoría de los casos este init es suficiente. Ver su uso:
let string = "Many animals here: πΆπ¦π± !!!"
if let range = string.range(of: "πΆπ¦π±"){
print((string as NSString).substring(with: NSRange(range, in: string))) // "πΆπ¦π±"
}
Pero la conversión se puede hacer directamente desde Range <String.Index> a NSRange sin la instancia de Swift String.
En lugar del uso de inicio genérico que requiere de usted el parámetro de destino como Cadena y si no tiene una cadena de destino a mano, puede crear la conversión directamente
extension NSRange {
public init(_ range:Range<String.Index>) {
self.init(location: range.lowerBound.encodedOffset,
length: range.upperBound.encodedOffset -
range.lowerBound.encodedOffset) }
}
o puede crear la extensión especializada para Range
extension Range where Bound == String.Index {
var nsRange:NSRange {
return NSRange(location: self.lowerBound.encodedOffset,
length: self.upperBound.encodedOffset -
self.lowerBound.encodedOffset)
}
}
Uso:
let string = "Many animals here: πΆπ¦π± !!!"
if let range = string.range(of: "πΆπ¦π±"){
print((string as NSString).substring(with: NSRange(range))) // "πΆπ¦π±"
}
o
if let nsrange = string.range(of: "πΆπ¦π±")?.nsRange{
print((string as NSString).substring(with: nsrange)) // "πΆπ¦π±"
}
Swift 5:
Debido a la migración de las cadenas Swift a la codificación UTF-8 de manera predeterminada, el uso de
encodedOffset
se considera obsoleto y Range no se puede convertir a NSRange sin una instancia de String, porque para calcular el desplazamiento necesitamos la cadena fuente que está codificado en UTF-8 y debe convertirse a UTF-16 antes de calcular el desplazamiento.
Entonces, el mejor enfoque, por ahora, es usar
init
genérico.
Variante de extensión Swift 3 que conserva los atributos existentes.
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
}
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)