Swift에서 String.Index는 어떻게 작동합니까?


108

Swift 3로 이전 코드와 답변 중 일부를 업데이트했지만 Swift Strings and Indexing에 도달했을 때 사물을 이해하는 것이 고통 스러웠습니다.

구체적으로 다음을 시도했습니다.

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

두 번째 줄에서 다음 오류가 발생했습니다.

'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"

startIndexendIndex

  • 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반드시 오프셋 인덱스가 범위의 외출이 발생하지 않음을 만들기위한 유용합니다. 경계 인덱스입니다. 오프셋이 제한을 초과 할 수 있으므로이 메서드는 Optional을 반환합니다. 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 문자는 하나, 둘 또는 그 이상의 유니 코드 코드 포인트로 구성 될 수 있습니다. 따라서 각각의 고유 한 문자열은 해당 문자의 인덱스를 계산해야합니다.

Int 인덱스 확장 뒤에 이러한 복잡성을 숨기는 것이 가능하지만 그렇게하는 것을 꺼려합니다. 실제로 일어나고있는 일을 상기시키는 것이 좋습니다.


18
startIndex0이 아닌 다른 이유는 무엇입니까?
Robo Robok

15
@RoboRobok : Swift는 "문자 소 클러스터"로 구성된 유니 코드 문자로 작동하기 때문에 Swift는 인덱스 위치를 나타내는 데 정수를 사용하지 않습니다. 첫 번째 캐릭터가 é. 실제로 e플러스 \u{301}유니 코드 표현으로 구성됩니다. 인덱스를 0으로 사용하면 e.NET Framework를 구성하는 전체 클러스터가 아니라 또는 악센트 ( "grave") 문자를 얻게됩니다 é. 를 사용 startIndex하면 모든 캐릭터에 대해 전체 자소 클러스터를 얻을 수 있습니다.
leanne

2
Swift 4.0에서 각 유니 코드 문자는 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두 개의 다른 문자열 에서 동일한 것을 사용할 수 없습니다 .
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

tableViewController 내부에 UITextView를 만듭니다. 나는 function : textViewDidChange를 사용한 다음 return-key-input을 확인했습니다. 리턴 키 입력이 감지되면 리턴 키 입력을 삭제하고 키보드를 닫습니다.

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.