你的位置:首页 > 操作系统

[操作系统]iOS开发之省市二级联动的数据组织(PHP版)以及PickerView的实现与封装


之所以要发表这篇博客,还源于最近的开发工作所实现的一个小的Demo, 当然这个Demo不会涉及工作中App的一些内容,下方要实现的Demo是通用的。因为项目需求的迭代,要求在银行卡绑定中添加支行所在的省市信息。在iOS中选择这种省市信息的一个比较不错的方式当时是使用UIPickerView进行显示了。当然在PickerView上的省市信息是联动显示的,我们在此因为需求定的是让用户选择省市信息,所以我们进行二级联动,当然多级联动的原理也是一样的。由于之前的老项目是使用Objective-C写的,虽然现在是Swift与OC混编,不过要在OC实现VC上添加新的功能还得用OC来实现呢,所以今天的博客的Demo咱就不用Swift来实现了,不过原理上是一样的。

下方的的截图就是我们今天博客中要介绍的Demo的运行效果,我们今天的博客就是生成PickerView所需的数据,以及对下方这个PickerView进行封装。从下方的动画中我们不难看出,在第一列选择省后,第二列会自动的显示该省下的所有地级市。点击完成后,会在上方相应的Label中显示出你所有选择的省市以及该省市所对应的编号。具体的请看下方这个粗劣的动画。

  

 

一、数据源的生成(从Excel到Plist)

1.组织数据的前奏

在封装上述PickerView控件之前,我们得有数据不是,也就是我们得有省市的名称,各个省市所对应的编码,以及省与市的对应关系。当然这些数据在网上一抓一大把,权威的数据要看"国家统计局"所提供的数据了。下方这两个截图是一个Excel表格中的两个Sheet,是我们服务端的一个程序媛给的,算是客户端与服务端的一个标准吧,估计也是从网上下载的。下方的省市信息以及编码当然与国家统计局提供的一致了,这个毋庸置疑。

我拿到这个Excel表格怎么用呢?我就想通过OC或者Swift来直接解析excel表格来读取数据,然后处理成我想要的格式。不过经过一番了解后,感觉该解决方案颇为复杂,于是乎就另寻他路。又于是乎,想起了之前用过的PHPExcel这个框架,因为之前做PHP开发的时候使用过PHPExcel来读取Excel文件。这个PHPExcel使用起来还是蛮顺手的,用起来也不复杂,于是乎我就决定使用PHPExcel来读取下方这两个Sheet中的数据。

使用PHPExcel读取数据后,重新将数据进行关联组织并生成json提供给iOS这边使用。iOS这边获取到Json后,将其进行解析后存储到plist文件中,这样我们就可以从plist文件中来获取“省市”相关数据了,然后我们就可以封装我们的PickerView了。今天博客就一步一步的来完成这个东西。当然你也可以使用SQLite数据库来存储下方Excel中的数据,create两张表,一张放省,一张放市,使用外键进行一对多的关联即可。使用SQLite数据库是另一种解决方案,在此我们使用的是plist文件,因为相对简单吗,因为数据少,plist文件度过了就可以在我们的pickerView上使用了,如果你想使用SQLite也是相当OK的,此篇博客值提供plist文件这种解决方案。

  

 

2.使用PHPExecl读取省市Excel数据

在上面的Excel数据中第一个Sheet中存储的是每个省以及每个省所对应的编码,而第二个Sheet中是存储的每个市和市的编码,并给出了每个市所在的省。接下来我们要使用PHPExcel这个第三方框架对上述Excel的数据进行读取,关于PHPExcel的东西请看其官方文档,地址为:https://phpexcel.codeplex.com/。下方代码就是我们使用PHPExcel读取上述Excel文件的代码了,并且将上述数据进行处理,将处理后的数据进行json编码。下方我们将介绍相关的PHP代码。

 

(1)加载PHPExcel框架以及省市excel文件--province.xls

下方的PHP代码片段就是加载PHPExcel框架,以及通过PHPExcel_IOFactory来创建文件读取器对象$objReader, 并公告$objReader对象来加载我们的province.xls文件。打开后会返回一个操作Excel文件的一个文件句柄对象$objPHPExcel,我们可以通过$objPHPExcel来操作已经打开的Excel文件。具体代码如下所示。

  

 

