[转] Swift 实践篇之链式 UI 代码

  原文链接

前言

本篇博客主要介绍

Swift 实践方面的一个技巧,链式 UI 代码。链式代码在 Swift 中有着比 Objective-C 天然的优势。而且通过 Swift

语言本身强大的特性,只需要很少的代码就可以让自己的 Swift 工程具有编写链式 UI 代码的能力。

缘由

先来回答一个问题,为什么要用链式代码来写

UI?答案就是,提高代码可读性。代码可读性的重要性在软件工程的意义不必多说。而代码可读性可以从许多方面优化,比如,合适的缩进、正确的命名、清晰有逻辑的结构等等。链式

UI 代码正是从清晰有逻辑的结构这点出发,提高代码可读性的一种方案。

iOS 开发中,我们每天都要 UI 代码打交道。而在 UI 的实现方面,用 Storyboad 实现还是代码实现,是 iOS

开发领域中一个永不停息的争论。两者各有优势,关于这个的讨论也不在本篇博客范围内。不过无论是用何种形式写 UI, 代码或者 Storyboard,

如果整体的 UI 布局没有一个清晰的分层和结构,那么维护起来都会吃力。本篇的重点在于介绍一种代码风格,一个使用链式代码来写 UI

布局的代码风格,而不在于讲解如何将 UI 布局做得结构清晰

前置条件

有一些前置条件需要交代一下。接下来的介绍中,UI

布局通过代码来完成,而且使用的是 AutoLayout 而非绝对布局。相信没有人会用原生的 Autolayout API 来写全部的 UI

吧,一般会选用一个封装库。博客下面的介绍以最常见的 Autolayout 封装库 SnapKit

来举例,如果你的工程使用了别的库,根据情况来修改相关的代码即可。

核心代码实现

先来分析一下 UI 布局代码的结构。翻出一段使用代码完成的 UI 布局代码看一看,你会发现其中的一些特定的结构。

第一步肯定是会初始化一个 view,接下来通过一系列步骤完成需求:添加到父 view、添加视图约束、配置属性。在这接下来的三个步骤里,添加到父视图这个步骤一定要在添加视图约束这个步骤之前。至于配置 view 属性这个步骤,在这两个步骤之前或之后都没有问题。

除了初始化 view 这个步骤,将其后的三个步骤抽取出来,做成三个通用的链式布局函数,将这三个函数使用 extension 机制扩展到 UIView 中。实现代码如下:


以上就是完成链式 UI 布局的所有代码。仅仅 20 多行代码就完成了整个核心功能,是不是很棒?不需要引用任何第三方库,只要将这段代码拷贝到 Swift 工程中,你的工程马上就有了写链式 UI 代码的能力。

来个样例说明用法,比如,我们要在一个 view 中添加一个 label,这个 label 水平居中,它的 top 与父 view 的 top 相距 80。代码如下:


那么这段样例代码有什么好处呢?首先,从这段代码中,可以明显看到

UI 布局的步骤,而且添加约束和配置 view 的具体逻辑代码,被不同的 Closure

封装起来。代码的结构要比之前的写法,代码都用同一个缩进的代码结构要更清晰一些。当定位到 UI

的问题后,可以根据问题是布局错误还是配置问题,直接去分析对应的代码逻辑。

这样的代码还有一些别的好处,那就是 UI 配置相关代码的复用性提高。来,针对上面的代码的 config 部分做如下的修改:


完成与之前同样功能的代码,但是这里定义了一个类型为 (UILabel) ->

Void 的 Closure,并将其作为参数直接传递给了链式函数 config。想想,如果接下来新建一个名为 label2 的

UILabel,配置跟 label 一样,那个它的 config 函数直接接收这个 labelConfiger 的 Closure

作为参数就可以完成配置。你可以能会问,如果这两个 label 的文本值不一样的时候怎么办呢。这种情况,只需要将代码改动如下


没错。作为链式代码,config 函数可以多次对自身调用。不过需要注意的是,针对视图的同一个属性,后面调用的 config 函数会覆盖掉前面的 config 函数里设置的状态。

在 Swift 中,Closure 是可以被当做变量的,而且就连函数其实也是一种 Closure。所以,接下来就可以自由发挥啦~~

分析实现

来讲解一下链式核心代码的实现。 链式函数的关键就在于返回值类型与自身相同。

由于是

UI 相关,那么首先是对 UIView 这个视图父类做扩展。adhere 这个函数没啥可说的,就是讲自身添加到通过参数传入的父视图上。接下来是

layout 这个布局函数实现,由于是使用 SnapKit,而且具体的布局逻辑不同的视图也不一样。所以将使用 SnapKit

布局时的关键逻辑代码以 Closure 闭包参数的形式传入。这里比较难理解的是 config 函数的实现,可以看到这里定义了一个名为

ViewChainable 的协议,然后对这个协议做了一个扩展,并让 UIView 实现了这个协议。这么做的原因在于,为了让 config

函数的闭包参数在实际使用中,闭包的第一个参数类型可以具体到 UIView 的不同子类上。拿上面的例子来说,UILabel 的实例在调用

config 函数时,所需的闭包参数类型就是 (UILabel) -> Void 而不是 (UIView) -> Void。

优化


不知不觉文章已经很长了,不过还是希望你读下去。接下来是对上面的链式核心代码的优化。

由于上面的实现是直接针对

UIView 做了方法扩展,这在实际的工程中会碰到一个问题:命名冲突。举例来说,如果下一个版本的 SDK 更新后,UIKit 给 UIView

新增了一个 adhere 函数,参数和返回值类型和上面的定义完全一样,那如果发生这种情况,就只能修改自己之前的代码了。

Objective-C

时代的一个通行做法是对系统库的扩展方法加一个至少三个字符的前缀,这样函数名字就变成了这样:xxx_adhere

。当然你可以按照这种形式,给之前的链式 UI 函数名字都加上一个前缀,这么做是没有任何问题的。可是在 Swift 的世界里,有着更 Swifty

的实现形式,我在之前的博客文章 Swift 命名空间形式扩展的实现 中具体介绍这种形式的实现方式。那篇文章里提到的 HandOfTheKing

这个工程,里面已经包含了命名空间形式的链式 UI 代码实现。需要注意的是,在最近的一次更新里,我把这个项目的命名空间前缀由之前的 hk 改为了

hand,hk 用起来总感觉跟香港有关。哎,谁让命名是编程世界的一大难题呢~~下面贴出具体实现的代码(这里不包含命名空间的实现)


改成这个之后,之前的 label 的代码就变成了下面的风格:


进阶

如果你对响应式编程感兴趣,你会发现 RxSwift 这个框架和上面的链式布局代码简直是绝配。RxSwift 这个库本身也是支持很多链式调用的。关于 RxSwift,这又是另外一个大坑了,本篇就不多讲了。

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

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,089评论 4 62
  • 小时侯看《格林童话》看的是热闹,现在再看格林童话看的是惊艳。 1.Rapunzel《长发公主》 这个故事告诉我们两...
    无非草木阅读 6,052评论 5 3
  • 文:黑珍珠 背景音乐:《叹十声》(古筝) 平常的一天,但是又有一点不平常。不仅仅是因为昨天受了刺激,让我惊醒...
    映月黑珍珠阅读 423评论 0 3
  • 我很累 也很烦这种乱七八糟的关系 为什么人总有那些什么七大姑八大姨的狗亲戚 梦的世界总是会醒的 人总是不会安于现状...
    残浅阅读 333评论 0 0
  • Tasmanian Laurel (Anopterus glandulosus) is a medium-size...
    飞鸿踏泥阅读 188评论 0 0