ios swift string unicode mongolian-vertical-script

ios - Trabajar con puntos de código Unicode en Swift



string mongolian-vertical-script (2)

Si no está interesado en los detalles del mongol pero solo desea una respuesta rápida sobre el uso y la conversión de valores Unicode en Swift, pase a la primera parte de la respuesta aceptada .

Antecedentes

Quiero representar el texto Unicode para que el mongol tradicional se use en aplicaciones iOS . La mejor solución a largo plazo es usar una fuente inteligente AAT que represente este script complejo. ( Tales fuentes existen, pero su licencia no permite modificaciones ni uso no personal). Sin embargo, dado que nunca he hecho una fuente, y mucho menos toda la lógica de representación para una fuente AAT, solo planeo hacer la representación yo mismo en Rápido por ahora. Quizás en una fecha posterior pueda aprender a hacer una fuente inteligente.

Externamente UITextView texto Unicode, pero internamente (para mostrar en un UITextView ) convertiré Unicode a glifos individuales que se almacenan en una fuente tonta (codificada con valores Unicode PUA ). Entonces, mi motor de renderizado necesita convertir los valores Unicode de Mongolia (rango: U + 1820 a U + 1842) en valores de glifos almacenados en el PUA (rango: U + E360 a U + E5CF). De todos modos, este es mi plan, ya que es lo que hice en Java en el pasado , pero tal vez necesito cambiar toda mi forma de pensar.

Ejemplo

La siguiente imagen muestra su escrito dos veces en mongol usando dos formas diferentes para la letra u (en rojo). (El mongol se escribe verticalmente con letras conectadas como letras cursivas en inglés).

En Unicode, estas dos cadenas se expresarían como

var suForm1: String = "/u{1830}/u{1826}" var suForm2: String = "/u{1830}/u{1826}/u{180B}"

El selector de variación libre (U + 180B) en suForm2 es reconocido (correctamente) por Swift String como una unidad con la u (U + 1826) que lo precede. Swift lo considera un solo personaje, un grupo de grafemas extendido. Sin embargo, para hacer el renderizado yo mismo, necesito diferenciar u (U + 1826) y FVS1 (U + 180B) como dos puntos de código UTF-16 distintos.

Para fines de visualización interna, convertiría las cadenas Unicode anteriores a las siguientes cadenas de glifos representadas:

suForm1 = "/u{E46F}/u{E3BA}" suForm2 = "/u{E46F}/u{E3BB}"

Pregunta

He estado jugando con Swift String y Character . Hay muchas cosas convenientes sobre ellos, pero dado que en mi caso particular trato exclusivamente con unidades de código UTF-16, me pregunto si debería usar el viejo NSString lugar de la String de Swift. Me doy cuenta de que puedo usar String.utf16 para obtener puntos de código UTF-16, pero la conversión de nuevo a String no es muy agradable .

¿Sería mejor seguir con String y Character o debería usar NSString y unichar ?

Lo que he leido

Las actualizaciones de esta pregunta se han ocultado para limpiar la página. Ver el historial de edición.


Actualizado para Swift 3

Cadena y Carácter

Para casi todos los que visiten esta pregunta en el futuro, String y Character serán la respuesta para usted.

Establezca los valores Unicode directamente en el código:

var str: String = "I want to visit 北京, Москва, मुंबई, القاهرة, and 서울시. 😊" var character: Character = "🌍"

Use hexadecimal para establecer valores

var str: String = "/u{61}/u{5927}/u{1F34E}/u{3C0}" // a大🍎π var character: Character = "/u{65}/u{301}" // é = "e" + accent mark

Tenga en cuenta que el carácter rápido puede estar compuesto por múltiples puntos de código Unicode, pero parece ser un solo carácter. Esto se llama un grupo de grafemas extendido.

Ver esta pregunta también.

Convertir a valores Unicode:

str.utf8 str.utf16 str.unicodeScalars // UTF-32 String(character).utf8 String(character).utf16 String(character).unicodeScalars

Convertir de valores hexadecimales Unicode:

let hexValue: UInt32 = 0x1F34E // convert hex value to UnicodeScalar guard let scalarValue = UnicodeScalar(hexValue) else { // early exit if hex does not form a valid unicode value return } // convert UnicodeScalar to String let myString = String(scalarValue) // 🍎

O alternativamente:

let hexValue: UInt32 = 0x1F34E if let scalarValue = UnicodeScalar(hexValue) { let myString = String(scalarValue) }

Algunos ejemplos más

