Swift 傻瓜技巧:自定义枚举的映射关系
在 iOS 平台的 app 中,有种列表设计非常常见: 一个 table view,里面是一个项目列表,每个项目只有一行简单的文字,对于被选中的项目,后面会有一个对勾。 当然,往往有着许多更好的方式来实现这种列表的功能,特别是在你的 app 中 UI 处于核心地位的时候。但是,用户们熟悉这种列表的展示方式,比如 App 中的设置页面,使用这种列表来表现是非常实用的。实际上,iOS 系统的设置页面也随处可见这种列表。让我们来为这篇文章找个案例--点开系统的设置,在其中点击信息一栏,然后点击保留信息,之后你就可以选择信息保留的时限:“30 天”,“一年” 或是 “永久”。 有时候选项是动态的(比如说,选择默认日历,里面的选项取决于用户创建了哪些日历),但是大部分时候,包括保留信息这个例子,选项列表是固定的,从代码的角度考虑,这些设置可以用一个枚举(enum)很好地表示出来: enum KeepMessagesOptions: Int { case For30Days,For1Year,Forever } 通过让这个枚举基于 但是,为了存储设置项,你还需要一些别的东西:一个 永久的 ID,和索引区分开来。为什么呢?让我们假设下次升级的时候你决定加入更多的时间间隔: enum KeepMessagesOptions: Int { case For30Days,For6Months,Forever } 如果之前一个 iOS 9 的用户选择了永久保存他的信息,而你也用了 index(2) 去存储他的设置,问题就产生了--现在这个索引对应的是 当然,你更不能用屏幕上显示的那些名称来存储设置,因为这些名称 a)(国际化时)要翻译成别的语言 b) 有可能有拼写错误,并在之后得到了修正(这样修正前和修正后的项目就无法对应起来了),或是发生一些类似的其他情况。通常来说,你永远都不该使用用户界面上的那些字符串来做设置的存储。所以你需要一些特殊的、机器能读取的 ID 来做这事儿。 这个问题显而易见,为什么我要一再强调呢?因为不得不费心神去处理这事儿,确实让人觉得很烦躁。 在 Objective-C 以及 C/C++ 中,枚举只是一个略漂亮的定义整数的方式。 所以,为了做一个 UI 给用户进行选项的选择,你需要定义两个字符串列表来表示这些选项(一个是给机器读取用的 ID 列表,一个是本地化的给人类阅读的名称列表),做一个 table view controller 来展示这些选项,每次你使用它的时候,传入人类可理解的字符串,定义一些 API 来处理结果,每次读写设置项目的时候,你都需要处理所有索引、机器可读取的 ID 和供人类阅读的名称之间的转换。 这并不难。但这很乏味。 一种更好的方式在 Swift 中,枚举能做的事多了不少。让我们快速地定义一个协议: protocol PickableEnum { var displayName: String { get } var permanentID: String { get } static var allValues: [Self] { get } static func fromPermanentID(id: String) -> Self? } 这样一来,写一个常见的,能从任何 @IBAction func showChoicesForKeepMessage() { // 假设我们已经从别的地方拿到了 currentValue EnumPickerController.pickFromEnum(currentValue,from: self).onSuccess { newValue in // 处理 newValue,把 newValue.permanentID 存到 NSUserDefaults 的什么地方去 } } 这看起来相当不错,但是枚举们本身怎样了呢--我们是否只是在枚举们实现 extension PickableEnum where Self: RawRepresentable,Self.RawValue == Int { var displayName: String { return Localised("(self.dynamicType).(self)") } var permanentID: String { return String(self) } static var allValues: [Self] { var result: [Self] = [] var value = 0 while let item = Self(rawValue: value) { result.append(item) value += 1 } return result } static func fromPermanentID(id: String) -> Self? { return allValues.indexOf { $0.permanentID == id }.flatMap { self.init(rawValue: $0) } } } 这些只用写一次,现在对于协议的所有要求,我们都有了合理的默认实现。所以我们所要做的一切就是让我们的枚举遵循协议: enum KeepMessagesOptions: Int,PickableEnum { case For30Days,Forever } 当然,你也可以(对协议)使用你自己的实现。比如说,如果你需要兼容 enum KeepMessagesOptions: Int,Forever var permanentID: String { return ["30d","6m","1y","forever"][rawValue] } } 但你也不是非得这么做。 另外,对于供人阅读的字符串,把他们加到本地化字符串文件(localised strings file),用枚举类型和枚举的 case 作为 key,你所需要做的就是这样: 在 Localized.strings 中: "KeepMessagesOptions.For30Days" = "For 30 Days"; // etc (我做了个假设,上面的那个 其他方法以及补充说明另一种可能的方法是,使用基于 protocol PickableEnum: RawRepresentable { var displayName: String { get } static var allValues: [Self] { get } } 理论看起来比较简单,但是在实践中,你必须给枚举的 case 做索引来和 table view 的行进行相互转换。你还必须根据 case 手动地定义 但在我的例子中,我还是坚持用基于整形的枚举,这是出于另外一个原因:当我需要用到 Ferrite 的音频引擎(用 Objective-C++ 写的,之前已经提到过)中的值时,基于整形的枚举会让我更加容易对这些值进行处理。 另外一个小小的原因:我上面给出的是最简单的定义,但是在实践中,我发现如果 噢,这还有另外一点……实际上,我们并不一定要用枚举来做这事儿。还记得不,最开始的时候我说了有时候列表是基于用户提供的数据的,并不是一个固定的集合?你可以构建自己的结构体来遵循
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |