复杂界面?搭积木般轻松

一、前言

  • 抱歉这段时间比较忙,博客好久没更新了,今天有空就谈谈一种界面搭建方式吧!本文所说得复杂界面针对的是表单界面,表单界面常见的就是UITableViewUICollectionView,因此本框架也是针对这两个进行封装,完整框架是使用Swift 3.0 编写,当然也有Objective-C 版本,不过功能目前相对少点,待完善。

  • 至于说复杂,是针对以后的界面维护(比如界面元素的增删改)、数据源的更新(内部刷新以及外部刷新),如果是一般的做法,这种操作都会大大增加控制器的代码量,逻辑紊乱,控制器就十分臃肿,迭代几个版本之后,改起来估计就要哭了。

  • 如下图,数据以及界面元素更新都涉及到,但我控制器的代码只有200多行(Objective-C编写),操作模块变得十分轻松,gitHub地址 觉得OK就点个Star呗。

示例演示

二、界面元素变更

  • 这里的界面元素变更,就是说表单界面需要动态插入、删除、修改某一个模块,或者交换两个模块的位置等等。

  • 常见的做法是,因为数据源是在控制器中获取,那么操作数据源一般也会在控制器中处理,所以控制器处理界面元素变更,就需要写很多逻辑代码!如果此时有类似的控制器也需要处理,那么就造成代码冗余了,想想都可怕。那么此时就会想,如果界面是一个一个独立模块搭建而成,本文称为Component,每个Component自己处理自己的逻辑,意味着一个界面维护一个Component集合。至于外部需要修改界面元素,就只需要操作这个集合就好,对应数组的addObjectremovereplaceexchange 即可,详细后面会说。


三、数据源更新

此时说得数据源更新是针对一个Component来区分的,因为上文提过,界面的搭建是一个一个独立Component来构造。

  • 1、内部更新:什么时候需要内部更新?比如当你一个Component里面带有状态更新(好比如按钮的点击选中)、输入内容更新等等,这时候就只需要Component自己处理就好,控制器也不需要关心这个处理过程,只需要关心最终处理后的结果。比如上图中的所有输入、按钮的点击,控制器是不会做任何处理的,独立的Component只需要最终告诉控制器,目前的文本是什么,按钮的状态是什么。

  • 2、外部更新:外部更新就是由外界更新模块,常见的就是服务端拉去数据,控制器控制去更新指定的模块,或者模块与模块之间的通讯,也是通过控制器去实现更新,比如上图中的选择标签视频标签以及自定义标签,三个Component模块的通讯,数据更新的操作发起是通过控制器,当然,具体处理逻辑还是Component自己实现,因为控制器根本不关心逻辑的处理细节,只在乎结果(控制器就是这么吊),日后的维护只需要修改对应的模块即可。这点就类似胖Model的做法了。

此时值得提一下,因为数据更新就必须获取到指定的Component,当然你会想到遍历Component集合,判断当前是哪个Component类,但是有一个问题,如果整个表单都是用同一个Component,只是数据展示不一样而已。那么你遍历拿不到具体的哪个Component,因此内部是引入了一个 Component Identifier 的东西,这个identifier并不是类似cell的重用标识,而是表示Component的唯一标识


四、如何实现Component化

  • 上图就一目了然了
Process
  • 1、Component只负责管理 CellCellHeaderViewCellFooter,就是对应 TableView 的一个组,那么一个表单的构成,就是由多个 Component 组成

  • 2、此时引入了一个 Handler 去处理 Component,但前面说了,一个表单维护一个Component集合,表单的创建一般都在 Controller 里面,那就意味着一个Component集合就通过 Controller 去管理维护,其实是不好的!

    • (1)这种处理方法,本质上还是由控制器去处理一系列操作逻辑,那么Component 的作用就几乎为零。
    • (2)当然,通过继承控制器来解决代码冗余也没毛病,但我觉得,这并没有从根本上瘦身控制器。我个人也不太喜欢继承,当然,并不是不使用继承!因此引入Handler,一个Handler专门处理一个表单的所有 Component的增删改操作,然后控制器去维护一个Handler

五、面向协议编程

因为iOS中常见表单有两种:一种使用TableView,一种使用CollectionView。两个表单都有共通的地方,例如:cell、headerView、footerView 的注册事件、dataSource数据源事件、delegate事件等等。那么共通的事件不可能每个类都写一遍的吧!那么此时就有两种方法了:

  • 继承基类:继承这种方式在这里其实没什么毛病,因为某些公共的方法都是必须的,基类可以方便统一管理

  • 协议:部分方法虽然都差不多,但可能形参不一样,返回不一样,那就把这类方法使用一个协议去管理,一方面可以方便移除,如果是基类继承就没办法移除了,当然 swift 有泛型处理,此时也可以统一管理不一样的入参以及返回,只需要确保遵守同一个协议就行。但这样外部使用就需要做类型判断,使用起来相对麻烦了

