64

モンゴル語の​​詳細には興味がなく、Swift での Unicode 値の使用と変換に関する簡単な回答が必要な場合は、受け入れられた回答の最初の部分までスキップしてください。


バックグラウンド

iOS アプリで使用する伝統的なモンゴル語の​​ Unicode テキストをレンダリングしたいと考えています。より優れた長期的な解決策は、この複雑なスクリプトをレンダリングするAAT スマート フォントを使用することです。(そのようなフォントは存在しますが、それらのライセンスは変更や非個人的な使用を許可していません。)しかし、私はフォントを作成したことがないので、AAT フォントのすべてのレンダリング ロジックは言うまでもなく、自分でレンダリングを行う予定です。とりあえずスイフト。後日、スマートフォントの作り方を学べるかもしれません。

外部的には Unicode テキストを使用しますが、内部的に ( で表示するためにUITextView) Unicode を個々のグリフに変換し、ダム フォント (Unicode PUA値でコード化) に保存します。したがって、私のレンダリング エンジンは、モンゴル語の​​ Unicode 値 (範囲: U+1820 から U+1842) を PUA に格納されているグリフ値 (範囲: U+E360 から U+E5CF) に変換する必要があります。とにかく、これは私が過去に Java で行ったことなので、私の計画ですが、考え方全体を変える必要があるかもしれません。

次の画像は、文字u (赤)の 2 つの異なる形式を使用して、モンゴル語で 2 回書かれたsuを示しています。(モンゴル語は英語の草書体のように文字が縦につながって書かれています。)

ここに画像の説明を入力

Unicode では、これら 2 つの文字列は次のように表されます。

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

Free Variation Selector (U+180B) はsuForm2、Swift によって (正しく) u (U+1826) が先行するStringユニットとして認識されます。Swift では、拡張された書記素クラスターである単一の文字と見なされます。ただし、レンダリングを自分で行うために、u (U+1826) と FVS1 (U+180B) を 2 つの異なる UTF-16 コード ポイントとして区別する必要があります。

内部表示のために、上記の Unicode 文字列を次のレンダリングされたグリフ文字列に変換します。

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

質問

私はSwiftStringCharacter. それらには便利なことがたくさんありますが、私の特定のケースでは、UTF-16 コード単位のみを扱っているため、NSStringSwift のString. String.utf16を使用して UTF-16 コード ポイントを取得できることはわかっていますが、 への変換Stringはあまりうまくいきません。

Stringandに固執する方が良いでしょうか、それともandCharacterを使用する必要がありますか?NSStringunichar

私が読んだこと

この質問への更新は、ページを整理するために非表示にされました。編集履歴を参照してください。

4

2 に答える 2

87

Swift 3 用に更新

文字列と文字

この質問にアクセスする将来のほぼすべての人にとって、StringそれCharacterがあなたの答えになります。

コードで Unicode 値を直接設定します。

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

16 進数を使用して値を設定する

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

Swift Character は複数の Unicode コード ポイントで構成できますが、単一の文字のように見えることに注意してください。これは、拡張書記素クラスターと呼ばれます。

この質問も参照してください。

Unicode 値に変換します。

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

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

Unicode 16 進値からの変換:

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

または、次のようにします。

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

さらにいくつかの例

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‼

UTF-8 と UTF-16 の場合、変換は必ずしも簡単ではないことに注意してください。( UTF-8UTF-16、およびUTF-32 の質問を参照してください。)

NSString と unichar

Swift で作業することも可能ですが、Objective C に精通してNSStringおりunichar、構文を Swift に変換するのが得意でない限り、適切なドキュメントを見つけるのは難しいことを認識してください。

また、unicharUInt16配列であり、前述のようにUInt16から Unicode スカラー値への変換は必ずしも容易ではありません (つまり、上位コード プレーンの絵文字やその他の文字などのサロゲート ペアを変換する)。

カスタム文字列構造

質問に記載されている理由により、上記の方法のいずれも使用しなくなりました。代わりに、基本的にUInt32Unicode スカラー値を保持するための配列である独自の文字列構造を作成しました。

繰り返しますが、これはほとんどの人にとって解決策ではありません。の機能を少しだけ拡張する必要がある場合は、まず拡張機能の使用を検討してください。StringCharacter

しかし、本当に Unicode スカラー値のみを扱う必要がある場合は、カスタム構造体を作成できます。

利点は次のとおりです。

  • 文字列操作を行うときに、タイプ ( StringCharacterUnicodeScalar、など)を常に切り替える必要はありません。UInt32
  • Unicode 操作が終了したら、最終的な への変換Stringは簡単です。
  • 必要に応じて簡単にメソッドを追加できます
  • Java や他の言語からのコード変換を簡素化

欠点は次のとおりです。

  • 他の Swift 開発者にとってコードの移植性と可読性が低下します
  • ネイティブの Swift 型ほど十分にテストおよび最適化されていない
  • これは、必要になるたびにプロジェクトに含める必要があるさらに別のファイルです

自分で作ることもできますが、参考までにこちらを。最も困難な部分は、 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
}
于 2015-07-17T19:23:44.803 に答える