Как работает String.Index в Swift

Я обновлял часть своего старого кода и отвечал с помощью Swift 3, но когда я добрался до Swift Strings и Indexing, было больно понять вещи.

В частности, я пытался сделать следующее:

let str = "Hello, playground"
let prefixRange = str.startIndex..<str.startIndex.advancedBy(5) // error

где вторая строка давала мне следующую ошибку

'advancedBy' недоступен: для продвижения индекса с помощью n шагов вызовите 'index (_: offsetBy:)' в экземпляр CharacterView, который создал индекс.

Я вижу, что String имеет следующие методы.

str.index(after: String.Index)
str.index(before: String.Index)
str.index(String.Index, offsetBy: String.IndexDistance)
str.index(String.Index, offsetBy: String.IndexDistance, limitedBy: String.Index)

Сначала это меня сбивало с толку, поэтому я начал играть с ними, пока не понял их. Я добавляю ответ ниже, чтобы показать, как они используются.

Ответ 1

введите описание изображения здесь

Во всех следующих примерах используйте

var str = "Hello, playground"

startIndex и endIndex

  • startIndex - это индекс первого символа
  • endIndex - это индекс после последнего символа.

Пример

// character
str[str.startIndex] // H
str[str.endIndex]   // error: after last character

// range
let range = str.startIndex..<str.endIndex
str[range]  // "Hello, playground"

С Swift 4 односторонние диапазоны диапазон можно упростить до одной из следующих форм.

let range = str.startIndex...
let range = ..<str.endIndex

Я буду использовать полную форму в следующих примерах для ясности, но для удобства чтения вы, вероятно, захотите использовать односторонние диапазоны в своем коде.

after

Как в: index(after: String.Index)

  • after относится к индексу символа непосредственно после данного индекса.

Примеры

// character
let index = str.index(after: str.startIndex)
str[index]  // "e"

// range
let range = str.index(after: str.startIndex)..<str.endIndex
str[range]  // "ello, playground"

before

Как в: index(before: String.Index)

  • before относится к индексу символа непосредственно перед данным индексом.

Примеры

// character
let index = str.index(before: str.endIndex)
str[index]  // d

// range
let range = str.startIndex..<str.index(before: str.endIndex)
str[range]  // Hello, playgroun

offsetBy

Как в: index(String.Index, offsetBy: String.IndexDistance)

  • Значение offsetBy может быть положительным или отрицательным и начинается с данного индекса. Хотя он имеет тип String.IndexDistance, вы можете дать ему Int.

Примеры

// character
let index = str.index(str.startIndex, offsetBy: 7)
str[index]  // p

// range
let start = str.index(str.startIndex, offsetBy: 7)
let end = str.index(str.endIndex, offsetBy: -6)
let range = start..<end
str[range]  // play

limitedBy

Как в: index(String.Index, offsetBy: String.IndexDistance, limitedBy: String.Index)

  • limitedBy полезен, чтобы убедиться, что смещение не приводит к тому, что индекс выходит за пределы. Это ограничивающий индекс. Так как смещение может превышать предел, этот метод возвращает Необязательный. Он возвращает nil, если индекс выходит за пределы.

Пример

// character
if let index = str.index(str.startIndex, offsetBy: 7, limitedBy: str.endIndex) {
    str[index]  // p
}

Если смещение было 77 вместо 7, то оператор if был бы пропущен.

Зачем нужен String.Index?

Было бы гораздо проще использовать индекс Int для строк. Причина, по которой вам нужно создать новый String.Index для каждой строки, состоит в том, что символы в Swift не имеют одинаковой длины под капотом. Один символ Swift может состоять из одного, двух или даже большего числа кодов Unicode. Таким образом, каждая уникальная строка должна вычислять индексы своих символов.

Возможно, эта сложность скрывается за расширением индекса Int, но я не хочу этого делать. Хорошо помнить о том, что на самом деле происходит.

Ответ 2

Создайте UITextView внутри tableViewController. Я использовал функцию: textViewDidChange, а затем проверил на ввод ключа. затем, если он обнаружил ввод с помощью клавиши возврата, удалите ввод клавиши возврата и отклоните клавиатуру.

func textViewDidChange(_ textView: UITextView) {
    tableView.beginUpdates()
    if textView.text.contains("\n"){
        textView.text.remove(at: textView.text.index(before: textView.text.endIndex))
        textView.resignFirstResponder()
    }
    tableView.endUpdates()
}