Bagaimana cara kerja String.Index di Swift

109

Saya telah memperbarui beberapa kode dan jawaban lama saya dengan Swift 3 tetapi ketika saya sampai di Swift Strings and Indexing, sangat sulit untuk memahami banyak hal.

Secara khusus saya mencoba yang berikut:

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

di mana baris kedua memberi saya kesalahan berikut

'advancedBy' tidak tersedia: Untuk memajukan indeks sebanyak n langkah, panggil 'index (_: offsetBy :)' pada instance CharacterView yang menghasilkan indeks.

Saya melihat yang Stringmemiliki metode berikut.

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)

Ini benar-benar membuatku bingung pada awalnya jadi aku mulai bermain-main dengan mereka sampai aku memahaminya. Saya menambahkan jawaban di bawah untuk menunjukkan bagaimana mereka digunakan.

Suragch
sumber

Jawaban:

280

masukkan deskripsi gambar di sini

Semua contoh berikut digunakan

var str = "Hello, playground"

startIndex dan endIndex

  • startIndex adalah indeks dari karakter pertama
  • endIndexadalah indeks setelah karakter terakhir.

Contoh

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

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

Dengan rentang satu sisi Swift 4 , rentang dapat disederhanakan ke salah satu bentuk berikut.

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

Saya akan menggunakan formulir lengkap dalam contoh berikut untuk kejelasan, tapi demi keterbacaan, Anda mungkin ingin menggunakan rentang satu sisi dalam kode Anda.

after

Seperti dalam: index(after: String.Index)

  • after mengacu pada indeks karakter langsung setelah indeks yang diberikan.

Contoh

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

Seperti dalam: index(before: String.Index)

  • before mengacu pada indeks karakter tepat sebelum indeks yang diberikan.

Contoh

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

Seperti dalam: index(String.Index, offsetBy: String.IndexDistance)

  • The offsetByvalue bisa positif atau negatif dan dimulai dari indeks yang diberikan. Meskipun dari jenisnya String.IndexDistance, Anda dapat memberikannya Int.

Contoh

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

Seperti dalam: index(String.Index, offsetBy: String.IndexDistance, limitedBy: String.Index)

  • Ini limitedByberguna untuk memastikan bahwa offset tidak menyebabkan indeks keluar batas. Ini adalah indeks pembatas. Karena ada kemungkinan offset melebihi batas, metode ini mengembalikan Opsional. Ia kembali niljika indeks di luar batas.

Contoh

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

Jika offsetnya 77bukan 7, maka ifpernyataan itu akan dilewati.

Mengapa String.Index dibutuhkan?

Akan lebih mudah menggunakan Intindeks untuk String. Alasan Anda harus membuat yang baru String.Indexuntuk setiap String adalah karena Karakter di Swift tidak memiliki panjang yang sama di bawah tenda. Satu Karakter Swift mungkin terdiri dari satu, dua, atau bahkan lebih titik kode Unicode. Jadi, setiap String unik harus menghitung indeks Karakternya.

Mungkin untuk menyembunyikan kerumitan ini di balik ekstensi indeks Int, tetapi saya enggan melakukannya. Senang rasanya diingatkan tentang apa yang sebenarnya terjadi.

Suragch
sumber
18
Mengapa startIndexlebih dari 0?
Robo Robok
15
@RoboRobok: Karena Swift bekerja dengan karakter Unicode, yang terbuat dari "kelompok grafik", Swift tidak menggunakan bilangan bulat untuk mewakili lokasi indeks. Katakanlah karakter pertama Anda adalah é. Ini sebenarnya terbuat dari eplus \u{301}representasi Unicode. Jika Anda menggunakan indeks nol, Anda akan mendapatkan karakter eatau aksen ("kuburan"), bukan seluruh kluster yang membentuk é. Menggunakan startIndexmemastikan Anda akan mendapatkan seluruh cluster grafem untuk karakter apa pun.
leanne
2
Di Swift 4.0 setiap karakter Unicode dihitung oleh 1. Misalnya: "👩‍💻" .count // Sekarang: 1, Sebelum: 2
selva
3
Bagaimana seseorang membangun a String.Indexdari integer, selain membangun dummy string dan menggunakan .indexmetode di atasnya? Saya tidak tahu apakah saya melewatkan sesuatu, tetapi dokumen tidak mengatakan apa-apa.
sudo
3
@sudo, Anda harus sedikit berhati-hati saat membuat a String.Indexdengan integer karena setiap Swift Charactertidak harus sama dengan yang Anda maksud dengan integer. Meskipun demikian, Anda dapat meneruskan integer ke offsetByparameter untuk membuat file String.Index. Jika Anda tidak memiliki a String, maka Anda tidak dapat membuat a String.Index(karena Swift hanya dapat menghitung indeks jika mengetahui karakter sebelumnya dalam string tersebut). Jika Anda mengubah string, maka Anda harus menghitung ulang indeks. Anda tidak dapat menggunakan yang sama String.Indexpada dua string yang berbeda.
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
sumber
-2

Buat UITextView di dalam tableViewController. Saya menggunakan fungsi: textViewDidChange dan kemudian memeriksa kembali-key-input. kemudian jika terdeteksi input tombol kembali, hapus input tombol kembali dan tutup keyboard.

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
sumber