let value0: UInt8 = 0x61 let value1: UInt16 = 0x5927 let value2: UInt32 = 0x1F34E let string0 = String(UnicodeScalar(value0)) // a let string1 = String(UnicodeScalar(value1)) // 大 let string2 = String(UnicodeScalar(value2)) // 🍎 // convert hex array to String let myHexArray = [0x43, 0x61, 0x74, 0x203C, 0x1F431] // an Int array var myString = "" for hexValue in myHexArray { myString.append(UnicodeScalar(hexValue)) } print(myString) // Cat‼🐱

Tenga en cuenta que para UTF-8 y UTF-16 la conversión no siempre es tan fácil. (Véanse las preguntas UTF-8 , UTF-16 y UTF-32 ).

NSString y unichar

También es posible trabajar con NSString y unichar en Swift, pero debe darse cuenta de que a menos que esté familiarizado con el Objetivo C y sea bueno para convertir la sintaxis a Swift, será difícil encontrar una buena documentación.

Además, unichar es una matriz UInt16 y, como se mencionó anteriormente, la conversión de valores escalares UInt16 a Unicode no siempre es fácil (es decir, convertir pares sustitutos para cosas como emoji y otros caracteres en los planos de código superiores).

Estructura de cadena personalizada

Por las razones mencionadas en la pregunta, terminé sin usar ninguno de los métodos anteriores. En cambio, escribí mi propia estructura de cadena, que era básicamente una matriz de UInt32 para contener valores escalares Unicode.

Nuevamente, esta no es la solución para la mayoría de las personas. Primero considere usar extensions si solo necesita extender un poco la funcionalidad de String o Character .

Pero si realmente necesita trabajar exclusivamente con valores escalares Unicode, puede escribir una estructura personalizada.

Las ventajas son:

  • No es necesario cambiar constantemente entre Tipos ( String , Character , UnicodeScalar , UInt32 , etc.) al manipular cadenas.
  • Una vez finalizada la manipulación Unicode, la conversión final a String es fácil.
  • Fácil de agregar más métodos cuando sean necesarios
  • Simplifica la conversión de código de Java u otros idiomas

Las desventajas son:

  • hace que el código sea menos portátil y menos legible para otros desarrolladores de Swift
  • no tan bien probado y optimizado como los tipos nativos de Swift
  • es otro archivo que debe incluirse en un proyecto cada vez que lo necesite

Puedes hacer el tuyo, pero aquí está el mío como referencia. La parte más difícil fue hacerlo Hashable .

