ios macos swift nsrange

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

Captura de pantalla:


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)