戏说CollectionView、TableView如何优雅应对变化的界面展示

前言

App里很多页面都是用UICollectionView和UITableView做的,比如一些列表类的、详情类的,甚至是商城首页等等,只要是分块的,特别是根据数据源不同而展示不同的页面,都很适合用,但怎么用,使它容易修改、扩展,是个问题。

产品经理老李说

  1. 这一块功能有数据的时候就展示,没有就隐藏
  2. 这一块挪到那块下面
  3. 这一块XXX功能删了,现在不需要了
  4. 之前删的XXX功能补回来吧,然后改成放在XXX功能块下面
  5. 这块地方的高度根据内容动态设置
  6. ......(数不尽的小数点)

程序猿懵了

  1. 咦,我之前这个Section放的是什么,看界面的话不准确,有些隐藏之类的情况,界面上这块东西对应的是哪个Cell,也不确定了。
  2. Delegate和DataSource,少的时候只实现三四个方法,多的时候实现七八个,这些都和Section、Cell的各项信息有关,这时候做什么变动都需要改动多处地方,if...else...也就写得到处都是。
  3. ......(数不尽的小数点)

这些麻烦不难解决,就是有时候会很花时间,效率低,于是我想了一个方法解决它。

最初是在做某一类详情的时候想出来的,后面经过应用到其他页面和几次变更的实践之后发现,还真挺方便的!下面将举几个例子来说明这个方法及其特点。

本想将本文语言组织得有趣些,但可惜功力不够,时间也紧哈,下次再尝试。

例子一

产品经理老李说:“给我实现xxx功能,有5部分信息,其中A、C、D功能没有东西的时候是隐藏的......”

程序猿答道:“哦......”

转化成技术需求

有五种Cell,分别是ACell(根据data.a判断隐藏与否)、BCell(默认有)、CCell(根据data.c数组个数判断隐藏与否,并且根据个数设置cell大小)、DCell(根据data.d数组个数判断隐藏与否)、ECell(默认有),隐藏与否顺序都是A\B\C\D\E。

笨拙的实现

class XXXController: UICollectionViewController {

    let data = ......
    override func viewDidLoad() {

        super.viewDidLoad()
        collectionView.reloadData()
    }
}

extension XXXController: UICollectionViewDelegateFlowLayout {

    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {

        // switch 或 if...else...
        switch indexPath.row {
        case 0:
            // 判断是A还是B
        case 1:
            // 判断是B还是C还是D还是E......(心里骂着产品经理)
            // 这里很容易犯错,细想下你就知道了......(又被扣工资了囧)
        case 2:
            // 判断是C还是D还是E......(心里骂着产品经理)
        case 3:
            // 判断是D还是E......(心里骂着产品经理)
        case 4:
            // ECell
        default:
            // 还要写default......
        }
    }
}

extension XXXController {// UICollectionViewDelegate、UICollectionViewDataSource

    override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        // 根据数据源计算分别有没有A/C/D
        // 一堆if...else......(心里骂着产品经理)
        return 2 + ......
    }

    override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        // 同sizeForItemAtIndexPath(心里骂着产品经理)
    }

    override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
        // 同sizeForItemAtIndexPath(心里骂着产品经理)
    }
}

优雅的实现

enum XXXItemTypes {
    case A
    case B
    case C(count:Int)
    case D
    case E
    ......
}

class XXXController: UICollectionViewController {

    let data = ......
    var itemTypes:[XXXItemTypes] = []
    override func viewDidLoad() {

        super.viewDidLoad()
        configItemTypes(data)
        collectionView.reloadData()
    }
}