// This struct is an array of UInt32 to hold Unicode scalar values // Version 3.4.0 (Swift 3 update) struct ScalarString: Sequence, Hashable, CustomStringConvertible { fileprivate var scalarArray: [UInt32] = [] init() { // does anything need to go here? } init(_ character: UInt32) { self.scalarArray.append(character) } init(_ charArray: [UInt32]) { for c in charArray { self.scalarArray.append(c) } } init(_ string: String) { for s in string.unicodeScalars { self.scalarArray.append(s.value) } } // Generator in order to conform to SequenceType protocol // (to allow users to iterate as in `for myScalarValue in myScalarString` { ... }) func makeIterator() -> AnyIterator<UInt32> { return AnyIterator(scalarArray.makeIterator()) } // append mutating func append(_ scalar: UInt32) { self.scalarArray.append(scalar) } mutating func append(_ scalarString: ScalarString) { for scalar in scalarString { self.scalarArray.append(scalar) } } mutating func append(_ string: String) { for s in string.unicodeScalars { self.scalarArray.append(s.value) } } // charAt func charAt(_ index: Int) -> UInt32 { return self.scalarArray[index] } // clear mutating func clear() { self.scalarArray.removeAll(keepingCapacity: true) } // contains func contains(_ character: UInt32) -> Bool { for scalar in self.scalarArray { if scalar == character { return true } } return false } // description (to implement Printable protocol) var description: String { return self.toString() } // endsWith func endsWith() -> UInt32? { return self.scalarArray.last } // indexOf // returns first index of scalar string match func indexOf(_ string: ScalarString) -> Int? { if scalarArray.count < string.length { return nil } for i in 0...(scalarArray.count - string.length) { for j in 0..<string.length { if string.charAt(j) != scalarArray[i + j] { break // substring mismatch } if j == string.length - 1 { return i } } } return nil } // insert mutating func insert(_ scalar: UInt32, atIndex index: Int) { self.scalarArray.insert(scalar, at: index) } mutating func insert(_ string: ScalarString, atIndex index: Int) { var newIndex = index for scalar in string { self.scalarArray.insert(scalar, at: newIndex) newIndex += 1 } } mutating func insert(_ string: String, atIndex index: Int) { var newIndex = index for scalar in string.unicodeScalars { self.scalarArray.insert(scalar.value, at: newIndex) newIndex += 1 } } // isEmpty var isEmpty: Bool { return self.scalarArray.count == 0 } // hashValue (to implement Hashable protocol) var hashValue: Int { // DJB Hash Function return self.scalarArray.reduce(5381) { ($0 << 5) &+ $0 &+ Int($1) } } // length var length: Int { return self.scalarArray.count } // remove character mutating func removeCharAt(_ index: Int) { self.scalarArray.remove(at: index) } func removingAllInstancesOfChar(_ character: UInt32) -> ScalarString { var returnString = ScalarString() for scalar in self.scalarArray { if scalar != character { returnString.append(scalar) } } return returnString } func removeRange(_ range: CountableRange<Int>) -> ScalarString? { if range.lowerBound < 0 || range.upperBound > scalarArray.count { return nil } var returnString = ScalarString() for i in 0..<scalarArray.count { if i < range.lowerBound || i >= range.upperBound { returnString.append(scalarArray[i]) } } return returnString } // replace func replace(_ character: UInt32, withChar replacementChar: UInt32) -> ScalarString { var returnString = ScalarString() for scalar in self.scalarArray { if scalar == character { returnString.append(replacementChar) } else { returnString.append(scalar) } } return returnString } func replace(_ character: UInt32, withString replacementString: String) -> ScalarString { var returnString = ScalarString() for scalar in self.scalarArray { if scalar == character { returnString.append(replacementString) } else { returnString.append(scalar) } } return returnString } func replaceRange(_ range: CountableRange<Int>, withString replacementString: ScalarString) -> ScalarString { var returnString = ScalarString() for i in 0..<scalarArray.count { if i < range.lowerBound || i >= range.upperBound { returnString.append(scalarArray[i]) } else if i == range.lowerBound { returnString.append(replacementString) } } return returnString } // set (an alternative to myScalarString = "some string") mutating func set(_ string: String) { self.scalarArray.removeAll(keepingCapacity: false) for s in string.unicodeScalars { self.scalarArray.append(s.value) } } // split func split(atChar splitChar: UInt32) -> [ScalarString] { var partsArray: [ScalarString] = [] if self.scalarArray.count == 0 { return partsArray } var part: ScalarString = ScalarString() for scalar in self.scalarArray { if scalar == splitChar { partsArray.append(part) part = ScalarString() } else { part.append(scalar) } } partsArray.append(part) return partsArray } // startsWith func startsWith() -> UInt32? { return self.scalarArray.first } // substring func substring(_ startIndex: Int) -> ScalarString { // from startIndex to end of string var subArray: ScalarString = ScalarString() for i in startIndex..<self.length { subArray.append(self.scalarArray[i]) } return subArray } func substring(_ startIndex: Int, _ endIndex: Int) -> ScalarString { // (startIndex is inclusive, endIndex is exclusive) var subArray: ScalarString = ScalarString() for i in startIndex..<endIndex { subArray.append(self.scalarArray[i]) } return subArray } // toString func toString() -> String { var string: String = "" for scalar in self.scalarArray { if let validScalor = UnicodeScalar(scalar) { string.append(Character(validScalor)) } } return string } // trim // removes leading and trailing whitespace (space, tab, newline) func trim() -> ScalarString { //var returnString = ScalarString() let space: UInt32 = 0x00000020 let tab: UInt32 = 0x00000009 let newline: UInt32 = 0x0000000A var startIndex = self.scalarArray.count var endIndex = 0 // leading whitespace for i in 0..<self.scalarArray.count { if self.scalarArray[i] != space && self.scalarArray[i] != tab && self.scalarArray[i] != newline { startIndex = i break } } // trailing whitespace for i in stride(from: (self.scalarArray.count - 1), through: 0, by: -1) { if self.scalarArray[i] != space && self.scalarArray[i] != tab && self.scalarArray[i] != newline { endIndex = i + 1 break } } if endIndex <= startIndex { return ScalarString() } return self.substring(startIndex, endIndex) } // values func values() -> [UInt32] { return self.scalarArray } } func ==(left: ScalarString, right: ScalarString) -> Bool { return left.scalarArray == right.scalarArray } func +(left: ScalarString, right: ScalarString) -> ScalarString { var returnString = ScalarString() for scalar in left.values() { returnString.append(scalar) } for scalar in right.values() { returnString.append(scalar) } return returnString }


