2017-02-08 24 views
6

Я окрашиваю некоторые части текста, исходящие из API (думаю, «@mention», как на Twitter), используя NSAttributedString.NSAttributedString и emojis: проблема с позициями и длиной

API дает мне текст и массив объектов, представляющих части текста, которые упоминаются (или ссылки, теги и т. Д.), Которые должны быть окрашены.

Но иногда окраска компенсируется из-за эмозиса.


Например, с этим текстом: "@ericd Некоторые текст @apero"

АНИ дает:

[ { " текст ":" ericd ", " len ": 6, " pos ": 0 }, { "Текст": "Apero", "лен": 6, "поз": 18 } ]

, который я успешно перевести на NSAttributedString с помощью NSRange:

for m in entities.mentions { 
    let r = NSMakeRange(m.pos, m.len) 
    myAttributedString.addAttribute(NSForegroundColorAttributeName, value: someValue, range: r) 
} 

Мы видим, что "pos": 18 верен, здесь начинается «@apero». Цветные части - «@ericd» и «@apero», как и ожидалось.

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

«@ericd Some текст ✌ @apero.»

дает:

[ { "текст": "ericd","лен": 6, "позы": 0 }, { "Текст": "Apero", "лен": 6, "позы": 22 } ]

"pos": 22: автор API утверждает, что это правильно, и я понимаю их точку зрения.

К сожалению, NSAttributedString не согласен, моя окраска от:

enter image description here

последние символы для второго упоминания не окрашиваются (потому что «позиция» слишком коротка из-за смайликов ?).

Как вы уже могли догадаться, я никоим образом не могу изменить способ поведения API, я должен адаптироваться на стороне клиента.

Кроме того ... Я понятия не имею, что делать. Должен ли я попытаться определить, какие эмоции находятся в тексте, и вручную изменить положение упоминаний, когда есть проблематичные эможи? Но каковы были бы критерии для определения того, какой эможи сдвигает позицию, а что нет? И как решить, сколько мне нужно смещения? Может быть, проблема вызвана NSAttributedString?

Я понимаю, что это связано с тем, что размер emojis был составлен по сравнению с их длиной как дискретные символы, но ... ну ... я потерялся (вздох).


Обратите внимание, что я пытался реализовать решение, похожее на this stuff, потому что мой API совместим с этим, но он работал только частично, некоторые Emojis еще ломать индексы:

enter image description here

ответ

9

A Swift String предоставляет различные «виды» по его содержимому. Хороший обзор приведен в "Strings in Swift 2" в Swift Дневник:

  • characters представляет собой набор значений символов, или расширенных графемы кластеров.
  • unicodeScalars представляет собой набор сканирующих значений Unicode.
  • utf8 представляет собой набор кодовых единиц UTF-8.
  • utf16 - это набор кодов UTF-16.

Как выяснилось в ходе обсуждения, pos и len из вашего API являются индексами в скалярах зрения Unicode.

С другой стороны, метод addAttribute()NSMutableAttributedString принимает NSRange, то есть в диапазоне, соответствующем для индексов кодовых точек UTF-16 в NSString.

String предоставляет методы для "перевода" между индексами различных взглядов (сравните NSRange to Range<String.Index>):

let text = "@ericd Some text. ✌ @apero" 
let pos = 22 
let len = 6 

// Compute String.UnicodeScalarView indices for first and last position: 
let from32 = text.unicodeScalars.index(text.unicodeScalars.startIndex, offsetBy: pos) 
let to32 = text.unicodeScalars.index(from32, offsetBy: len) 

// Convert to String.UTF16View indices: 
let from16 = from32.samePosition(in: text.utf16) 
let to16 = to32.samePosition(in: text.utf16) 

// Convert to NSRange by computing the integer distances: 
let nsRange = NSRange(location: text.utf16.distance(from: text.utf16.startIndex, to: from16), 
         length: text.utf16.distance(from: from16, to: to16)) 

Это NSRange является то, что вам нужно для приписаннога строки:

let attrString = NSMutableAttributedString(string: text) 
attrString.addAttribute(NSForegroundColorAttributeName, 
         value: UIColor.red, 
         range: nsRange) 

Обновление для Swift 4 (Xcode 9): В S wift 4, стандартная библиотека предоставляет методы для преобразования между Swift String диапазонов и NSString диапазонов, поэтому расчеты упрощаются

let text = "@ericd Some text. ✌ @apero" 
let pos = 22 
let len = 6 

// Compute String.UnicodeScalarView indices for first and last position: 
let fromIdx = text.unicodeScalars.index(text.unicodeScalars.startIndex, offsetBy: pos) 
let toIdx = text.unicodeScalars.index(fromIdx, offsetBy: len) 

// Compute corresponding NSRange: 
let nsRange = NSRange(fromIdx..<toIdx, in: text) 
+1

Интересно! (диапазон, который я использую, уже является NSRange, но не Swift Range). Мне нужно некоторое время, чтобы изучить это и запустить тесты, посмотреть, как он ведет себя по сравнению с API, который я вызываю.Спасибо, я дам отзывы, когда это возможно. – Moritz

+0

В вашем примере, используя pos 22 и len 6, как и мой API, возвращается для этого же текста, «@apero» окрашивается только после «@» [вместо всего упоминания] (https://www.evernote.com/л/AOznB6IhPzxJrbvva5Jxx24cyFv9MrhSqkI). – Moritz

+0

@EricAya: Но «@» из «@apero» находится в позиции 21, а не 22. Или как вы считаете персонажей? –

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

  • Нет связанных вопросов^_^