web-dev-qa-db-fra.com

Utilisation de points de code Unicode dans Swift

Si vous n'êtes pas intéressé par les détails en mongol mais souhaitez simplement une réponse rapide sur l'utilisation et la conversion des valeurs Unicode dans Swift, passez à la première partie de la réponse acceptée .


Contexte

Je souhaite restituer du texte Unicode pour le mongol traditionnel utilisé dans les applications iOS . La meilleure solution à long terme consiste à utiliser une police de type AAT qui rendrait ce script complexe. ( De telles polices existent mais leur licence ne permet aucune modification ni utilisation non personnelle.) Cependant, comme je n’ai jamais créé de police, et encore moins toute la logique de rendu d’une police AAT, j’ai l’intention de le faire. me rendre à Swift pour le moment. Peut-être que plus tard, je pourrai apprendre à créer une police de caractères intelligente. 

En externe, j'utiliserai du texte Unicode, mais en interne (pour l'affichage dans un UITextView), je convertirai l'Unicode en glyphes individuels stockés dans une police muette (codé avec Unicode PUA values). Mon moteur de rendu doit donc convertir les valeurs Unicode de Mongolie (plage: U + 1820 à U + 1842) en valeurs de glyphes stockées dans le PUA (plage: U + E360 à U + E5CF). Quoi qu’il en soit, c’est mon plan, car c’est ce que j’avais fait à Java dans le passé , mais j’ai peut-être besoin de changer complètement ma façon de penser.

Exemple

L'image suivante montre su écrit deux fois en mongol sous deux formes différentes pour la lettre u (en rouge). (Le mongol est écrit verticalement, les lettres étant reliées comme des lettres cursives en anglais.) 

enter image description here

En Unicode, ces deux chaînes seraient exprimées comme suit: 

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

Le sélecteur de variation libre (U + 180B) dans suForm2 est reconnu (correctement) par Swift String comme une unité avec le u (U + 1826) qui le précède. Swift considère qu'il s'agit d'un caractère unique, d'un cluster de graphèmes étendu. Cependant, pour effectuer le rendu moi-même, je dois différencier u (U + 1826) et FVS1 (U + 180B) en deux points de code UTF-16 distincts.

Pour l'affichage interne, je voudrais convertir les chaînes Unicode ci-dessus en chaînes de glyphes rendus:

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

Question

J'ai joué avec Swift String et Character. Il y a beaucoup de choses commodes à leur sujet, mais comme dans mon cas particulier, je traite exclusivement avec des unités de code UTF-16, je me demande si je devrais utiliser l'ancienne NSString plutôt que la String de Swift. Je me rends compte que je peux utiliser String.utf16 pour obtenir des points de code UTF-16, mais la conversion en String n'est pas très agréable .

Serait-il préférable de rester avec String et Character ou devrais-je utiliser NSString et unichar ?

Ce que j'ai lu

Les mises à jour de cette question ont été masquées afin de nettoyer la page. Voir l'historique d'édition.

44
Suragch

_ {Mis à jour pour Swift 3} _

Chaîne et caractère

Pour presque tout le monde dans le futur qui visite cette question, String ET Character sera la réponse pour vous. 

Définir les valeurs Unicode directement dans le code:

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

Utilisez hexadécimal pour définir les valeurs

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

Notez que le caractère rapide peut être composé de plusieurs points de code Unicode, mais semble être un seul caractère. C'est ce qu'on appelle un cluster de graphèmes étendus.

Voir cette question aussi.

Convertir en valeurs Unicode:

str.utf8
str.utf16
str.unicodeScalars // UTF-32

String(character).utf8
String(character).utf16
String(character).unicodeScalars

Convertir à partir des valeurs hexadécimales 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) // ????

Ou bien:

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

Quelques exemples supplémentaires

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‼????

Notez que pour UTF-8 et UTF-16, la conversion n’est pas toujours aussi facile. (Voir UTF-8 , UTF-16 et UTF-32 questions.)

NSString et unichar

Il est également possible de travailler avec NSString et unichar dans Swift, mais vous devez savoir que si vous n'êtes pas familier avec Objective C et capable de convertir la syntaxe en Swift, il sera difficile de trouver une bonne documentation. 

De plus, unichar est un tableau UInt16 et, comme mentionné ci-dessus, la conversion des valeurs scalaires UInt16 en Unicode n’est pas toujours facile (par exemple, convertir des paires de substitution pour des éléments comme emoji et d’autres caractères dans les plans de code supérieurs).

Structure de chaîne personnalisée

Pour les raisons mentionnées dans la question, je n’ai utilisé aucune des méthodes ci-dessus. Au lieu de cela, j'ai écrit ma propre structure de chaîne, qui était essentiellement un tableau de UInt32 contenant des valeurs scalaires Unicode.

Encore une fois, ce n'est pas la solution pour la plupart des gens. Envisagez d’abord d’utiliser extensions si vous n’avez qu’à étendre légèrement les fonctionnalités de String ou Character

Mais si vous devez vraiment travailler exclusivement avec des valeurs scalaires Unicode, vous pouvez écrire une structure personnalisée. 

Les avantages sont:

  • Il n'est pas nécessaire de basculer constamment entre les types (String, Character, UnicodeScalar, UInt32, etc.) lors de la manipulation de chaînes.
  • Une fois la manipulation Unicode terminée, la conversion finale en String est facile.
  • Facile d'ajouter plus de méthodes quand elles sont nécessaires
  • Simplifie la conversion de code à partir de Java ou d'autres langages

Les inconvénients sont:

  • rend le code moins portable et moins lisible par les autres développeurs Swift
  • pas aussi bien testé et optimisé que les types Swift natifs
  • c'est encore un autre fichier qui doit être inclus dans un projet chaque fois que vous en avez besoin

Vous pouvez faire le vôtre, mais voici le mien pour référence. La partie la plus difficile a été rendant 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
}
56
Suragch
//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
}
0
Marijan V.