iOS 提供给我们一些很强大的 API ,用以处理字符串,包括把字符串切分成数组,移除空白字符,甚至检查拼写:

在接下来这个 app 中,我们将从 app bundle 中加载一个文件,它包含 10,000 个 8 字母的单词,其中的任何一个都可以用以开始游戏。每行存储一个单词,所以我们要做的是将整个文件里的字符串切分成一个字符串的数组,以便我们可以从中随机选择。

Swift 提供了一个叫 components(separatedBy:) 的方法,可以将一个单一字符串转换成一个字符串数组,通过找出其他字符串将原字符串打碎的方式。举个例子,下面的代码会创建一个 ["a", "b", "c"] 的字符串数组:


let input = "a b c"
let letters = input.components(separatedBy: " ")


我们有一个通过换行分隔的字符串,所以为了转换成字符串数组,我们需要对它进行拆分。

在编程上,我们用一个特殊的序列来代表换行:n。所以,我们可以这样书写代码:


let input = """
            a
            b
            c
            """
let letters = input.components(separatedBy: "n")


无论我们以什么字符串切分原字符串,结果都会是一个字符串数组。从这个数组中,我们可以利用小标读取里面的元素,例如 letters[0] 或者 letters[2] ,但 Swift 提供了一个有用的选项 :randomElement()方法。它会返回数组中一个随机的元素。

举个例子,下面的代码会从我们的数组中读取一个随机的字母。


let letter = letters.randomElement()


不过,虽然我们知道字母数组中包含了三个元素,但是 Swift 并不知道 —— 也有可能,我们试图切分一个空的字符串。这种情况下,randomElement()方法会返回一个可选字符串,我们必须解包或者用空合运算符来操作结果。

还有一个有用的字符串方法是 trimmingCharacters(in:),它让 Swift 从一个字符串的开始到结尾移除特定的字符。这里用到了一个新的类型叫 CharacterSet,不过多少情况下我们用这个方法来移除空白字符和空行 —— 包括空格,tabs,换行,一次解决。

这个用法非常常见,以至于它内置在 CharacterSet 结构体中,所以我们可以像这样要求 Swift 移除一个字符串的空白字符:


let trimmed = letter?.trimmingCharacters(in: .whitespacesAndNewlines)


在进行主工程之前,我还有一个关于字符串的知识点需要介绍,这就是检查误拼写单词的功能。

这个功能是通过 UITextChecker 来提供的,你可能没有意识到这一点,名字中 “UI” 的部分带来两层含义:

  1. 这个类来自 UIKit ,不过这并不是说我们需要加载所有的旧的 framework;实际上,通过 SwiftUI,我们已经自动拿到了。
  2. 它是用 Apple 的老的编程语言 Objective-C 写成的。我们并不需要写 Objective-C 的代码才能用上它,不过对于 Swift 用户来说这个 API 会有难以驾驭。

检查一个字符串单词的拼写一共分 4 步。首先,我们创建一个待检查的单词实例,一个用于检查字符串的UITextChecker 实例:


let word = "swift"
let checker = UITextChecker()


其次,我们需要告诉 checker 我们想要检查多少字符串。想象一下,在一个文字处理 app 中,拼写检查器要检查的是用户选择的文本,而非整个文档。

不过,有一点要注意:Swift 采取一种非常清晰,也很高级的方式来处理字符串,这使得用到 emoji 这种复杂字符的字符串,能够像英文字母一样地处理。不过,Objective-C 并不采用这种方式处理字符串。这意味着我们需要让 Swift 创建一个 Objective-C 的字符串范围。就像下面这样:


let range = NSRange(location: 0, length: word.utf16.count)


UTF-16 是一种字符编码方式 —— 一种存储字符的方式。我们在 Objective-C 中使用它,这样 Objective-C 就能够理解 Swift 里的字符串是如何存储的:它是一种连接两种格式的桥梁。

其三,我们让 checker 报告它是否在我们的字符串中找到误拼写,通过把字符串范围传进去检查,并指定范围的起点和终点 (以便我们能做 “查找下一个” 这样的操作),以及检查器在到达行尾时是否换行,检查采用什么语言:


let misspelledRange = checker.rangeOfMisspelledWord(in: word, range: range, startingAt: 0, wrap: false, language: "en")


这个调用将返回另一个 Objective-C 字符串范围,告诉我们误拼写的地方在哪里。此外,还有一个复杂性要处理:Objective-C 并没有可选型的概念,它是依赖特定的值来代表数据缺失。

在这个实例中,如果返回的 Objective-C 的范围是空的 —— 也就说,因为字符串拼写正确所以没有错误返回,那我们将得到一个特别的值 NSNotFound

因此,我们可以匹配这个结果是否等于值 NSNotFound 来确定字符串是否有拼写错误,像下面这样:


let allGood = misspelledRange.location == NSNotFound