it-swarm.com.de

Arbeiten mit Unicode-Codepunkten in Swift

Wenn Sie nicht an den Details der mongolischen Sprache interessiert sind, aber nur eine schnelle Antwort zur Verwendung und Konvertierung von Unicode-Werten in Swift wünschen, dann fahren Sie mit dem ersten Teil der akzeptierten Antwort fort.


Hintergrund

Ich möchte Unicode-Text für traditionelles Mongolisch zur Verwendung in iOS-Apps rendern. Die bessere und langfristigere Lösung besteht in der Verwendung einer AAT-Smart-Schriftart , die dieses komplexe Skript darstellen würde. ( Solche Zeichensätze existieren aber ihre Lizenz erlaubt keine Änderung und nicht-persönlichen Gebrauch.) Da ich jedoch noch nie eine Schriftart erstellt habe, geschweige denn die gesamte Rendering-Logik für eine AAT-Schriftart, plane ich dies nur das Rendern selbst in Swift vorerst. Vielleicht kann ich zu einem späteren Zeitpunkt lernen, eine intelligente Schrift zu erstellen. 

Äußerlich werde ich Unicode-Text verwenden, aber intern (zur Anzeige in einer UITextView) werde ich den Unicode in einzelne Glyphen konvertieren, die in einer blöden Schrift gespeichert sind (kodiert mit Unicode PUA values). Meine Rendering-Engine muss also mongolische Unicode-Werte (Bereich: U + 1820 bis U + 1842) in in der PUA gespeicherte Zeichenwerte (Bereich: U + E360 bis U + E5CF) konvertieren. Wie auch immer, das ist mein Plan, denn es ist was ich in Java in der Vergangenheit gemacht habe , aber vielleicht muss ich meine gesamte Denkweise ändern.

Beispiel

Das folgende Bild zeigt su zweimal in der Mongolischen Sprache, wobei zwei verschiedene Formen für den Buchstaben u (in Rot) verwendet werden. (Mongolisch wird vertikal geschrieben, wobei Buchstaben wie kursive Buchstaben auf Englisch verbunden sind.) 

enter image description here

In Unicode werden diese beiden Zeichenfolgen als ausgedrückt 

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

Der Freie-Variations-Selektor (U + 180B) in suForm2 wird (korrekt) von Swift String als Einheit mit der vorangestellten u (U + 1826) erkannt. Es wird von Swift als einzelnes Zeichen, als erweitertes Graphem-Cluster, betrachtet. Für das Rendern selbst muss ich jedoch u (U + 1826) und FVS1 (U + 180B) als zwei verschiedene UTF-16-Codepunkte unterscheiden.

Für interne Anzeigezwecke würde ich die obigen Unicode-Zeichenfolgen in die folgenden gerenderten Zeichenketten konvertieren:

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

Frage

Ich habe mit Swift String und Character herumgespielt. Es gibt eine Menge praktischer Dinge, aber da ich mich in meinem speziellen Fall ausschließlich mit UTF-16-Code-Einheiten beschäftige, frage ich mich, ob ich die alte NSString anstelle von Swifts String verwenden sollte. Ich stelle fest, dass ich String.utf16 verwenden kann, um UTF-16-Codepunkte zu erhalten, aber die Konvertierung zurück in String ist nicht sehr schön .

Wäre es besser, bei String und Character zu bleiben, oder sollte ich NSString und unichar verwenden?

Was ich gelesen habe

Updates für diese Frage wurden ausgeblendet, um die Seite zu bereinigen. Siehe den Bearbeitungsverlauf.

44
Suragch

Aktualisiert für Swift 3

Zeichenfolge und Zeichen

Für fast jeden zukünftigen Besucher dieser Frage sind String UND Character die Antwort für Sie. 

Setzen Sie Unicode-Werte direkt im Code:

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

Verwenden Sie Hexadezimal zum Setzen von Werten.

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

Beachten Sie, dass das Swift-Zeichen aus mehreren Unicode-Codepunkten bestehen kann, aber ein einziges Zeichen zu sein scheint. Dies wird als Extended Grapheme Cluster bezeichnet.

diese Frage auch.

In Unicode-Werte konvertieren:

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

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

Konvertieren von Unicode-Hex-Werten:

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) // ????

Oder alternativ:

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

Noch ein paar Beispiele

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

Beachten Sie, dass die Konvertierung für UTF-8 und UTF-16 nicht immer so einfach ist. (Siehe UTF-8 , UTF-16 und UTF-32 Fragen.)

NSString und unichar

Es ist auch möglich, mit NSString und unichar in Swift zu arbeiten. Sie sollten jedoch wissen, dass eine gute Dokumentation nur schwer zu finden ist, wenn Sie mit Objective C vertraut sind und die Syntax in Swift umwandeln können. 

unichar ist ein UInt16-Array, und wie oben erwähnt, ist die Konvertierung von UInt16 in Unicode-Skalarwerte nicht immer einfach (d. H. Das Konvertieren von Ersatzpaaren für Dinge wie Emoji und andere Zeichen in den oberen Codeebenen).

Benutzerdefinierte String-Struktur

Aus den in der Frage genannten Gründen habe ich schließlich keine der oben genannten Methoden verwendet. Stattdessen schrieb ich meine eigene String-Struktur, die im Wesentlichen ein Array von UInt32 war, um Unicode-Skalarwerte zu speichern.

Wieder ist dies nicht die Lösung für die meisten Menschen. Erwägen Sie zunächst die Verwendung von extensions , wenn Sie die Funktionalität von String oder Character nur ein wenig erweitern müssen. 

Wenn Sie jedoch wirklich ausschließlich mit Unicode-Skalarwerten arbeiten müssen, können Sie eine benutzerdefinierte Struktur schreiben. 

Die Vorteile sind:

  • Sie müssen nicht ständig zwischen Types (String, Character, UnicodeScalar, UInt32 usw.) wechseln, wenn Sie die Zeichenfolge bearbeiten.
  • Nachdem die Unicode-Bearbeitung abgeschlossen ist, ist die endgültige Konvertierung in String einfach.
  • Einfach weitere Methoden hinzufügen, wenn sie benötigt werden
  • Vereinfacht die Konvertierung von Code aus Java oder anderen Sprachen

Nachteile sind:

  • macht Code weniger portabel und für andere Swift-Entwickler weniger lesbar
  • nicht so gut getestet und optimiert wie die nativen Swift-Typen
  • dies ist eine weitere Datei, die jedes Mal in ein Projekt eingefügt werden muss, wenn Sie es benötigen

Sie können Ihre eigenen machen, aber hier ist meine als Referenz. Der schwierigste Teil war es Hash-fähig zu machen .

// 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.