String.Index如何在Swift中工作


108

我一直在用Swift 3更新一些旧代码和答案,但是当我使用Swift Strings和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)

刚开始这些让我很困惑,所以我开始和他们一起玩直到我理解它们。我在下面添加一个答案,以显示它们的用法。

Answers:


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
}

如果使用offset 77代替7,则该if语句将被跳过。

为什么需要String.Index?

对字符串使用索引会容易得多Int。您必须String.Index为每个String 创建一个新字符串的原因是,Swift中的Characters在引擎盖下的长度并不相同。一个Swift字符可能由一个,两个甚至更多个Unicode代码点组成。因此,每个唯一的String必须计算其Characters的索引。

可能会将这种复杂性隐藏在Int索引扩展的后面,但是我不愿意这样做。最好提醒一下实际发生的事情。


18
为什么startIndex除0以外的其他值?
Robo Robok'17年

15
@RoboRobok:因为Swift使用Unicode字符(由“字素簇”组成),所以Swift不使用整数来表示索引位置。假设您的第一个字符是é。它实际上由e加号\u{301}Unicode表示组成。如果使用零索引,则将得到或带有e重音(“ grave”)字符,而不是组成的整个簇 é。使用startIndex确保,您将获得任何字符的整个字素簇。
leanne

2
在Swift 4.0中,每个Unicode字符都由1计算。例如:“ 👩‍👩” .count //现在:1,之前:2
selva

3
String.Index除了构建虚拟字符串并对其使用.index方法外,如何从整数构造a ?我不知道我是否缺少什么,但是文档什么也没说。
sudo

3
@sudo,在String.Index使用整数构造a时必须小心一点,因为每个Swift Character不一定等于用整数表示的相同含义。也就是说,您可以将整数传递到offsetBy参数中以创建String.Index。如果没有String,则无法构造String.Index(因为Swift仅在知道字符串中的先前字符是什么的情况下才可以计算索引)。如果更改字符串,则必须重新计算索引。您不能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。我使用了函数:textViewDidChange,然后检查了返回键输入。如果检测到返回键输入,则删除返回键的输入并关闭键盘。

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()
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.