Come funziona String.Index in Swift


109

Ho aggiornato alcuni dei miei vecchi codici e risposte con Swift 3 ma quando sono arrivato a Swift Strings and Indexing è stato un problema capire le cose.

Nello specifico stavo provando quanto segue:

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

dove la seconda riga mi dava il seguente errore

"advancedBy" non è disponibile: per far avanzare un indice di n passaggi, chiama "index (_: offsetBy :)" sull'istanza di CharacterView che ha prodotto l'indice.

Vedo che Stringha i seguenti metodi.

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)

All'inizio mi creavano davvero confusione, quindi ho iniziato a giocarci finché non li ho capiti. Aggiungo una risposta di seguito per mostrare come vengono utilizzati.

Risposte:


280

inserisci qui la descrizione dell'immagine

Tutti i seguenti esempi usano

var str = "Hello, playground"

startIndex e endIndex

  • startIndex è l'indice del primo carattere
  • endIndexè l'indice dopo l'ultimo carattere.

Esempio

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

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

Con le gamme unilaterali di Swift 4 , la gamma può essere semplificata in una delle seguenti forme.

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

Userò il modulo completo nei seguenti esempi per motivi di chiarezza, ma per motivi di leggibilità, probabilmente vorrai usare gli intervalli unilaterali nel tuo codice.

after

Come in: index(after: String.Index)

  • after si riferisce all'indice del carattere subito dopo l'indice dato.

Esempi

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

Come in: index(before: String.Index)

  • before si riferisce all'indice del carattere direttamente prima dell'indice dato.

Esempi

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

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

  • Il offsetByvalore può essere positivo o negativo e parte dall'indice dato. Sebbene sia del tipo String.IndexDistance, puoi dargli unInt .

Esempi

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

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

  • Il limitedByè utile per fare in modo che l'offset non causa l'indice di andare fuori dai limiti. È un indice di delimitazione. Poiché è possibile che l'offset superi il limite, questo metodo restituisce un optional. Restituisce nilse l'indice è fuori dai limiti.

Esempio

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

Se l'offset fosse stato 77invece di 7, l' ifistruzione sarebbe stata ignorata.

Perché è necessario String.Index?

Sarebbe molto più facile usare un fileInt indice per le stringhe. Il motivo per cui devi creare un nuovo String.Indexper ogni stringa è che i personaggi in Swift non hanno tutti la stessa lunghezza sotto il cofano. Un singolo Swift Character potrebbe essere composto da uno, due o anche più punti di codice Unicode. Quindi ogni stringa univoca deve calcolare gli indici dei suoi caratteri.

È possibile nascondere questa complessità dietro un'estensione dell'indice Int, ma sono riluttante a farlo. È bene ricordare ciò che sta realmente accadendo.


18
Perché dovrebbe startIndexessere qualcosa di diverso da 0?
Robo Robok

15
@ RoboRobok: Poiché Swift funziona con i caratteri Unicode, che sono costituiti da "grapheme cluster", Swift non utilizza numeri interi per rappresentare le posizioni degli indici. Diciamo che il tuo primo personaggio è un é. In realtà è composto dal epiù una \u{301}rappresentazione Unicode. Se hai usato un indice pari a zero, otterrai il carattere eo l'accento ("grave"), non l'intero cluster che compone il é. Usando il startIndexti assicura che otterrai l'intero grapheme cluster per qualsiasi personaggio.
leanne

2
In Swift 4.0 ogni carattere Unicode viene contato per 1. Ad esempio: "👩‍💻" .count // Now: 1, Before: 2
selva

3
Come si costruisce a String.Indexda un numero intero, oltre a costruire una stringa fittizia e utilizzare il .indexmetodo su di essa? Non so se mi manca qualcosa, ma i dottori non dicono niente.
sudo

3
@sudo, devi stare un po 'attento quando costruisci un String.Indexcon un numero intero perché ogni Swift Characternon è necessariamente uguale alla stessa cosa che intendi con un numero intero. Detto questo, puoi passare un numero intero nel offsetByparametro per creare un file String.Index. Se non hai un String, però, non puoi costruire un String.Index(perché Swift può calcolare l'indice solo se sa quali sono i caratteri precedenti nella stringa). Se si modifica la stringa, è necessario ricalcolare l'indice. Non puoi usare lo stesso String.Indexsu due stringhe diverse.
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)
    }

}

-2

Crea un UITextView all'interno di un tableViewController. Ho usato la funzione: textViewDidChange e poi ho controllato l'input della chiave di ritorno. quindi se ha rilevato l'input del tasto di ritorno, elimina l'input del tasto di ritorno e ignora la tastiera.

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()
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.