private extension XXXController {
    func configItemTypes(data) {

        itemTypes = []
        if let _ = data.a {// 根据对象是否存在判断隐藏与否的情况
            itemTypes.append(XXXItemTypes.A)
        }
        itemTypes.append(XXXItemTypes.B)// 默认有的情况
        if let cCount = data.c.count where cCount > 0 {// 根据个数判断隐藏与否的情况
            itemTypes.append(XXXItemTypes.C(cCount))
        }
        if let dCount = data.d.count where dCount > 0 {
            itemTypes.append(XXXItemTypes.D)
        }
        itemTypes.append(XXXItemTypes.E)// 默认有的情况
        ......
    }
}

extension XXXController: UICollectionViewDelegateFlowLayout {

    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {

        switch itemTypes[indexPath.row] {
        case .A:
            return ACell.cellSize(......)
        case .B(let count):
            return BCell.cellSize(count: count)
        case .C:
            return CCell.cellSize(......)
        case .D:
            return DCell.cellSize(......)
        case .E:
            return ECell.cellSize(......)
        }
    }
}

extension XXXController {// UICollectionViewDelegate、UICollectionViewDataSource

    override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return itemTypes.count
    }

    override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {

        switch itemTypes[indexPath.row] {
        case .A:
            ......
        case .B:
            ......
        case .C:
            ......
        case .D:
            ......
        case .E:
            ......
        }
    }

    override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
        switch itemTypes[indexPath.row] {
            ......
        }
    }
}

小分析

可以看到,所有的判断(4处地方)都归结一处,也就是configItemTypes里面,省去了一堆因为隐藏与否而做的判断逻辑,而且不会出现复杂的判断的逻辑出错。

例子二

又是产品经理老李说:“D功能很重要,放在第一处吧,这很容易实现吧!”

程序猿答道:“嗯......”

笨拙的实现

每个UICollectionViewDelegate、UICollectionViewDataSource、UICollectionViewDelegateFlowLayout的方法里面的判断逻辑都修改......

磨刀霍霍向老李

优雅的实现

只需修改configItemTypes,将DCell的判断逻辑放到最前面先判断。(这是真的吗??就这么简单??)

小分析

任何顺序变化都可以通过简单修改configItemTypes而实现,不改动其他地方。

例子三

老李又来了:“把C功能去掉......”

程序猿答道:“嗯......”

笨拙的实现

同例子二,又要重新看那段判断逻辑。

优雅的实现

同样只需修改configItemTypes,将添加CCell的部分删除。(好崇拜自己啊!!!!!!!!)

小分析

任何增加和删除都可以通过简单修改configItemTypes而实现,不改动其他地方。

进阶例子

上面的几个例子都是没有Section的,如果再加多一层Section,那么复杂的程度就上升很多了,这里就不再赘述了。主要说下这种情况下枚举的数据结构设计,可以有很多种,下面是我的一个实现,还可以更优化。

enum XXXSectionType {
    case SectionA(rowTypes:[XXXRowType])
    case SectionB(rowTypes:[XXXRowType], index: Int)
    case SectionC(rowTypes:[XXXRowType])
}

enum XXXRowType {
    case RowA
    case RowB
    case RowC
}

这种枚举的方法在有section的情况下更彰显了它的价值!依着上面的几个例子的需求尝试下就能体会得出来了。

总结

从上面的几个需求和处理可以慢慢抽象出这麻烦的问题所在:
=>Section/Cell的分布情况没有统一
=>导致Delegate和DataSource的多处方法需要进行判断
=>导致展示变化时Delegate和DataSource的多处方法都要修改
=>导致修改辨识麻烦
=>导致效率低

枚举的方法将所有分布情况都放在了分布数组的初始化上,Delegate/DataSource不需要通过indexPath去判断自己当前是哪个row/item,实际上就是将未知的麻烦的indexPath抽象出一维数组或者二维数组来表示它们的分布,并且每个分布情况所需的设置(如Cell高度、Cell样式、Header高度等等)都是固定的,所以任何变动只需修改那个分布数组。

看似简单的抽取,却处理掉了很多麻烦的问题和冗余的处理!

-END-
欢迎到我的博客交流:http://zackzheng.info

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

推荐阅读更多精彩内容