(2)通过上述$objPHPExcel对象来读取Excel文件内容

接下来我们要通过$objPHPExcel这个操作文件的对象来获取province.中的数据。下方对主要的代码添加的注释,应该还算是清晰。在下方代码片段中$dataArray数组我们用来存储province.中所有Sheet的数据。我们循环了两次来打开该Excel中的两个Sheet,通过$objPHPExcel对象的setActiveSheetIndex()方法通过索引来选择相应Sheet(从左到右,从0到n),并通过该对象的getActiveSheet()方法来获取当前选择的Sheet,选择后返回一个$objWorksheet对象,我们可以通过$objWorksheet对象来读取当前Sheet中每行每列的数据。

我们通过foreach来迭代当前Sheet中的每行数据,同样适用foreach来迭代一行中每列的数据。我们将每列的数据存入$tempRowArray数组中,然后在将每行的数据即$tempRowArray存入到Sheet数据的$tempSheetArray中。最后将当前的Sheet的数据$tempSheetArray存入到$dataArray中。具体实现如下:

  

 

3.数据的验证

通过上述步骤,我们就可以将Excel中的每个Sheet中的数据存入到我们的数组中。其中的数据结果是这样的: $dataArray中存储的是每个Sheet($tempSheetArray)的数据,Sheet($tempSheetArray)中又有多行($tempRowArray),每行($tempRowArray)中又有多列。所以dataArray就对应着整个Excel表格。我们也就获取了所有的Excel数据。经过上述代码,$dataArray中就存储了Excel的数据。为了保险起见我们将$dataArray中的数据进行打印,下方是我们的测试代码。

还有在我们写程序时呢,为了减少bug量,以及减少调试bug的难度,我们一定要养成一边写代码一遍调试的好习惯。这样会及时发现bug并修正,写好一个小的功能模块我们就对其进行测试,如果出了问题就很容易定位bug的所在之处。下方代码就是对上述代码的测试:

  

上述代码对$dataArray中存储的数据进行了一个打印,可以帮助我们查看我们$dataArray中所存储的数据是否符合我们的预期。下方的输出结果就是我们上述的测试用例所输出的结果,上面红框中是第一个Sheet中的数据,下方的是第二个Sheet中的数据,我们大体上一看符合我们的预期,就说明我们之前的代码没有什么问题,我们就可以对$data中的数据进行关联并生成JSON数据了。

  

 

4.省市数据进行关联

上面我们已经将数据从Excel中读取出来了,并且将量Sheet中的数据存入了不同的数组,接下来我们将要对数据进行处理。该部分就是将省市的数据进行关联,也就是将两个Sheet中的数据合并成一个数据块。下方就是我们要存储数据的一个结构图。整个是一个数组,数组中是一个字典,每个字典就代表一个省。每个省的字典又省编码Code、省名Name、所有市Citys组成。Citys中存储的又是一个数组。该数组中的每一项又是一个字典,此处的每个字典代表着一个市,每个市的字典中有包括市名Name和市编码Code。数据结构如下所示。

  

参考上图,我们要对读取的数据进行处理,将数据重新组织成上述结构。下方代码段就是对读取的Excel表格中的数据进行重组。经过下方代码的处理我们就可以得到上述结构的数据了。下面的$allDataArray就存储的是所有的数据信息,$provinceTempData中暂存着每次省的所有信息,$currentProvinceCitys中存储的就是当前省中所有市的信息。第二个循环中的if语句则负责管理省市间的关系了,具体代码以及代码注释如下所示。

  

经过上面的代码我们所有的数据就会存入到$allDataArray中,上面对$allDataArray进行了Json编码并输出,下方就是处理后输出的Josn数据。在此我们以河北省为例。下图中的结构是与上面我们数据结构图一一对应的,这正是我们想要的数据。到此我们数据处理的任务就完成一大半了,因为我们得到了我们想要的JSON。

  

 

5. 将上述JSON数据进行解析并存入Plist文件

经过上述步骤,PHP的工作算是告一段落。接下来我们就是要使用iOS客户端来访问上述地址,获取上述生成的JSON数据。获取到JSON数据后,我们将JSON数据进行解析,并存储到沙盒中的plist文件。这样我们就可以从plist文件中来加载我们的省市数据了。

