Wie funktioniert String.Index in Swift?

108

Ich habe einige meiner alten Codes und Antworten mit Swift 3 aktualisiert, aber als ich zu Swift Strings and Indexing kam, war es schwierig, die Dinge zu verstehen.

Insbesondere habe ich Folgendes versucht:

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

wo die zweite Zeile mir den folgenden Fehler gab

'advancedBy' ist nicht verfügbar: Um einen Index um n Schritte voranzutreiben, rufen Sie 'index (_: offsetBy :)' für die CharacterView-Instanz auf, die den Index erstellt hat.

Ich sehe, dass Stringdie folgenden Methoden hat.

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)

Diese verwirrten mich zuerst wirklich und ich fing an, mit ihnen herumzuspielen, bis ich sie verstand. Ich füge unten eine Antwort hinzu, um zu zeigen, wie sie verwendet werden.

Suragch
quelle

Antworten:

280

Geben Sie hier die Bildbeschreibung ein

Alle folgenden Beispiele verwenden

var str = "Hello, playground"

startIndex und endIndex

  • startIndex ist der Index des ersten Zeichens
  • endIndexist der Index nach dem letzten Zeichen.

Beispiel

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

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

Mit den einseitigen Bereichen von Swift 4 kann der Bereich auf eine der folgenden Formen vereinfacht werden.

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

Ich werde das vollständige Formular in den folgenden Beispielen aus Gründen der Klarheit verwenden, aber aus Gründen der Lesbarkeit möchten Sie wahrscheinlich die einseitigen Bereiche in Ihrem Code verwenden.

after

Wie in: index(after: String.Index)

  • after bezieht sich auf den Index des Zeichens direkt nach dem angegebenen Index.

Beispiele

// 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

Wie in: index(before: String.Index)

  • before bezieht sich auf den Index des Zeichens direkt vor dem angegebenen Index.

Beispiele

// 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

Wie in: index(String.Index, offsetBy: String.IndexDistance)

  • Der offsetByWert kann positiv oder negativ sein und beginnt mit dem angegebenen Index. Obwohl es vom Typ ist String.IndexDistance, können Sie ihm eine geben Int.

Beispiele

// 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

Wie in: index(String.Index, offsetBy: String.IndexDistance, limitedBy: String.Index)

  • Dies limitedByist nützlich, um sicherzustellen, dass der Index durch den Versatz nicht außerhalb der Grenzen liegt. Es ist ein Begrenzungsindex. Da der Offset den Grenzwert überschreiten kann, gibt diese Methode eine Option zurück. Es wird zurückgegeben, nilwenn der Index außerhalb der Grenzen liegt.

Beispiel

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

Wenn der Offset 77anstelle von gewesen wäre 7, ifwäre die Anweisung übersprungen worden.

Warum wird String.Index benötigt?

Es wäre viel einfacher, einen IntIndex für Strings zu verwenden. Der Grund, warum Sie String.Indexfür jeden String einen neuen erstellen müssen, ist, dass die Zeichen in Swift unter der Haube nicht alle gleich lang sind. Ein einzelnes Swift-Zeichen kann aus einem, zwei oder sogar mehr Unicode-Codepunkten bestehen. Daher muss jeder eindeutige String die Indizes seiner Zeichen berechnen.

Es ist möglicherweise möglich, diese Komplexität hinter einer Int-Index-Erweiterung zu verbergen, aber ich zögere dies. Es ist gut, daran erinnert zu werden, was tatsächlich passiert.

Suragch
quelle
18
Warum startIndexsollte etwas anderes als 0 sein?
Robo Robok
15
@RoboRobok: Da Swift mit Unicode-Zeichen arbeitet, die aus "Graphemclustern" bestehen, verwendet Swift keine Ganzzahlen, um Indexpositionen darzustellen. Angenommen, Ihr erster Charakter ist ein é. Es besteht eigentlich aus dem ePlus einer \u{301}Unicode-Darstellung. Wenn Sie einen Index von Null verwenden, erhalten Sie entweder das eZeichen oder das Akzentzeichen ("Grab"), nicht den gesamten Cluster, aus dem sich das Zeichen zusammensetzt é. Wenn startIndexSie die Option verwenden, erhalten Sie den gesamten Graphemcluster für jedes Zeichen.
Leanne
2
In Swift 4.0 werden alle Unicode-Zeichen mit 1 gezählt. ZB: "👩‍💻" .count // Now: 1, Before: 2
selva
3
Wie konstruiert man eine String.Indexaus einer ganzen Zahl, außer eine Dummy-Zeichenfolge zu erstellen und die .indexMethode darauf anzuwenden? Ich weiß nicht, ob mir etwas fehlt, aber die Dokumente sagen nichts.
Sudo
3
@sudo, du musst ein wenig vorsichtig sein, wenn du eine String.Indexmit einer ganzen Zahl konstruierst, da jeder Swift Characternicht unbedingt dem entspricht, was du mit einer ganzen Zahl meinst. Sie können jedoch eine Ganzzahl an den offsetByParameter übergeben, um eine zu erstellen String.Index. Wenn Sie jedoch keine haben String, können Sie keine erstellen String.Index(da Swift den Index nur berechnen kann, wenn es die vorherigen Zeichen in der Zeichenfolge kennt). Wenn Sie die Zeichenfolge ändern, müssen Sie den Index neu berechnen. Sie können nicht dasselbe String.Indexfür zwei verschiedene Zeichenfolgen verwenden.
Suragch
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)
    }

}
Pavel Zavarin
quelle
-2

Erstellen Sie eine UITextView in einem tableViewController. Ich habe die Funktion textViewDidChange verwendet und dann nach Eingabetasten gesucht. Wenn die Eingabetaste erkannt wurde, löschen Sie die Eingabe der Eingabetaste und schließen Sie die Tastatur.

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()
}
Karl Mogensen
quelle