因此本文是一分开两个协议,协议中就是一些数据源事件以及代理事件;然后公共的就使用继承基类的方式实现,比如注册事件(cell、header、footer的注册都在这处理)、刷新Component事件等。所以本框架的目录结构如下:


目录结构

六、框架分析

1、Component

Component分析
  • (1)、上文说了,一个Component就是对应一个表单的Section,因此必须对应有Section表示当前Component所在下标,包含Cell headerView、Cell、Cell footerView三块,因此要处理cell,必须要传入当前的表单(TableView或CollectionView),当然还有很重要的Component Identifier,如果外界不指定,框架默认会有对应的identifier,如果是相同的Component,那么Identifier就会根据Component的section拼接后缀。

  • (2)、注意,虽然传入的表单对象,但表单的数据源以及代理并不是Component去做的,Component只是做了第一层数据处理,处理注册事件、展示控件的创建(Cell、Cell headerView、Cell footerView)以及基本数据配置(Cell、Cell headerView、Cell footer 的高度)等

  • (3)、使用的时候,不建议直接使用基类(BaseComponent)创建,可以理解为它是一个抽象类,先初始化一个子类的Component,然后重写一些父类的方法,比如注册的register,数据源的numberOfItemscellForItem等等

    Component协议部分Api
    override func register() {
        super.register()
        self.collectionView?.registerNib(DemoCollectionViewCell.self, withReuseIdentifier: cellIdentifier)
    }
    
    override func numberOfItems() -> NSInteger {
        return 12
    }
    
    override func cellForItem(at item: Int) -> UICollectionViewCell {
        let cell : DemoCollectionViewCell = super.cellForItem(at: item) as! DemoCollectionViewCell
        cell.backgroundColor = UIColor.red
        cell.textLabel.text = stringFromSize(at: item)
        return cell
    }

2、Handler

上文说了,Handler的作用就是做Component和Controller的中介,Controller维护一个Handler,而Handler就负责帮Controller处理一系列逻辑操作。

  • (1)、Handler通过维护一个Component集合来管理各个Component的逻辑操作,当然,Component集合的来源还是需要Controller提供,然后通过Component集合来实现表单的数据源方法、真正处理相关控件的展示、以及表单的展示样式等。

  • (2)、上文中提过一个重要的点,就是 Component Identifier,此时Handler内部维护一个表,通过外界或者内部提供的 Component Identifier来绑定对应的Component,后续就很容易通过唯一的Identifier来获取对应的Component,进而处理多个Component的通讯问题。

  • (3)、此时为了简便Component的操作(这个就是对应界面的元素变更),框架对应两个协议,这里为什么不用泛型而是写两套协议,原因跟上文提及的一样,为了让外界调用少些逻辑判断。协议中提供了Component的各种操作,应该都能满足大众需求,这部分代码没有做注释,看Api完全看得懂哈

    Componet集合的逻辑操作
  • (4)、用法很简单,在对应需要创建表单的界面中强引用一个 Handler,设置数据源给Handler,如果你需要处理Cell、Cell headerView、Cell footerView的点击事件,遵守代码并设置Handler 的代理为当前的Controller,然后实现相关方法即可,当然,前提下你还是需要创建表单对象的

private(set) var handler : FLTableViewHandler = FLTableViewHandler()
   var components : Array<FLTableBaseComponent> = [] {
        didSet {
            handler.components = components
        }
    }
handler.delegate = self

3、Controller

当然,虽然不喜欢使用继承,但很多时候继承确实好用,因此也提供了一个BaseController,此时如果你的界面只是一个表单,你完全可以继承自它,然后通过它的Handler去处理表单界面的元素变更以及Component之间的通讯


七、细节功能点

  • 1、本文提供了针对CollectionView表单的一种自定义Flowlayout,目前只有两种情况,左对齐布局或者是系统的中心对齐布局,并实现了CollectionView表单的Cell的HeaderView和FooterView的计算显示,简化Api的调用

  • 2、针对TableView表单,Cell headerView、Cell footerView的代理方法中只能返回String类型,除非自定义HeaderView、FooterView,框架中新增了可以直接返回 NSMutableAttributedString ,方便使用富文本显示

  • 3、框架中做了很多数据判断,避免出现数据操作造成的崩溃

  • 4、本文虽然主要针对是Swift,但也提供了针对Objective-C版本,但目前Objective-C只有针对TableView表单的处理,当然,CollectionView表单处理是一样的思路。


八、总结

  • 1、本文并没有过多讲解具体代码的实现,而是偏向于讲解思路,减少代码冗余,而且极其方便后续开发维护,想看具体演示,请看 Demo。

  • 2、如果上文有说得不对的地方或者有问题建议,请大家留言,如果你觉得我文章不错,欢迎关注我,点个like和star,谢谢支持!

    gitHub地址

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

推荐阅读更多精彩内容