下方代码段是我们的iOS客户端的代码,该代码通过NSURLSessionDataTask来请求上述PHP代码所在的文件获取省市的JSON数据。请求到JSON数据后对数据进行解析,将JSON数据解析成数组后在通过NSFileManager存储到沙盒中的PList文件中。如果你要在外部使用,只需要找到模拟器中的沙盒路径拷贝出plist文件即可。下方代码就是网络请求+JSON解析+Plist文件存储的的代码。

  

经过上述代码的执行,你会在你的模拟器中上述App的沙盒中发现一个叫province.plist的文件,该文件中存储的就是我们要使用的省市数据。该plist文件的数据存储结构是我们上面的介绍过的数据结构,下方就是该plist文件中数据的部分截图。至此我们就获得了一个按我们的预期存有省市数据plist文件了。

  

 

二、封装选择省市的PickerView的使用方式

封装当然不是简单的将PickerView的简单使用,在封装代码时我们要考虑到用户的易用性和可扩展性。在此我只对PickerView做了一个简单的封装,不过干货还是有的,主要是思想呢。经过上面一大模块的数据组织呢,我们就可以将之前服务端所给的Excel文档中的数据组织成我们想要的plist数据。本部分所做的主题就是读取plist文件中的数据,将该数据显示在二级联动的PickerView上供用户选择。用户选择完成后返回用户选择的省市名以及省市所对应的编码。开始我们控件的封装。

 

1.所封装控件的目录结构

首先我们先整体的看一下我们所封装控件的目录结构是怎样的,先整体的了解一下我们这个封装的控件。下方截图中就是我们所封装控件的目录结构了,因为我们是对显示省市信息的UIPikcerView进行的封装,所以在此我们称其为ProvincePickerView,ProvincePickerView就是我们所封装的组件了。用户只需要对其进行实例化并添加到其视图上就可以进行使用了。下方的province.plist数据就是我们上面所生成的存有省市信息是数据,我们ProvincePickerView中的数据源就是province.plist文件。

ProvinceModel就存储着当前选中的省市的名称以及编码,下面第二张截图就是ProvinceModel中的内容了。provinceCode存储的是当前选中的省的编码,provinceName存储的就是当前所选省的名称,cityCode存储的是所选市的编码,cityName存储的是所选市的名称。具体代码如下所示。

   

  

 

2. 所封装控件的初始化以及调用方式

接下来我们看一下我们封装的这个ProvincePickerView的使用方式,使用起来还算简单。下方代码段就是ProvincePickerView初始化方式,将ProvincePickerView进行初始化然后添加到所要显示ProvincePickerView的视图上,然后设置ProvincePickerView对象的Block回调。该回调会在用户点击ProvincePickerView上的完成按钮时执行,并返回当前用户选中的省市信息的Model数据。

  

上面是初始化ProvincePickerView,并且设置数据回调Block。下方的代码段就是用来显示ProvincePickerView的,调用showPickerView方法就会在下方弹出ProvincePickerView,因为ProvincePickerView本身就有取消按钮,取消后就会自动收回ProvincePickerView,所以我们不需要为用户提供收回ProvincePickerView的方法,所以使用起来还是比较简单的。

  

 

三、代码分享

由于博客篇幅有限,至于ProvincePickerView中封装的代码在此就不一一的往上粘贴了。说白了最核心就是对UIPickerViewDelegate和UIPickerViewDataSource两个代理中的相应的方法的封装。还有就是如何显示和隐藏PickerView,换一句话说,就是讲PickerView放在什么地方进行显示。有感兴趣的小伙伴可以从下方的github中分享的代码来自行分析呢。事无巨细,所以在此就不做过多的赘述了。

下方的代码截图就是在github上分享的部分代码,可以说是添加了详细的注释,有感兴趣的小伙伴可以进行自行阅读。代码中如有偏颇之处,还望指出。

  

上述所有的代码都是使用截图的方式呈现的,这无关紧要,在博客的结尾会给出所有相关的代码,当然也包括上述的PHP代码,以及PickerView的具体实现呢。好了,由于博客篇幅有限,今天就先到这儿吧。下方就是本篇博客相关代码的分享链接。

github分享地址:https://github.com/lizelu/ProvincePickerDemo