La subclass NSTextStorage no puede manejar caracteres emoji y cambia la fuente en algunos casos.

Estoy subclassando NSTextStorage para hacer algunos resaltados de enlaces y he leído todo lo que he NSTextStorage sobre el tema . Todo funciona bien hasta que 🏒 caracter 🏒 emoji.

Mi subclass:

 private let ims = NSMutableAttributedString() override var string: String { return ims.string } override func attributesAtIndex(location: Int, effectiveRange range: NSRangePointer) -> [String : AnyObject] { return ims.attributesAtIndex(location, effectiveRange: range) } override func replaceCharactersInRange(range: NSRange, withString str: String) { ims.replaceCharactersInRange(range, withString: str) self.edited(.EditedCharacters, range: range, changeInLength:(str as NSString).length - range.length) } override func setAttributes(attrs: [String : AnyObject]?, range: NSRange) { ims.setAttributes(attrs, range: range) self.edited(.EditedAttributes, range: range, changeInLength: 0) } 

Nada complicado Luego, al ingresar al personaje infame, cambia a Courier New por alguna razón aleatoria:

¡Todo menos mensajero nuevo!

Ahora estoy escogiendo el carácter 🏒 , hay otros que también causan esta locura. He consultado la fuente mientras escribo y va desde Sistema> Apple Emoji> Courier New.

También traté de configurar la fuente desde processEditing() que semi soluciona el problema, hace que se agregue un espacio adicional (aunque no en el simulador). Y estoy codificando un valor == malo.

Pregunta final:

¿Qué estoy haciendo mal? No veo este problema con las implementaciones de otras personas, donde estoy seguro de que los desarrolladores han subclasificado NSTextStorage.

Nota: Puedo confirmar que en la aplicación de demostración de objc.io está presente el mismo problema.

Aquí está la comprensión de mi laico. La mayoría de los emoji solo existen en la fuente AppleColorEmoji de Apple. Cuando escribe un personaje emoji, su NSTextStorage llama a ProcessEditing, que luego llama a fixAttributesInRange . Este método garantiza que los caracteres que falten en su cadena sean reemplazados por fonts que los admitan. Si su cadena contiene emoji, todos los ranges que contienen emoji obtendrán un atributo de fuente AppleColorEmoji.

Desafortunadamente, nada detiene este nuevo atributo de fuente de los caracteres "infectantes" que se escribieron después de él. AppleColorEmoji no parece contener el set ASCII habitual, por lo que esos caracteres posteriores se "arreglan" ellos mismos con una fuente monoespacial.

¿Qué hacer al respecto? En mi progtwig, quiero administrar los attributes de mi almacenamiento de text manualmente, ya que no quiero copyr y pegar el text para agregar nuevos styles a mi text. Esto significa que simplemente puedo hacer esto:

 override func setAttributes(attrs: [String : AnyObject]?, range: NSRange) { if self.isFixingAttributes { self.attributedString.setAttributes(attrs, range: range) self.edited(NSTextStorageEditActions.EditedAttributes, range: range, changeInLength: 0) } } override func fixAttributesInRange(range: NSRange) { self.isFixingAttributes = true super.fixAttributesInRange(range) self.isFixingAttributes = false } override func processEditing() { // not really fixing -- just need to make sure setAttributes follows orders self.isFixingAttributes = true self.setAttributes(nil, range: self.editedRange) self.setAttributes(self.dynamicType.defaultAttributes(), range: self.editedRange) self.isFixingAttributes = false super.processEditing() } 

Cada vez que se escribe un nuevo text, simplemente borro sus attributes (en caso de que alguno de los ranges fijos anteriormente "lo infecte") y los reemplace con los attributes pnetworkingeterminados. Después de eso, super.processEditing() hace lo suyo y corrige cualquier nuevo carácter faltante en ese range (si hay alguno).

Si, por otro lado, desea poder pegar text de estilo en la vista de text, debería poder rastrear sus ranges fijos comparando el antes / después de fixAttributesInRange y, a continuación, impidiendo que esos styles se transfieran a fixAttributesInRange de fixAttributesInRange nuevos. text en processEditing de processEditing .

En realidad resulta que en mi caso solo tuve que cambiar:

 self.edited(.editedCharacters, range: range, changeInLength: str.characters.count-range.length) 

a:

 self.edited(.editedCharacters, range: range, changeInLength: (str as NSString).length-range.length) 

Es lamentable que get la longitud de una cadena no sea lo mismo que un NSString

fixAttributes(in range: NSRange) NO era necesario