省市区选择组件实践-2020-04-21
2020-04-24 本文已影响0人
勇往直前888
简介
一般省市区的选择组件采用UIPickerView
来实现,比如这篇文章就很好iOS-省市区选择的实现
。这次为了和Android
保持风格一致,采用自定义视图的方式。大概的样子如下:
数据源
-
省市区数据一般放在后台,可以一个接口给(省市区数据一次给全);也可以分三个接口给(先给省的,选了省之后再根据选择情况给市,然后再根据市的情况给区)。
-
考虑到省市区数据一般比较稳定,数据虽然有点多,但也不是特别大,所以可以考虑采用文件的方式打包进客户端。
-
一般
iOS
访问比较方便的格式是plist
,不过考虑到和Android
的通用性,还是采用json
的方式。网上也有这样的数据提供,比如这个 -
从网上下载数据之后,通过网络工具,检查一下是否json格式是否正确。比如这个
使用
json
格式检查工具的另外一个好处是可以将文本内容格式化,查看起来更方便
- 将内容复制过来,以.json格式存为本地文件,并且加入工程当中。
- 读取本地
json
文件内容:基本思路是将文件内容读取成NSData
格式,然后通过json
转换方法,转换为字典和数组的嵌套结构。
iOS:本地json文件读取、存储
// 读取本地JSON文件
- (NSArray *)readLocalFileWithName:(NSDictionary *)name {
// 获取文件路径;注意,如果在framework中,这里就不一定是mainBundle了
NSString *path = [[NSBundle mainBundle] pathForResource:name ofType:@"json"];
// 将文件数据化
NSData *data = [[NSData alloc] initWithContentsOfFile:path];
// 对数据进行JSON格式化并返回字典形式
return [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
}
数据Model化
-
json
文件读取之后,是字典和数组的嵌套结构,查看和使用都不是很方便。所以考虑Model
话,这样看起来更有意义。原始的json
数据如下图所示,嵌套较多。
-
省市区,一般需要两个字段,一个是名称,比如我们常见的“北京”;另外一个是对应的统一编码,比如“110000”。
-
第三级区,就只有名字和编码两个字段
@interface ZXSDistrictModel : NSObject
// 名称
@property (copy, nonatomic) NSString *name;
// 编码
@property (copy, nonatomic) NSString *code;
@end
- 第二级市,除了名字和编码两个字段外,还有一个所属区的数组。
@interface ZXSCityModel : NSObject
// 名称
@property (copy, nonatomic) NSString *name;
// 编码
@property (copy, nonatomic) NSString *code;
// 所属的区
@property (copy, nonatomic) NSArray<ZXSDistrictModel *> *districtArray;
@end
- 第一级省,除了名字和编码两个字段外,还有一个所属市的数组。
@interface ZXSProvinceModel : NSObject
// 名称
@property (copy, nonatomic) NSString *name;
// 编码
@property (copy, nonatomic) NSString *code;
// 所属的市
@property (copy, nonatomic) NSArray<ZXSCityModel *> *cityArray;
@end
- 将字典数组嵌套结构转换为Model数组之后,结构就简单清晰多了,可读性也大大提高
界面
- 界面可以采用故事版,简单直接
- 省市区都包括
name
和code
两个信息,对于表格来说,还有一个信息就是是否选中,所以用ZXSAreaCellModel
对象来记录表格所需要的信息。
@interface ZXSAreaCellModel : NSObject
// 省市区的名称
@property (copy, nonatomic) NSString *name;
// 省市区的编码
@property (copy, nonatomic, nullable) NSString *code;
// 是否选中
@property (assign, nonatomic) BOOL isSelect;
@end
- 写三个生成函数,分别通过省、市、区对象获得:
// 通过省对象创建实例
+ (instancetype)instanceWithProvinceModel:(ZXSProvinceModel *)province {
ZXSAreaCellModel *cellModel = [[ZXSAreaCellModel alloc] init];
cellModel.name = province.name;
cellModel.code = province.code;
cellModel.isSelect = NO;
return cellModel;
}
// 通过市对象创建实例
+ (instancetype)instanceWithCityModel:(ZXSCityModel *)city {
ZXSAreaCellModel *cellModel = [[ZXSAreaCellModel alloc] init];
cellModel.name = city.name;
cellModel.code = city.code;
cellModel.isSelect = NO;
return cellModel;
}
// 通过区对象创建实例
+ (instancetype)instanceWithDistrictModel:(ZXSDistrictModel *)district {
ZXSAreaCellModel *cellModel = [[ZXSAreaCellModel alloc] init];
cellModel.name = district.name;
cellModel.code = district.code;
cellModel.isSelect = NO;
return cellModel;
}
- 创建一个继承自UITableViewCell的对象来表示表格中的一项。
@interface ZXSAreaCell : UITableViewCell
// 更新cell
- (void)updateWithCellModel:(ZXSAreaCellModel *)cellModel;
@end
只有两个对象,表示名称的标签,以及选中标志图片
@interface ZXSAreaCell ()
// 选项标签
@property (weak, nonatomic) IBOutlet UILabel *itemLabel;
// 选中标志
@property (weak, nonatomic) IBOutlet UIImageView *selectImage;
@end
- 需要一个通过
ZXSAreaCellModel
对象更新cell
界面显示的方法:
// 更新cell
- (void)updateWithCellModel:(ZXSAreaCellModel *)cellModel {
if (cellModel) {
self.itemLabel.text = cellModel.name;
self.selectImage.hidden = !cellModel.isSelect;
}
}
调用接口
- 首先要有一个表示用户选择信息的数据接口,记录省市区
name
和code
两个数据:
@interface ZXSAreaModel : NSObject
// 省名称
@property (copy, nonatomic) NSString *provinceName;
// 省编码
@property (copy, nonatomic) NSString *provinceCode;
// 市名称
@property (copy, nonatomic) NSString *cityName;
// 市编码
@property (copy, nonatomic) NSString *cityCode;
// 区名称
@property (copy, nonatomic) NSString *districtName;
// 区编码
@property (copy, nonatomic) NSString *districtCode;
@end
-
由于我们准备以
Present
的方式展示省市区选择,所以需要调用者提供一个UIViewController
; -
另外,需要记住上次用户的选择,这也是一个
ZXSAreaModel
对象 -
用户选择或者取消之后,可能会有后续操作,所以提供两个
block
,供使用者定义后续操作。
@interface ZXSAreaSelector : NSObject
// present方式显示地区选择
+ (void)launchOnContorller:(UIViewController *)vc currentArea:(nullable ZXSAreaModel *)currentArea select:(nullable void(^)(ZXSAreaModel *selectArea))selectBlock
cancel:(nullable void(^)(void))cancelBlock;
@end
控制器的属性
数据接口所需要信息都是通过控制的外部属性提供的。
@interface ZXSAreaSelectorViewController : UIViewController
// 当前选中地区;
@property (strong, nonatomic, nullable) ZXSAreaModel *currentArea;
// 选择后的block
@property (copy, nonatomic, nullable) void(^selectBlock)(ZXSAreaModel *selectArea);
// 取消的block
@property (copy, nonatomic, nullable) void(^cancelBlock)(void);
// 所有的省数据
@property (strong, nonatomic, nonnull) NSArray<ZXSProvinceModel *> *provinceArray;
@end
属性观察
-
最方便的是使用第三方库
ReactiveObjC
-
这里内容不多,可以直接使用
setter
函数自定义来替代。比如:
// 根据索引设置下划线位置和标签颜色
- (void)setAreaIndex:(ZXSAreaIndex)areaIndex {
_areaIndex = areaIndex;
// 下划线位置
self.maskLeftConstant.constant = areaIndex * kIndexStep;
// 标签颜色
switch (areaIndex) {
case ZXSAreaIndexProvince:
self.provinceLabel.textColor = kBlueColor;
self.cityLabel.textColor = kBlackColor087;
self.districtLabel.textColor = kBlackColor087;
break;
case ZXSAreaIndexCity:
self.provinceLabel.textColor = kBlackColor087;
self.cityLabel.textColor = kBlueColor;
self.districtLabel.textColor = kBlackColor087;
break;
case ZXSAreaIndexDistrict:
self.provinceLabel.textColor = kBlackColor087;
self.cityLabel.textColor = kBlackColor087;
self.districtLabel.textColor = kBlueColor;
break;
default:
break;
}
// 表格数据源设置
switch (areaIndex) {
case ZXSAreaIndexProvince:
self.dataSource = self.provinceDataSource;
break;
case ZXSAreaIndexCity:
self.dataSource = self.cityDataSource;
break;
case ZXSAreaIndexDistrict:
self.dataSource = self.districtDataSource;
break;
default:
break;
}
}
使用方式
用起来就相对比较简单:
@interface ZXSViewController ()
@property (strong, nonatomic) ZXSAreaModel *area;
@end
- (IBAction)areaSelectorButtonTouched:(id)sender {
[ZXSTool launchAreaSelectorOnContorller:self currentArea:self.area select:^(ZXSAreaModel * _Nonnull selectArea) {
self.area = selectArea;
NSLog(@"%@%@%@", selectArea.provinceName, selectArea.cityName, selectArea.districtName);
NSLog(@"%@%@%@", selectArea.provinceCode, selectArea.cityCode, selectArea.districtCode);
} cancel:nil];
}
Demo地址
这个组件已经上传GitHub
,支持CocoaPod
。