本文正在参加「金石计划 . 分割6万现金大奖」

前言

上一篇,我经过Protocol去封装入参,抹平了入参之间的差异。

今天这篇依然围绕一个我遇到的事务场景,给我们供给一种思路——运用enum抹平数组元素差异。

事务场景

我先说明一下事务场景:

  • 页面是一个有限能够滑动的的页面(后面咱们会分析到其实无限或许有限都无所谓)

  • 页面的每一个子View都是和后台回来的数据绑定,其JSON大致能够理解为下面这种方式:

{
    "aPart":{
        "some":""
    },
    "bPart":[
        {
            "label":"",
            "value":""
        },
        {
            "label":"",
            "value":""
        }
    ],
    "cPart":[
        {
            "iconUrl":"",
            "text":"",
            "functionType":""
        },
        {
            "iconUrl":"",
            "text":"",
            "functionType":""
        }
    ],
    "dPart":{
        "name":"",
        "age":""
    },
    "deviceType":"",
    "serviceAble":""
}

事务需求如下:

  • aPart对应aView,bPart对应bView,cPart对应cView,dPart对应dView,能够如此穷举下去

  • 每个part的JSON结构或许都不相同

  • 后台下发JSON的时分,会依据用户账号的状况,回来不同数据,比方aPart的事务没有,后台会回来"aPart":null,App的aView需求躲藏,存在或许多个part都回来为null的状况,比方"bPart""cPart"都为null的状况,那么App的bView和cView需求躲藏。

那么问题来了,iOS端怎么构建一个能够灵敏配置的界面?

分析

用什么控件

运用UIScrollView的分析

作为开发App页面的第一点,选取适宜的控件是非常重要,由于控件决定了终究数据源的方式。

由于后台数据回来的并不是一个JSON数组,一起又是有限的数据,很多人优先会考虑经过UIScrollView去进行页面的构建,我一开始也是这么想的,可是麻烦的是这一点:

后台下发JSON的时分,会依据用户账号的状况,回来不同数据,比方aPart的事务没有,后台会回来"aPart":null,App的aView需求躲藏。

也便是说你把aViewbViewcViewdView贴在了scrollView上面之后,需求依据后台的数据躲藏页面,乃至更新布局逻辑,每一个view都有2种状况,假设后台有4个事务数据,那么那便是2的4次方——32种或许。

其实仅躲藏仍是显现非常简略,可是假如是运用SnapKit布局,那么更新view的布局,或许就并不是特别好了,一起假如这个页面后续还有新的事务数据,那么就子view会持续添加,保护本钱也会越来越高。

所以,到此运用UIScrollView的计划,别否决了,并不是说它不能构建,而是本钱有些高,而且不行灵敏。

所以剩余的只剩余一种挑选了——运用经过数据源绑定UI的UITableView(补白:其实运用UICollectionViewUITableView都一样,只是多一个瀑布流布局罢了)。

运用UITableView的分析

运用UITableView的优势:

完完全全经过数据去驱动页面,页面是否显现完全经过有无数据决定。

可是数据源相较一般模型有着愈加严厉的数据格式——数组!

而且数组的每个元素最好都是相同的数据类型,由于假如运用[Any]这样去表达一个数据源,本钱太高。

好了,已然咱们定下了运用UITableView来进行页面构建,那么剩余的难点也就来了——怎么将后台的数据加工成为一个好用的数据源?

加工数据

将后台数据加工成为一个数组并不难,关键是统和数据类型才是难点。

在上一篇文章里面,我经过Protocol去统和Model[String: String],在这次状况下面可不可行呢?

protocol EraserConvertible {}
struct Response: Codabel {
    let aPart: APart?
    let bPart: BPart?
    let cPart: CPart?
    let dPart: DPart?
}
struct APart: Codabel, EraserConvertible {}
struct BPart: Codabel, EraserConvertible {}
struct CPart: Codabel, EraserConvertible {}
struct DPart: Codabel, EraserConvertible {}

咱们回头看看这个JSON,你不得不认清这样个现实,每个Part都是独立的,完全看不到任何相关,假如想要做类型共同EraserConvertible这个协议很难满意。

不如做向上类型统和,也便是说数据源变成let dataSource = [Response]()这种方式,在tableView的数据源办法中获取单个数据,然后在进行分析:

