String.IndexはSwiftでどのように機能しますか


108

私は古いコードの一部を更新してSwift 3で回答していますが、Swift Strings and Indexingに到達したとき、物事を理解するのは大変でした。

具体的には、私は以下を試しました:

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

2行目は私に次のエラーを与えていました

'advancedBy'は使用できません。インデックスをnステップ進めるには、インデックスを生成したCharacterViewインスタンスで 'index(_:offsetBy :)'を呼び出します。

それ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です。String.Indexすべての文字列に対して新しい文字列を作成する必要があるのは、Swiftの文字の長さがすべて同じではないためです。単一のSwift文字は、1つ、2つ、またはそれ以上のUnicodeコードポイントで構成される場合があります。したがって、各一意の文字列は、その文字のインデックスを計算する必要があります。

この複雑さをIntインデックス拡張の背後に隠すことはおそらく可能ですが、私はそうすることに消極的です。実際に何が起こっているのかを思い出させるのは良いことです。


18
なぜstartIndex0以外になるのですか?
Robo Robok 2017

15
@RoboRobok:Swiftは「書記素クラスター」で構成されるUnicode文字を処理するため、Swiftは整数を使用してインデックスの場所を表しません。最初の文字がであるとしましょうé。実際には、eプラスの\u{301}Unicode表現でできています。ゼロのインデックスを使用した場合e、を構成するクラスター全体ではなく、またはアクセント(「墓」)文字の いずれかを取得しますé。を使用startIndexすると、任意のキャラクターの書記素クラスタ全体を取得できます。
傾斜

2
Swift 4.0では、各Unicode文字は1ずつカウントされます。例: "👩‍💻" .count // Now:1、Before:2
selva

3
String.Indexダミー文字列を作成して.indexメソッドを使用する以外に、整数からaをどのように構築しますか?何か足りないものがあるかどうかはわかりませんが、ドキュメントには何も書かれていません。
sudo

3
@sudo、String.Index各Swift Characterは必ずしも整数で意味するものと同じではないため、整数でa を作成するときは少し注意する必要があります。つまり、整数をoffsetByパラメータに渡してを作成できますString.Index。あなたが持っていない場合はString、しかし、あなたが構築することができませんString.Index(それは文字列の前の文字が何であるかを知っている場合スウィフトはインデックスのみを計算することができるため)。文字列を変更する場合は、インデックスを再計算する必要があります。同じものString.Indexを2つの異なる文字列に使用することはできません。
Suragch 2017年

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

tableViewController内にUITextViewを作成します。私は関数textViewDidChangeを使用し、return-key-inputをチェックしました。その後、return-key-inputを検出した場合は、returnキーの入力を削除してキーボードを閉じます。

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()
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.