本节摘要:本节清点排片界面最常用的三件道具——数组(有序列表)、字典(键值映射)与字符串(文案与索引)。核心结论:数组对应界面上"一排一排的场次",字典对应"按编号取物",字符串的索引体系独立于整数下标,三者各有脾气,选错道具会在第3章建模时付出返工代价。
登记处办完手续(1.2 节),角色们要有地方住。道具间在剧场后台,界面开演前,数据在这里被整理成能上台的形状。
排片界面拿到的原始素材通常长这样:一个装着几十部影片的列表、一份"影片编号到放映厅"的对应表、一堆要拼进界面文案的字符串。三件道具正好各管一摊。

界面上每一行场次,在数据层就是一个数组元素。数组的日常操作全在下面这段里:
// 建表:字面量直接建,类型推断为 [String] var films = ["雾中灯塔", "星际列车", "深海回声"] films.append("午夜飞行") // 尾部追加:变 4 个 films.insert("首映礼", at: 0) // 头部插入:首映礼顶到 0 号位 let first = films[0] // 下标访问:"首映礼" films.removeLast() // 移除末尾的"午夜飞行" print(films) // 输出:["首映礼", "雾中灯塔", "星际列车", "深海回声"] print("共 \(films.count) 部") // 输出:共 4 部 // 遍历:要下标时用 enumerated for (index, name) in films.enumerated() { print("第 \(index + 1) 位:\(name)") } // 输出四行:第 1 位:首映礼 …… 第 4 位:深海回声
数组最要命的一点是越界即崩溃:films[99] 直接炸场,不是返回 nil。Swift 留了安全的口子:
// 安全取值:返回可选,越界时是 nil 而不是崩溃 if let last = films.last { print("最后一部是 \(last)") // 输出:最后一部是深海回声 } if let beyond = films.indices.contains(9) ? films[9] : nil { print(beyond) } else { print("第 10 位不存在,已安全避开") // 输出:第 10 位不存在,已安全避开 }
排片界面的"场次行"会用结构体数组(第3章建模),这里用字符串数组先把操作练熟。
影片编号查放映厅、场次编号查座位余量——凡是"拿一个键换一个值"的场景都是字典。
// 建册:[String: String],键唯一 var hallOfFilm: [String: String] = [ "F001": "3 号厅", "F002": "IMAX 厅", ] hallOfFilm["F003"] = "情侣厅" // 新增 hallOfFilm["F001"] = "杜比厅" // 覆盖旧值(键已存在即覆盖) let hall = hallOfFilm["F001"] // 注意:hall 是 String? 可选 print(hall ?? "未排厅") // 输出:杜比厅 let removed = hallOfFilm.removeValue(forKey: "F003") // 删除并返回旧值 print(removed ?? "本来就没有") // 输出:情侣厅 // 遍历:键值对无序返回,不要依赖顺序 for (filmID, hallName) in hallOfFilm { print("\(filmID) 排在 \(hallName)") }
注意 hallOfFilm["F001"] 的类型是 String?——字典查不到是常态而不是异常,所以取值天然是可选值,这正好接上 1.2 节的铁律:用之前先解包。字典遍历顺序不保证稳定,需要顺序就先把键排序再取值。
Swift 字符串是"字符序列",但它的下标不是整数——因为一个"字"在 Unicode 里可能由多个码元拼成(比如"灯"后面跟一个变音符)。所以取子串要走 String.Index:
let title = "雾中灯塔·悬疑夜场" print(title.count) // 输出:9(字符数,不是字节数) let start = title.startIndex let end = title.index(start, offsetBy: 4) print(String(title[start..<end])) // 输出:雾中灯塔 // 拼装界面文案的主力:插值,永远优先于加号拼接 let hallName = "IMAX 厅" let seats = 12 let line = "《\(title)》\(hallName) 仅剩 \(seats) 座" print(line) // 输出:《雾中灯塔·悬疑夜场》IMAX 厅 仅剩 12 座 // 判断与查包含 print(title.hasPrefix("雾中")) // 输出:true,常用于搜索联想的前缀匹配 print(title.contains("夜场")) // 输出:true
⚠️ 常见坑:拿别的语言的习惯写
title[0]。Swift 编译器直接拒绝——整数下标对"变长字符"无法既快速又正确。改用title.first(返回可选字符)或String.Index。
把三件道具串成排片界面的数据预处理。背景:接口返回影片名数组和编号-厅字典,界面要拼出"片名(厅)"的行文案。操作如下:
let filmNames = ["雾中灯塔", "星际列车", "深海回声"] let hallMap: [String: String] = ["雾中灯塔": "杜比厅", "星际列车": "IMAX 厅"] var rows: [String] = [] for name in filmNames { let hall = hallMap[name] ?? "待排厅" // 查不到给兜底 rows.append("《\(name)》\(hall)") } rows.insert("—— 今日排片 ——", at: 0) // 顶部插一行标题 print(rows) // 输出: // ["—— 今日排片 ——", "《雾中灯塔》杜比厅", "《星际列车》IMAX 厅", "《深海回声》待排厅"]
结果解读:数组保证了影片的出场顺序与接口一致;字典负责随机查询;字符串插值把两者粘成文案。深海回声没查到厅,靠问号问号兜底成"待排厅"——这正是 1.2 节可选值在道具层的落地。变式:如果要求"同一影片多个厅",字典的值就该换成数组 [String: [String]];要求按厅分组显示,则改用字典套字典。道具形状跟着界面需求走,这也是第3章引入自定义类型的动机:散装道具拼到一定程度,就该定制货架了。
序幕到此落幕。第一幕开演:排片界面正式出生,函数是它的台词与动作(2.1),闭包是随时候场的替补(2.2),而加载失败这类演出事故,交给错误处理救场(2.3)。