Storyboard 与 Xib 的“抉择”

同步:ckitakishi.com

近期开始进行一个新项目的原型制作及其结构设计,打算把一些心路历程记录下来,随便先给它取个名字叫做:P-OOP

比起手写 UI,“拖控件”的 Storyboard 和 Xib 似乎一直都更投我所好。不过即使是 Storyboard 和 Xib 之间,似乎也还是多多少少有一些纷争。

Storyboard & Xib

公司 (年久失修) 的 iOS Guidelines 中写着一句话:

进行源码管理时 Storyboard 极易导致冲突,团队开发时,各画面与各组件尽可能使用 Xib 进行实现。

对此我一直抱着赞否两论的观点。在实际工作时,同一 Storyboard 中存在大量 ViewController 十分容易冲突是一个不争的事实,掉进这个坑的人有可能还进行过 xml 修正。但是这个锅 Storyboard 不背。一部分人可能因此选择了弃 Storyboard 从 Xib 之路,我也一度徘徊是否这才是正道。但是很显然的是,Storyboard 从一开始就不是为了代替 Xib 而来。

除了 UI 设置的相似部分以外,Storyboard 更重视画面之间的关联和迁移,而 Xib 作为通用组件的模版应该是不二的选择。

在 P-OOP 中,将会存在大量的 dialog,尽管可以很容易的使用 Present Modally 来实现,不过为了保持系列产品的风格一致性,需要考虑如何以比较好的方式来实现共通的 header 和 footer 样式。考虑过很多方案,比如:

  1. 将 footer 和 header 集成在同一个 view 中,并添加一个 content view,最终在某 controlelr view 中将上述 view 与实际从另一个 xib 中载入的 content view 组合,完成组装。但是存在一个比较显著的缺点,实际可见的 controller view 所呈现的内容并不是很直观,果然还是必须看代码才能梳理清楚。

  2. 将 footer 和 header 以及一个 content view 集成在同一个 controller view 中。在代码中按照要求载入 content,代理方法之类变得容易管理了一些,但是更糟糕的是这个 controller 的代码终将成为垃圾场的。。。那加入继承呢?有些小题大做?

果然简洁才是最高的,将 footer 和 header 完全独立为两个 view,按需载入。结合 @IBInspectable 和 @IBDesignable 可以说是比较完美了,从画面设计到迁移等都很清晰。不足一提的小缺点是使用时候的 auto layout 的设置可能存在一些重复操作 (比如 Auto Layout 之类的),若考虑 Model 除了 form sheet 以外可以是 full screen,后者需要在顶部额外预留 20px,这样一来反而变得巧妙了。

也许过几天自己的想法又发生了细微变化,但简洁清晰无论何时都不会太坏。

心得

Storyboard Reference

Storyboard 容易引发冲突,这句话在 Storyboard Reference 面前是不成立的。

Storyboard Reference 第一次出现在 Xcode 7,可以从组件库中找到它,并自行进行配置和关联,十分简单,无需赘述。即使是一个已经完成且十分繁杂的 Storyboard,也可以选中想要分离的 Storyboard,通过 Editor -> Refactor to Storyboard 来实现。比如,使用了两个 Container View,默认情况下此时画面中存在三个 controller,对其进行分离之后,变成了这样:

storyboard_reference.png

Loadable Nib

将 Xib 组件的载入协议化,其中一个目的是为了类型安全,另一个目的是为了减少重复代码。

    protocol Loadable: class {
        static var nibName: String { get }
    }
    
    extension Loadable {
        static var nibName: String { return String(describing: Self.self) }
    }

UIView 进行扩展,要求被载入的 view 遵循 Loadable 协议:

    extension UIView {
        func instantiateFromNib<T: UIView>(_:T.Type) -> T where T: Loadable {
            if let nib = UINib(nibName: T.nibName, bundle: nil).instantiate(withOwner: nil, options: nil).first as? T {
                return nib
            } else {
                fatalError("Nib \(T.nibName) is not exist ?!")
            }
        }
        
        func instantiateFromNibOwner<T: UIView>(_:T.Type) where T: Loadable {
            let bundle = Bundle(for: type(of: self))
            if let nib = UINib(nibName: T.nibName, bundle: bundle).instantiate(withOwner: self, options: nil).first as? UIView {
                nib.frame = self.bounds
                nib.autoresizingMask = [.flexibleWidth, .flexibleHeight]
                self.addSubview(nib)
            } else {
                fatalError("Nib \(T.nibName) is not exist ?!")
            }
        }
    }

简洁的初始化:

    let view:ClassName = self.instantiateFromNib(ClassName.self)
    self.instantiateFromNibOwner(ClassName.self)

后来发现一个名为 Reusable 的库,其中除了这一部分的实现之外,还有对 Cell 甚至是 Storyboard 和 ViewController 的重用,十分强大。

回到这一部分的实现,略有区别的地方在于:

  1. Reusable 在初始化 nib 的时候选择了扩展协议。
  2. File's Owner 的情况下,Reusable 使用了 Auto Layout。由于我们的 P-OOP 项目对应的设备尺寸不多,所以像是部分弹出框就没有对应 Auto Layout,所以就直接从 frame 的尺寸下手了。。

追记:把这部分实现和例子提了出来放在了 Github 上~

@IBDesignable 和 @IBInspectable

@IBDesignable 可以用于视图的实时渲染,@IBInspectable 可以用于定义运行时属性。

举个例子来说:首先在定义一个 DialogHeaderView,标记为 @IBDesignable,将它的 headerTitle 属性设置为 @IBInspectable

    @IBDesignable class DialogHeaderView: UIView {
    
        @IBInspectable var headerTitle: String = "" {
            didSet {
                navigationBar.topItem?.title = self.headerTitle
            }
        }
        ...   
    }

然后向目标视图添加一个 UIView,并将类定义为 DialogHeaderView,此时在 Attribuite Inspector 中可以直接设置属性:

IB1.png

之后即会反映在运行时属性栏中:

IB2.png

不过构建失败的时候还是挺多的,不妨通过 Editor -> Debug Selected Views 来调试一下选中的视图。

类型安全

除了定义上面的 Loadable 协议,在类型安全这个问题上还可以进一步再做一些工作。

存在 Storyboard,Segue 的定义也就会有存在,由于 identifier 的定义是字符串,防不胜防,不匹配的情况还是会时而发生。这时候使用 R.swift 就能够完全解消这个担忧了。

R.swift 被广泛使用于解决类型安全的问题,图片、字体、本地化等等都受益于此。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容