作者:Wooji Juice,原文链接,原文日期:2016-04-08
译者:Lanford3_3;校对:shanks;定稿:CMB
在 iOS 平台的 app 中,有种列表设计非常常见: 一个 table vIEw,里面是一个项目列表,每个项目只有一行简单的文字,对于被选中的项目,后面会有一个对勾。
当然,往往有着许多更好的方式来实现这种列表的功能,特别是在你的 app 中 UI 处于核心地位的时候。但是,用户们熟悉这种列表的展示方式,比如 App 中的设置页面,使用这种列表来表现是非常实用的。实际上,iOS 系统的设置页面也随处可见这种列表。让我们来为这篇文章找个案例--点开系统的设置,在其中点击信息一栏,然后点击保留信息,之后你就可以选择信息保留的时限:“30 天”,“一年” 或是 “永久”。
有时候选项是动态的(比如说,选择默认日历,里面的选项取决于用户创建了哪些日历),但是大部分时候,包括保留信息这个例子,选项列表是固定的,从代码的角度考虑,这些设置可以用一个枚举(enum)很好地表示出来:
enum KeepMessagesOptions: Int{ case For30days,For1Year,Forever}
通过让这个枚举基于 Int
类型,枚举中的每一项都会和一个索引关联起来(0 对应 For30days
,1 对应 For1Year
,2 对应 Forever
),这样的话,你就能将 NSIndexPath.row
和枚举互相转换了。
但是,为了存储设置项,你还需要一些别的东西:一个 永久的 ID,和索引区分开来。为什么呢?让我们假设下次升级的时候你决定加入更多的时间间隔:
enum KeepMessagesOptions: Int{ case For30days,For6Months,Forever}
如果之前一个 iOS 9 的用户选择了永久保存他的信息,而你也用了 index(2) 去存储他的设置,问题就产生了--现在这个索引对应的是 For1Year
,所以用户可能会失去许多他本想保留下来的信息,这样很不好。
当然,你更不能用屏幕上显示的那些名称来存储设置,因为这些名称 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?}
这样一来,写一个常见的,能从任何 PickableEnum
中进行选项选择的 vIEw controller 会变得相当简单了。 加到 Futures 中,你就能像下面这样轻松使用它:
@IBAction func showChoicesForKeepMessage(){ // 假设我们已经从别的地方拿到了 currentValue EnumPickerController.pickFromEnum(currentValue,from: self).onSuccess { newValue in // 处理 newValue,把 newValue.permanentID 存到 NSUserDefaults 的什么地方去 }}
这看起来相当不错,但是枚举们本身怎样了呢--我们是否只是在枚举们实现 PickableEnum
这个协议中的方法时,把所有要做的额外工作都写在里面了呢?不 -- 枚举们由协议拓展(protocol extension)来实现:
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 {enum KeepMessagesOptions: Int,PickableEnum{ case For30days,Forever}.permanentID == ID }.flatMap { self.init(rawValue:NSUserDefaults
) } }}
这些只用写一次,现在对于协议的所有要求,我们都有了合理的默认实现。所以我们所要做的一切就是让我们的枚举遵循协议:
enum KeepMessagesOptions: Int,Forever var permanentID: String { return ["30d","6m","1y","forever"][rawValue] }}
当然,你也可以(对协议)使用你自己的实现。比如说,如果你需要兼容
"KeepMessagesOptions.For30days" = "For 30 Days";// etc中已有的设置的 ID,你可以这样做:
Localised()
但你也不是非得这么做。
另外,对于供人阅读的字符串,把他们加到本地化字符串文件(localised strings file),用枚举类型和枚举的 case 作为 key,你所需要做的就是这样:
在 Localized.strings 中:
NSLocalizedString()
(我做了个假设,上面的那个 genstrings
是一个函数,它使用标准的 cocoa 本地化步骤,从 main bundle 中寻找适当的文本。为什么不用 String
呢?因为那只是用于静态的字符串,当你构造字符串(就像我们这儿所做的),运行 Int
或是类似的其他方法时,很可能给你带来麻烦。)
另一种可能的方法是,使用基于 init(rawValue:)
而非 rawValue
的枚举。 这样做的好处在于,你能够使用默认的 RawRepresentable
方法以及 PickableEnum
成员变量来转换 ID 和 case,如果你通过拓展标准库中的 allValues
协议来实现 displayname
协议,那么协议也可以缩减为仅剩
protocol PickableEnum: RawRepresentable{ var displayname: String { get } static var allValues: [Self] { get }}和
allValues
。 String
理论看起来比较简单,但是在实践中,你必须给枚举的 case 做索引来和 table vIEw 的行进行相互转换。你还必须根据 case 手动地定义 1 数组。如果你发现你总是在做这事儿,也许使用一个基于 allValues
的枚举会更好。或者说,在将来,即使是对于基于非整形类型的枚举,Swift 也可能提供一种方法来自动地访问一个枚举中所有可能的 case(至少对于没有关联数据的枚举来说)。毕竟编译器掌握了相关信息[(name: String,ID: String)]
。
但在我的例子中,我还是坚持用基于整形的枚举,这是出于另外一个原因:当我需要用到 Ferrite 的音频引擎(用 Objective-C++ 写的,之前已经提到过)中的值时,基于整形的枚举会让我更加容易对这些值进行处理。
另外一个小小的原因:我上面给出的是最简单的定义,但是在实践中,我发现如果 PickableEnum
返回的不是一个实例数组,而是一个元组数组,比如说 UIPickableVIEw
,将会非常有用。这是 vIEw controller 真正需要的数据,并且减少了泛型适配所有类型的副作用 -- 它只需要处理已知的类型。这点或许对你有价值。
噢,这还有另外一点……实际上,我们并不一定要用枚举来做这事儿。还记得不,最开始的时候我说了有时候列表是基于用户提供的数据的,并不是一个固定的集合?你可以构建自己的结构体来遵循 switch
,你的 vIEw controller (或者是其他你使用的组件,也许是……?)也能够很好地接受他。而且幸运的是,你不用去写一堆重复的代码。
就像你使用 语句时,它知道你都有哪些 case ↩ 总结本文由 SwiftGG 翻译组翻译,已经获得作者翻译授权,最新文章请访问 http://swift.gg。
以上是内存溢出为你收集整理的Swift 傻瓜技巧:自定义枚举的映射关系全部内容,希望文章能够帮你解决Swift 傻瓜技巧:自定义枚举的映射关系所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)