let dataSource = [Response]()
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let item = dataSource[indexPath.row]
    if let aPart = item.aPart {
        /// 构建aCell
        let cell = tableView.dequeueReusableCell(withIdentifier: aCell.className)!
        /// 将aPart赋值给aCell
        cell.aPart = aPart
        return cell
    }
    if let bPart = item.bPart {
        /// 构建bCell,
        let cell = tableView.dequeueReusableCell(withIdentifier: bCell.className)!
        /// 将bPart赋值给bCell
        cell.bPart = bPart
        return cell
    }
    .
    .
    .
}

嗯,这样看起来非常不错,只是每个item都包含了aPart到dPart,计划的是可行。让咱们想想有没有愈加高雅的思路呢?

数据源的类型不同决定了不同的Cell。咱们可不能够用状态来表明?

于是乎我写下了这样的代码:

enum BusinessPart {
    case a
    case b
    case c
    case d
}

数据源变成let dataSource = [BusinessPart]()这种方式,那么怎么区别每个case不同的数据呢?

Swift的enum是能够带参数的,而且单个case带不带参数,带什么类型的参数都很自在。

于是乎,咱们接着改造BusinessPart

enum BusinessPart {
    case a(APart)
    case b(BPart)
    case c(CPart)
    case d(DPart)
}

这样我现在就经过enum抹平的数组元素的差异,接下来只要把后台数据架构成为我想要的[BusinessPart]格式就好了,这儿放处理逻辑:

private func process(model: Response) -> [BusinessPart] {
    var array: [BusinessPart] = []
    /// 非空才参加数组,后台回来null,此时aPart为nil,那么aCell也不会出现
    if let aPart = model.aPart {
        array.append(.a(aPart))
    }
    if let bPart = model.bPart {
        array.append(.b(bPart))
    }
    if let cPart = model.cPart {
        array.append(.c(cPart))
    }
    if let dPart = model.dPart {
        array.append(.d(dPart))
    }
    return array
}

TableView的数据源办法中这么运用:

let dataSource = [BusinessPart]()
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let item: BusinessPart = dataSource[indexPath.row]
    switch item {
        case .a(let aPart):
        /// 构建aCell
        let cell = tableView.dequeueReusableCell(withIdentifier: aCell.className)!
        /// 将aPart赋值给aCell
        cell.aPart = aPart
        return cell
        case .b(let bPart):
        /// 构建bCell
        let cell = tableView.dequeueReusableCell(withIdentifier: bCell.className)!
        /// 将bPart赋值给bCell
        cell.bPart = bPart
        return cell
        case .c(let cPart):
        /// 构建cCell
        let cell = tableView.dequeueReusableCell(withIdentifier: cCell.className)!
        /// 将cPart赋值给cCell
        cell.cPart = cPart
        return cell
        .
        .
        .
    }    
}

至于这个[BusinessPart]的dataSource怎么在UITableViewDataSource中怎么处理,亦或许在BusinessPart的分类中怎么处理,这个便是一个仁者见仁智者见智的问题了。

总结

到最终,这个页面的数据加工,最终仍是变成了向上仍是向下统和的思考

其实便是关于一个差异性特别大的数据,怎么比较好的整合为一个在iOS中适宜的数组问题,经过字段的全覆盖达到模型的统合,抑或经过Swift中枚举带参的特点,抹平差异。

从代码层面上看,两者的代码量差不多,可是运用enum抹平数组元素差异为咱们供给一个新的解题思路,至少这是我自己思考的效果。

别的一个角度便是经过布局控件来打开,由于我看Android便是直接用一个ScrollView撸起的:

Android开发中,大部分控件都有visibility这个特点,其特点有3个分别为“visible ”、“invisible”、“gone”。主要用来设置控制控件的显现和躲藏。

invisible当控件visibility特点为invisible时,界面保留了view控件所占有的空间;而控件特点为gone时,界面则不保留view控件所占有的空间。

由于Android这个三个特点能够非常便当的完结躲藏仍是不躲藏,以及是否保留控件所占有的空间。

而iOS或许需求不只改动isHidden特点,乃至连view的frame也要进行改动,本钱太大了。在运用UITableView的分析现已说到。

可是也不是不或许,比方运用FlexLib应该能够完成。(补白:我自己没用过FlexLib库,这个有待考证哈),可是也添加了必定的学习本钱与引入了更多的库。

参考文档

Swift:enum你会用吗?

自己写的项目,欢迎我们star⭐️

RxStudy:RxSwift/RxCocoa框架,MVVM形式编写wanandroid客户端。

GetXStudy:运用GetX,重构了Flutter wanandroid客户端。

本文正在参加「金石计划 . 分割6万现金大奖」