String.Index hoạt động như thế nào trong Swift


108

Tôi đã cập nhật một số mã và câu trả lời cũ của mình với Swift 3 nhưng khi tôi sử dụng Swift Strings và Indexing, thật khó để hiểu mọi thứ.

Cụ thể, tôi đã thử những điều sau:

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

nơi dòng thứ hai cho tôi lỗi sau

'advancedBy' không khả dụng: Để tăng chỉ mục lên n bước, hãy gọi 'index (_: offsetBy :)' trên cá thể CharacterView đã tạo chỉ mục.

Tôi thấy Stringcó các phương pháp sau.

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)

Những điều này thực sự khiến tôi bối rối lúc đầu vì vậy tôi bắt đầu chơi với chúng cho đến khi tôi hiểu chúng. Tôi thêm câu trả lời bên dưới để cho biết cách chúng được sử dụng.

Câu trả lời:


280

nhập mô tả hình ảnh ở đây

Tất cả các ví dụ sau đều sử dụng

var str = "Hello, playground"

startIndexendIndex

  • startIndex là chỉ số của ký tự đầu tiên
  • endIndexlà chỉ số sau ký tự cuối cùng.

Thí dụ

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

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

Với phạm vi một phía của Swift 4 , phạm vi có thể được đơn giản hóa thành một trong các dạng sau.

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

Tôi sẽ sử dụng biểu mẫu đầy đủ trong các ví dụ sau để rõ ràng hơn, nhưng để dễ đọc, bạn có thể sẽ muốn sử dụng phạm vi một phía trong mã của mình.

after

Như trong: index(after: String.Index)

  • after đề cập đến chỉ số của ký tự ngay sau chỉ số đã cho.

Ví dụ

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

Như trong: index(before: String.Index)

  • before đề cập đến chỉ số của ký tự ngay trước chỉ số đã cho.

Ví dụ

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

Như trong: index(String.Index, offsetBy: String.IndexDistance)

  • Các offsetBygiá trị có thể là tích cực hay tiêu cực và bắt đầu từ chỉ số nhất định. Mặc dù nó thuộc loại String.IndexDistance, bạn có thể cho nó một Int.

Ví dụ

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

Như trong: index(String.Index, offsetBy: String.IndexDistance, limitedBy: String.Index)

  • Điều limitedBynày rất hữu ích để đảm bảo rằng phần bù không làm cho chỉ mục đi ra ngoài giới hạn. Nó là một chỉ mục giới hạn. Vì có thể bù đắp vượt quá giới hạn, phương thức này trả về tùy chọn. Nó trả về nilnếu chỉ mục nằm ngoài giới hạn.

Thí dụ

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

Nếu phần bù đã được 77thay vì 7, thì ifcâu lệnh sẽ bị bỏ qua.

Tại sao cần String.Index?

Sẽ dễ dàng hơn nhiều nếu sử dụng một Intchỉ mục cho Chuỗi. Lý do mà bạn phải tạo mới String.Indexcho mỗi Chuỗi là các ký tự trong Swift không có cùng độ dài dưới mui xe. Một ký tự Swift đơn lẻ có thể bao gồm một, hai hoặc thậm chí nhiều điểm mã Unicode. Vì vậy, mỗi Chuỗi duy nhất phải tính chỉ số của các Ký tự của nó.

Có thể ẩn sự phức tạp này đằng sau một phần mở rộng chỉ mục Int, nhưng tôi không muốn làm như vậy. Thật tốt khi được nhắc nhở về những gì đang thực sự xảy ra.


18
Tại sao sẽ startIndexlà bất kỳ điều gì khác hơn 0?
Robo Robok

15
@RoboRobok: Vì Swift làm việc với các ký tự Unicode, được tạo thành từ "các cụm grapheme", nên Swift không sử dụng số nguyên để đại diện cho các vị trí chỉ mục. Giả sử ký tự đầu tiên của bạn là một é. Nó thực sự được làm bằng biểu diễn Unicode ecộng \u{301}. Nếu bạn sử dụng chỉ mục bằng 0, bạn sẽ nhận được eký tự dấu hoặc dấu ("nghiêm trọng"), không phải toàn bộ cụm tạo nên é. Sử dụng startIndexđảm bảo bạn sẽ nhận được toàn bộ cụm grapheme cho bất kỳ ký tự nào.
Leanne

2
Trong Swift 4.0, mỗi ký tự Unicode được đếm bằng 1. Ví dụ: "👩‍💻" .count // Hiện tại: 1, Trước: 2
selva.

3
Làm cách nào để tạo một a String.Indextừ một số nguyên, ngoài việc xây dựng một chuỗi giả và sử dụng .indexphương thức trên đó? Tôi không biết liệu tôi có thiếu thứ gì không, nhưng các tài liệu không nói bất cứ điều gì.
sudo

3
@sudo, bạn phải cẩn thận một chút khi xây dựng một String.Indexvới một số nguyên vì mỗi Swift Characterkhông nhất thiết phải giống ý bạn với một số nguyên. Điều đó nói rằng, bạn có thể truyền một số nguyên vào offsetBytham số để tạo a String.Index. Tuy nhiên, nếu bạn không có String, thì bạn không thể xây dựng a String.Index(bởi vì Swift chỉ có thể tính chỉ số nếu nó biết các ký tự trước đó trong chuỗi là gì). Nếu bạn thay đổi chuỗi thì bạn phải tính toán lại chỉ số. Bạn không thể sử dụng giống nhau String.Indextrên hai chuỗi khác nhau.
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

Tạo một UITextView bên trong tableViewController. Tôi đã sử dụng hàm: textViewDidChange và sau đó kiểm tra phím trả về-đầu vào. sau đó, nếu nó phát hiện ra phím nhập lại, hãy xóa đầu vào của phím quay lại và loại bỏ bàn phím.

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()
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.