¿Cómo puedo cambiar el estilo de las palabras preseleccionadas en mi textView?

Este es un seguimiento de esta pregunta ¿Cómo puedo cambiar el estilo de algunas palabras en mi UITextView una por una en Swift?

Gracias a la ayuda de @ Josh, pude escribir un código que resalta cada palabra que comienza con # , y hacerlo una por una. Mi código final para eso fue:

 func highlight (to index: Int) { let regex = try? NSRegularExpression(pattern: "#(\\w+)", options: []) let matches = regex!.matches(in: hashtagExplanationTextView.text, options: [], range: NSMakeRange(0, (hashtagExplanationTextView.text.characters.count))) let titleDict: NSDictionary = [NSForegroundColorAttributeName: orangeColor] let titleDict2: NSDictionary = [NSForegroundColorAttributeName: UIColor.networking] let stonetworkingAttributedString = NSMutableAttributedString(string: hashtagExplanationTextView.text!, attributes: titleDict as! [String : AnyObject]) let attributedString = NSMutableAttributedString(attributedString: stonetworkingAttributedString) guard index < matches.count else { return } for i in 0..<index{ let matchRange = matches[i].rangeAt(0) attributedString.addAttributes(titleDict2 as! [String : AnyObject], range: matchRange) } hashtagExplanationTextView.attributedText = attributedString if #available(iOS 10.0, *) { let _ = Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { _ in self.highlight(to: index + 1) } } else { DispatchQueue.main.asyncAfter(deadline: .now() + 1) { self.highlight(to: index + 1) } } } 

Esto funciona bien, pero me gustaría cambiar la lógica para que no resalte las palabras # , sino que resalta (una por una) las palabras de la matriz preseleccionada de esas palabras.

Entonces tengo este array var myArray:[String] = ["those","words","are","highlighted"] y cómo puedo ponerlo en lugar de coincidencias de regex en mi código?

Creo que está utilizando expresiones regulares para get una matriz de NSRange . Aquí, necesita una estructura de datos ligeramente diferente como [String : [NSRange]] . Luego puede usar la function rangeOfString para detectar el NSRange donde se encuentra la palabra. Puede seguir el ejemplo que se muestra a continuación para eso:

 let wordMatchArray:[String] = ["those", "words", "are", "highlighted"] let labelText:NSString = NSString(string: "those words, those ldsnvldnvsdnds, are, highlighted,words are highlighted") let textLength:Int = labelText.length var dictionaryForEachWord:[String : [NSRange]] = [:] for eachWord:String in wordMatchArray { var prevRange:NSRange = NSMakeRange(0, 0) var rangeArray:[NSRange] = [] while ((prevRange.location + prevRange.length) < textLength) { let start:Int = (prevRange.location + prevRange.length) let rangeEach:NSRange = labelText.range(of: eachWord, options: NSString.CompareOptions.literal, range: NSMakeRange(start, textLength-start)) if rangeEach.length == 0 { break } rangeArray.append(rangeEach) prevRange = rangeEach } dictionaryForEachWord[eachWord] = rangeArray } 

Ahora que tiene una matriz de NSRange , es decir, [NSRange] para cada palabra almacenada en un dictionary, puede resaltar cada palabra en consecuencia en su UITextView .

Siéntase libre de comentar si tiene alguna duda sobre la implementación 🙂

Para este nuevo requisito no necesita una expresión regular, solo puede repetir su matriz de palabras y usar rangeOfString para averiguar si esa cadena existe y establecer los attributes para el range localizado.

Para que coincida con la funcionalidad original, después de encontrar un range coincidente, debe search nuevamente, comenzando desde el final de ese range, para ver si hay otra coincidencia más adelante en su text fuente.

Las soluciones propuestas hasta ahora sugieren que revise cada palabra y luego la busque en la vista de text. Esto funciona, pero estás atravesando el text demasiadas veces.

Lo que sugiero es enumerar todas las palabras en el text y ver si coinciden con alguna de las palabras para resaltar:

 class ViewController: UIViewController { @IBOutlet var textView: UITextView! override func viewDidLoad() { super.viewDidLoad() textView.delegate = self highlight() } func highlight() { guard let attributedText = textView.attributedText else { return } let wordsToHighlight = ["those", "words", "are", "highlighted"] let text = NSMutableAttributedString(attributedString: attributedText) let textRange = NSRange(location: 0, length: text.length) text.removeAttribute(NSForegroundColorAttributeName, range: textRange) (text.string as NSString).enumerateSubstrings(in: textRange, options: [.byWords]) { [weak textView] (word, range, _, _) in guard let word = word else { return } if wordsToHighlight.contains(word) { textView?.textStorage.setAttributes([NSForegroundColorAttributeName: UIColor.networking], range: range) } else { textView?.textStorage.removeAttribute(NSForegroundColorAttributeName, range: range) } } textView.typingAttributes.removeValue(forKey: NSForegroundColorAttributeName) } } extension ViewController: UITextViewDelegate { func textViewDidChange(_ textView: UITextView) { highlight() } } 

Esto debería estar bien para pequeños texts. Para texts largos, revisar todo en cada cambio puede perjudicar el performance. En ese caso, recomiendo utilizar una subclass personalizada NSTextStorage . Allí tendrá un mejor control sobre qué range de text ha cambiado y aplicar el resaltado solo a esa sección.