2015-07-07 2 views
36

Если вас не интересуют детали монгольского языка, но вы просто хотите получить быстрый ответ об использовании и преобразовании значений Unicode в Swift, перейдите к первой части accepted answer.Работа с кодовыми точками Unicode в Swift


фон

Я хочу, чтобы сделать текст Unicode для traditional Mongolian to be used in iOS apps. Лучшим и долгосрочным решением является использование AAT smart font, который будет отображать этот сложный скрипт. (Such fonts do exist, но их лицензия не допускает модификации и неличного использования.) Однако, поскольку я никогда не делал шрифта, не говоря уже о всей логике визуализации шрифта AAT, я просто планирую сделать рендеринг в Swift на данный момент , Может быть, в какой-то более поздний срок я могу научиться делать умный шрифт.

Внешне я буду использовать текст в Юникоде, но внутренне (для отображения в UITextView). Я преобразую Unicode в отдельные глифы, которые хранятся в негромном шрифте (кодируются значениями Unicode PUA). Поэтому мой движок рендеринга должен преобразовать значения монгольского Unicode (диапазон: U + 1820 в U + 1842) в значения глифов, хранящиеся в PUA (диапазон: U + E360 до U + E5CF). Во всяком случае, это мой план, так как это what I did in Java in the past, но, возможно, мне нужно изменить весь мой образ мышления.

Пример

Следующее изображение показывает су написано дважды в монгольских с использованием двух различных форм для письма у (в красном). (Монгольский написано вертикально буквы соединены, как скоропись письма на английском языке.)

enter image description here

В Unicode эти две строки будут выражены как

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

Свободный Выбор варианта (U + 180B) в suForm2 распознается (правильно) Swift String как единица с u (U + 1826), которая предшествует ему. Swift считается одним персонажем, расширенным кластером графем. Однако для выполнения рендеринга я должен различать u (U + 1826) и FVS1 (U + 180B) в виде двух различных кодовых точек UTF-16.

Для внутренних целей отображения, я бы преобразовать вышеупомянутые строки Unicode в следующих предоставляемых строки глифов:

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

Вопрос

Я играл вокруг с Swift String и Character. Есть много удобных вещей о них, но поскольку в моем конкретном случае я имею дело исключительно с кодовыми единицами UTF-16, мне интересно, должен ли я использовать старый NSString, а не от Swift. Я понимаю, что могу использовать String.utf16 для получения кодовых точек UTF-16, но the conversion back to String isn't very nice.

Было бы лучше придерживаться String и Character или использовать NSString и unichar?

То, что я прочитал

Обновления на этот вопрос, были скрыты, чтобы очистить страницу вверх. См. Историю изменений.

+1

Ваш вопрос мне не ясна. 'count (string)' дает количество «расширенных кластеров графема unicode», 'count (string.utf16)' дает количество кодовых точек UTF-16, необходимых для одной и той же строки (это длина соответствующей 'NSString' или 'CFString'). (И 'count (string.utf8)' будет указывать количество кодовых точек UTF-8). - Вопрос * «Должен ли я делать что-то вроде suForm1.utf16 каждый раз, когда я ссылаюсь на String?» * Не может быть дан ответ вообще, это зависит от того, для чего вам нужен счет. –

+0

Мне нужно работать исключительно (я думаю) с кодовыми точками UTF-16. Подсчет длины строки (в UTF-16) - это одна вещь, которую мне нужно будет сделать, но это всего лишь пример. Мне также нужно будет делать такие вещи, как сравнение символов (это кодовые точки UTF-16, а не только кластеры grapheme, которые Swift считает равными). Должен ли я использовать 'String' или' NSString' или что-то еще? – Suragch

+0

Что вы подразумеваете под «длиной» ... Bytes? Кодовые единицы? Кодовые пункты? Графемные кластеры? Пиксели? И аналогично для «сравнения» ... Равноправие кодовых точек? Каноническая эквивалентность? Совместимость эквивалентности? –

ответ

48

Обновление для Swift 3

строк и символов

Для почти всех в будущем, кто посещает этот вопрос, String and Character будет ответом для вас.

Установить значение Unicode непосредственно в коде:

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

Используйте шестнадцатеричные, чтобы установить значение

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

Обратите внимание, что Swift символов могут состоять из нескольких точек коды Unicode, но появляется быть единственным персонажем. Это называется расширенным кластером графем.

См. Также this question.

Преобразование к значениям Unicode:

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

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

Преобразование из значений шестнадцатеричных:

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-8, UTF-16 и UTF-32 вопросы.)

NSString и unichar

Также можно работать с NSString и unichar в Swift, но вы должны понимать, что если вы не знакомы с Objective C и хорошо преобразование синтаксиса в Swift, будет сложно найти хорошую документацию.

Кроме того, unichar является UInt16 массива и, как упоминалось выше, преобразование из UInt16 до значений скалярных Unicode не всегда легко (то есть, преобразование суррогатных пар для вещей, как смайлики и другие символы в верхних плоскостей кода).

Пользовательские структура струны

По причинам, указанным в вопросе, я в конечном итоге не используя какой-либо из указанных выше способов. Вместо этого я написал свою собственную строковую структуру, которая была в основном массивом UInt32 для хранения скалярных значений Unicode.

Опять же, это не решение для большинства людей. Сначала рассмотрите использование extensions, если вам нужно только расширить функциональность String или Character.

Но если вам действительно нужно работать исключительно с сканирующими значениями Unicode, вы можете написать собственную структуру.

Преимущества:

  • Не нужно постоянно переключаться между типами (String, Character, UnicodeScalar, UInt32 и т.д.) при выполнении манипуляций со строками.
  • После завершения обработки Юникодом окончательное преобразование в String легко.
  • Легко добавить больше методов, когда они необходимы
  • Упрощает конвертирование код из Java или других языках

Disadavantages являются:

  • делает код менее переносимым и менее читабельным для других Swift разработчиков
  • не так хорошо протестирован и оптимизирован, как родной тип Swift
  • это еще один файл, который должен быть включен в проектировать каждый раз, когда вам это нужно

Вы можете сделать это самостоятельно, но здесь для справки. Самая сложная часть была making it 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 
} 
0
//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

Я думаю, что использование 'String (описывающее: UnicodeScalar (scalar))' производит строки с надписью «Необязательно», написанные в них.См. Обновление для моего ответа для лучшего метода. – Suragch

Смежные вопросы