Як працює String.Index у Swift


108

Я оновлював частину свого старого коду та відповідей за допомогою Swift 3, але коли я потрапив до Swift Strings та індексації, мені було болісно зрозуміти речі.

Конкретніше я намагався:

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)

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

Відповіді:


280

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

Усі наведені нижче приклади використовують

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індекс для Strings. Причиною того, що вам потрібно створити нову String.Indexдля кожної струни, є те, що символи у Swift не всі однакової довжини під кришкою. Один символ Swift може складатися з однієї, двох, а то й більше кодових точок Unicode. Таким чином, кожен унікальний рядок повинен обчислювати індекси своїх символів.

Можливо, цю складність приховати за розширенням індексу Int, але я не хочу це робити. Добре нагадати, що насправді відбувається.


18
Чому б startIndexнічого іншого, ніж 0?
Робо Робок

15
@RoboRobok: Оскільки Swift працює з символами Unicode, які складаються з "кластерів графем", Swift не використовує цілих чисел для представлення індексованих місць. Скажімо, ваш перший персонаж - це é. Він фактично складається з eплюс \u{301}представлення Unicode. Якщо ви використали індекс нуля, ви отримаєте або eсимвол наголосу ("серйозний"), а не весь кластер, що складається з é. Використовуючи startIndexзабезпечення, ви отримаєте весь кластер графеми для будь-якого символу.
leanne

2
У Swift 4.0 кожен символ Unicode рахується 1. Наприклад: "👩‍💻" .count // Зараз: 1, раніше: 2
selva

3
Як можна побудувати a String.Indexз цілого числа, окрім побудови макетного рядка та використання .indexметоду на ньому? Я не знаю, чи щось мені не вистачає, але документи нічого не говорять.
судо

3
@sudo, ви повинні бути обережними, будуючи String.Indexціле число, оскільки кожен Swift Characterне обов'язково дорівнює одній і тій же речі, яку ви маєте на увазі з цілим числом. Однак, ви можете передати ціле число в offsetByпараметр, щоб створити String.Index. Якщо у вас немає String, тоді ви не можете побудувати a String.Index(оскільки Swift може обчислити індекс лише у тому випадку, якщо він знає, що таке попередні символи в рядку). Якщо ви змінюєте рядок, то необхідно перерахувати індекс. Ви не можете використовувати одне і те ж String.Indexу двох різних рядках.
Сурагч

0
func change(string: inout String) {

    var character: Character = .normal

    enum Character {
        case space
        case newLine
        case normal
    }

    for i in stride(from: string.count - 1, through: 0, by: -1) {
        // first get index
        let index: String.Index?
        if i != 0 {
            index = string.index(after: string.index(string.startIndex, offsetBy: i - 1))
        } else {
            index = string.startIndex
        }

        if string[index!] == "\n" {

            if character != .normal {

                if character == .newLine {
                    string.remove(at: index!)
                } else if character == .space {
                    let number = string.index(after: string.index(string.startIndex, offsetBy: i))
                    if string[number] == " " {
                        string.remove(at: number)
                    }
                    character = .newLine
                }

            } else {
                character = .newLine
            }

        } else if string[index!] == " " {

            if character != .normal {

                string.remove(at: index!)

            } else {
                character = .space
            }

        } else {

            character = .normal

        }

    }

    // startIndex
    guard string.count > 0 else { return }
    if string[string.startIndex] == "\n" || string[string.startIndex] == " " {
        string.remove(at: string.startIndex)
    }

    // endIndex - here is a little more complicated!
    guard string.count > 0 else { return }
    let index = string.index(before: string.endIndex)
    if string[index] == "\n" || string[index] == " " {
        string.remove(at: index)
    }

}

-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()
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.