Как я могу преобразовать NSRange
в Range<String.Index>
в Swift?
Я хочу использовать следующий UITextFieldDelegate
метод:
func textField(textField: UITextField!,
shouldChangeCharactersInRange range: NSRange,
replacementString string: String!) -> Bool {
textField.text.stringByReplacingCharactersInRange(???, withString: string)
Ответы:
NSString
Версия (в отличие от Swift струны)replacingCharacters(in: NSRange, with: NSString)
принимаетNSRange
, поэтому один простое решение , чтобы преобразоватьString
вNSString
первый . Имена методов делегата и замены немного отличаются в Swift 3 и 2, поэтому в зависимости от того, какой Swift вы используете:Swift 3.0
Swift 2.x
источник
textField
содержит юникод-символы с несколькими кодами, такие как эмодзи. Поскольку это поле ввода, пользователь вполне может ввести смайлики (😂), другие символы страницы 1, состоящие из символов нескольких кодовых единиц, такие как флаги (🇪🇸).UITextFieldDelegate
«stextField:shouldChangeCharactersInRange:replacementString:
метод не обеспечивает диапазон , который начинается или заканчивается внутри юникода характер, так как обратный вызов на основе ввода пользователем символов с клавиатуры, и это невозможно напечатать только часть эмодзи юникод персонажа. Обратите внимание, что если бы делегат был написан на Obj-C, у него возникла бы та же проблема.(textField.text as NSString?)?.stringByReplacingCharactersInRange(range, withString: string)
Начиная с Swift 4 (Xcode 9), стандартная библиотека Swift предоставляет методы для преобразования между строковыми диапазонами Swift (
Range<String.Index>
) иNSString
range (NSRange
). Пример:Поэтому замена текста в методе делегата текстового поля теперь может быть выполнена как
(Старые ответы для Swift 3 и ранее :)
Начиная с Swift 1.2,
String.Index
имеет инициализаторкоторый может быть использован для преобразования ,
NSRange
чтобыRange<String.Index>
правильно ( в том числе всех случаев Emojis, региональные индикаторы или других расширенных кластеров графемов) без промежуточного преобразования вNSString
:Этот метод возвращает необязательный диапазон строк, потому что не все
NSRange
s действительны для данной строки Swift.Затем
UITextFieldDelegate
метод делегата может быть записан какОбратное преобразование
Простой тест:
Обновление для Swift 2:
Версия Swift 2
rangeFromNSRange()
уже была предоставлена Сергеем Яковенко в этом ответе , я включаю его здесь для полноты:Версия Swift 2
NSRangeFromRange()
являетсяОбновление для Swift 3 (Xcode 8):
Пример:
источник
range
,(0,2)
потому чтоNSRange
ссылается на символы UTF-16 вNSString
. Но он считается одним символом Юникода в строке Swift. -let end = advance(start, range.length)
из указанного ответа происходит сбой в этом случае с сообщением об ошибке «Неустранимая ошибка: невозможно увеличить endIndex».String
чтобы перейти к справочнику API, прочитать все методы и комментарии, а затем попробовать разные вещи, пока он не заработает :)let from16 = utf16.index(utf16.startIndex, offsetBy: nsRange.location, limitedBy: utf16.endIndex)
, я думаю , что вы на самом деле хотите , чтобы ограничить егоutf16.endIndex - 1
. В противном случае вы можете начать с конца строки.Вы должны использовать
Range<String.Index>
вместо классическогоNSRange
. Способ, которым я делаю это (возможно, есть лучший способ), заключается в том, чтобы взять строку сString.Index
собойadvance
.Я не знаю, какой диапазон вы пытаетесь заменить, но давайте представим, что вы хотите заменить первые 2 символа.
источник
Этот ответ Мартина Р, кажется, правильный, потому что он учитывает Юникод.
Однако на момент публикации (Swift 1) его код не компилируется в Swift 2.0 (Xcode 7), потому что они удалили
advance()
функцию. Обновленная версия ниже:Swift 2
Свифт 3
Swift 4
источник
Это похоже на ответ EMILIE в однако , так как вы специально просили , как преобразовать
NSRange
кRange<String.Index>
вам будет делать что - то вроде этого:источник
NSRange
говорит, хотя ее компоненты являются целыми числами) в представлении символов строки, что может привести к сбою при использовании строки со «символами», для выражения которой требуется более одного модуля UTF16.Риф на отличный ответ @Emilie, а не замена / конкурирующий ответ.
(Xcode6-Beta5)
Вывод:
Обратите внимание, что индексы учитывают несколько единиц кода. Флаг (РЕГИОНАЛЬНЫЕ ИНДИКАТОРНЫЕ СИМВОЛНЫЕ БУКВЫ ES) имеет длину 8 байтов, а (ЛИЦО С ОЗНАКОМ РАДОСТИ) - 4 байта. (В этом конкретном случае оказывается, что количество байтов одинаково для представлений UTF-8, UTF-16 и UTF-32.)
Обернуть это в функцию:
Вывод:
источник
источник
В Swift 2.0 предполагается
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
:источник
Вот мои лучшие усилия. Но это не может проверить или обнаружить неправильный входной аргумент.
Результат.
подробности
NSRange
изNSString
отсчетов кодовой единицы UTF-16 с. АRange<String.Index>
от SwiftString
есть непрозрачный относительный тип, который обеспечивает только операции равенства и навигации. Это намеренно скрытый дизайн.Хотя
Range<String.Index>
кажется, что он сопоставлен со смещением кодовых единиц UTF-16, это всего лишь деталь реализации, и я не смог найти упоминаний о какой-либо гарантии. Это означает, что детали реализации могут быть изменены в любое время. Внутреннее представление SwiftString
не совсем определено, и я не могу на это полагаться.NSRange
значения могут быть напрямую сопоставлены сString.UTF16View
индексами. Но нет способа конвертировать его вString.Index
.Swift
String.Index
- это индекс для итерации Swift,Character
который представляет собой кластер графем Unicode . Затем вы должны предоставить правильный,NSRange
который выбирает правильные кластеры графемы. Если вы укажете неправильный диапазон, как в примере выше, это приведет к неправильному результату, потому что невозможно определить правильный диапазон кластера графем.Если есть гарантия , что
String.Index
это смещение UTF-16 код-модуль, то проблема становится простой. Но это вряд ли произойдет.Обратное преобразование
В любом случае обратное преобразование может быть сделано точно.
Результат.
источник
return NSRange(location: a.utf16Count, length: b.utf16Count)
должно быть изменено наreturn NSRange(location: a.utf16.count, length: b.utf16.count)
Я нашел самое чистое решение swift2 - создать категорию на NSRange:
А затем вызовите его из функции делегата текстового поля:
источник
Официальная документация Swift 3.0 beta предоставила стандартное решение для этой ситуации под заголовком String.UTF16View в разделе UTF16View. Элементы соответствуют NSString. Символы title
источник
В принятом ответе я считаю необязательные варианты. Это работает со Swift 3 и, похоже, не имеет проблем с Emojis.
источник
источник