//Swift 3.0 // This struct is an array of UInt32 to hold Unicode scalar values struct ScalarString: Sequence, Hashable, CustomStringConvertible { private var scalarArray: [UInt32] = [] init() { // does anything need to go here? } init(_ character: UInt32) { self.scalarArray.append(character) } init(_ charArray: [UInt32]) { for c in charArray { self.scalarArray.append(c) } } init(_ string: String) { for s in string.unicodeScalars { self.scalarArray.append(s.value) } } // Generator in order to conform to SequenceType protocol // (to allow users to iterate as in `for myScalarValue in myScalarString` { ... }) //func generate() -> AnyIterator<UInt32> { func makeIterator() -> AnyIterator<UInt32> { let nextIndex = 0 return AnyIterator { if (nextIndex > self.scalarArray.count-1) { return nil } return self.scalarArray[nextIndex + 1] } } // append mutating func append(scalar: UInt32) { self.scalarArray.append(scalar) } mutating func append(scalarString: ScalarString) { for scalar in scalarString { self.scalarArray.append(scalar) } } mutating func append(string: String) { for s in string.unicodeScalars { self.scalarArray.append(s.value) } } // charAt func charAt(index: Int) -> UInt32 { return self.scalarArray[index] } // clear mutating func clear() { self.scalarArray.removeAll(keepingCapacity: true) } // contains func contains(character: UInt32) -> Bool { for scalar in self.scalarArray { if scalar == character { return true } } return false } // description (to implement Printable protocol) var description: String { var string: String = "" for scalar in scalarArray { string.append(String(describing: UnicodeScalar(scalar))) //.append(UnicodeScalar(scalar)!) } return string } // endsWith func endsWith() -> UInt32? { return self.scalarArray.last } // insert mutating func insert(scalar: UInt32, atIndex index: Int) { self.scalarArray.insert(scalar, at: index) } // isEmpty var isEmpty: Bool { get { return self.scalarArray.count == 0 } } // hashValue (to implement Hashable protocol) var hashValue: Int { get { // DJB Hash Function var hash = 5381 for i in 0 ..< scalarArray.count { hash = ((hash << 5) &+ hash) &+ Int(self.scalarArray[i]) } /* for i in 0..< self.scalarArray.count { hash = ((hash << 5) &+ hash) &+ Int(self.scalarArray[i]) } */ return hash } } // length var length: Int { get { return self.scalarArray.count } } // remove character mutating func removeCharAt(index: Int) { self.scalarArray.remove(at: index) } func removingAllInstancesOfChar(character: UInt32) -> ScalarString { var returnString = ScalarString() for scalar in self.scalarArray { if scalar != character { returnString.append(scalar: scalar) //.append(scalar) } } return returnString } // replace func replace(character: UInt32, withChar replacementChar: UInt32) -> ScalarString { var returnString = ScalarString() for scalar in self.scalarArray { if scalar == character { returnString.append(scalar: replacementChar) //.append(replacementChar) } else { returnString.append(scalar: scalar) //.append(scalar) } } return returnString } // func replace(character: UInt32, withString replacementString: String) -> ScalarString { func replace(character: UInt32, withString replacementString: ScalarString) -> ScalarString { var returnString = ScalarString() for scalar in self.scalarArray { if scalar == character { returnString.append(scalarString: replacementString) //.append(replacementString) } else { returnString.append(scalar: scalar) //.append(scalar) } } return returnString } // set (an alternative to myScalarString = "some string") mutating func set(string: String) { self.scalarArray.removeAll(keepingCapacity: false) for s in string.unicodeScalars { self.scalarArray.append(s.value) } } // split func split(atChar splitChar: UInt32) -> [ScalarString] { var partsArray: [ScalarString] = [] var part: ScalarString = ScalarString() for scalar in self.scalarArray { if scalar == splitChar { partsArray.append(part) part = ScalarString() } else { part.append(scalar: scalar) //.append(scalar) } } partsArray.append(part) return partsArray } // startsWith func startsWith() -> UInt32? { return self.scalarArray.first } // substring func substring(startIndex: Int) -> ScalarString { // from startIndex to end of string var subArray: ScalarString = ScalarString() for i in startIndex ..< self.length { subArray.append(scalar: self.scalarArray[i]) //.append(self.scalarArray[i]) } return subArray } func substring(startIndex: Int, _ endIndex: Int) -> ScalarString { // (startIndex is inclusive, endIndex is exclusive) var subArray: ScalarString = ScalarString() for i in startIndex ..< endIndex { subArray.append(scalar: self.scalarArray[i]) //.append(self.scalarArray[i]) } return subArray } // toString func toString() -> String { let string: String = "" for scalar in self.scalarArray { string.appending(String(describing:UnicodeScalar(scalar))) //.append(UnicodeScalar(scalar)!) } return string } // values func values() -> [UInt32] { return self.scalarArray } } func ==(left: ScalarString, right: ScalarString) -> Bool { if left.length != right.length { return false } for i in 0 ..< left.length { if left.charAt(index: i) != right.charAt(index: i) { return false } } return true } func +(left: ScalarString, right: ScalarString) -> ScalarString { var returnString = ScalarString() for scalar in left.values() { returnString.append(scalar: scalar) //.append(scalar) } for scalar in right.values() { returnString.append(scalar: scalar) //.append(scalar) } return returnString }