省市区三级联动 pickerView
效果图
概述
关于 省市区 三级联动的 pickerView,我想大多数的 iOS 开发者应该都遇到过这样的需求。在遇到这样的需求的时候,大多数人都会觉的这个很复杂,一时无从下手。其实真的没那么复杂。在这里我们来一起看看,怎么去实现这样的 pickerView,并做一个简单的封装,使其使用的更加简单,从而也减少了 ViewController 中的代码。
实现思路
如何封装
- 我们使用一个 View(IDAddressPickerView) 来封装 PickerView,来处理 PickerView 的 dataSource 和 delegate,将原本需要在 ViewController处理的 逻辑封装的 View 中。
- ViewController 只需要为 IDAddressPickerView 提供 dataSource,并获取选中的 Address。而不去关心其他逻辑,不如说:联动逻辑,数据格式化。
- IDAddressPickerView 使用委托模式来获取 ViewController 提供的数据源。
数据如何组织
-
IDAddressPickerView 的数据源是一个数组,且需要满足一定的格式,这在一定程度上降低了其使用灵活性。
-
目前 IDAddressPickerView 数据源的需要满足的格式如图:
获取选中的地址
- 选中地址的格式,目前是通过固定的 key 包装在一个 Dictionary,灵活性不高。
- 在此没有使用委托等模式,而是通过一个属性保存当前选中的地址,让用户(IDAddressPickerView 的使用者)主动去获取选中的地址。
期望结果
- 目前实现的 IDAddressPickerView 的数据源缺乏灵活性,尽管我们可以与后台沟通约定数据格式,但是对于一个封装的 IDAddressPickerView 来说,显然是不妥当的。
- 我期望实现的结果是,在为 IDAddressPickerView 提供数据源的时候,指定一个 dataFormatter,IDAddressPickerView 根据 dataFormatter 去解析数据源的数据。而不是现在的根据固定的格式解析数据。
- 由于这些问题的存在,我将项目代码上传到 github 上,还希望有兴趣的小伙伴们多提宝贵意见。
具体实现
IDAddressPickerView
-
自定义 UIView 的 子类 IDAddressPickerView
@interface IDAddressPickerView : UIView @end
-
添加 UIPickerView 子控件
- (UIPickerView *)pickerView { if (_pickerView == nil) { _pickerView = [[UIPickerView alloc] init]; _pickerView.dataSource = self; _pickerView.delegate = self; } return _pickerView; }
-
UIPickerView 的数据源
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView { return 3; } - (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component { NSInteger numberOfRowsInComponent = 0; switch (component) { case 0: numberOfRowsInComponent = self.addressArray.count; break; case 1: { NSDictionary *province = self.addressArray[self.provinceIndex]; numberOfRowsInComponent = [province[@"cities"] count]; } break; case 2: { NSDictionary *province = self.addressArray[self.provinceIndex]; NSDictionary *cities = province[@"cities"][self.cityIndex]; numberOfRowsInComponent = [cities[@"areas"] count]; } break; } return numberOfRowsInComponent; } - (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component { NSString *titleForRow = @""; switch (component) { case 0: titleForRow = self.addressArray[row][@"state"]; break; case 1: { NSDictionary *province = self.addressArray[self.provinceIndex]; titleForRow = province[@"cities"][row][@"city"]; } break; case 2: { NSDictionary *province = self.addressArray[self.provinceIndex]; NSDictionary *city = province[@"cities"][self.cityIndex]; titleForRow = city[@"areas"][row]; } break; } return titleForRow; }
-
UIPickerView 的代理
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component { switch (component) { case 0: { self.provinceIndex = row; self.cityIndex = 0; self.areaIndex = 0; [pickerView reloadComponent:1]; [pickerView reloadComponent:2]; [pickerView selectRow:0 inComponent:1 animated:NO]; [pickerView selectRow:0 inComponent:2 animated:NO]; /** * 更新选中的 addresss,包括:市,区 */ NSDictionary *province = self.addressArray[self.provinceIndex]; NSDictionary *city = province[@"cities"][self.cityIndex]; self.selectedAddress[ProvinceKey] = self.addressArray[row][@"state"]; if ([province[@"cities"] count] > 0) { self.selectedAddress[CityKey] = province[@"cities"][0][@"city"]; } else { self.selectedAddress[CityKey] = @""; } if ([city[@"areas"] count] > 0) { self.selectedAddress[AreaKey] = city[@"areas"][0]; } else { self.selectedAddress[AreaKey] = @""; } } break; case 1: { self.cityIndex = row; self.areaIndex = 0; [pickerView reloadComponent:2]; [pickerView selectRow:0 inComponent:2 animated:NO]; /** * 更新选中的 addresss,包括:区 */ NSDictionary *province = self.addressArray[self.provinceIndex]; NSDictionary *city = province[@"cities"][self.cityIndex]; self.selectedAddress[CityKey] = province[@"cities"][row][@"city"]; if ([city[@"areas"] count] > 0) { self.selectedAddress[AreaKey] = city[@"areas"][0]; } else { self.selectedAddress[AreaKey] = @""; } } break; case 2: { self.areaIndex = row; /** * 更新选中的 addresss */ NSDictionary *province = self.addressArray[self.provinceIndex]; NSDictionary *city = province[@"cities"][self.cityIndex]; self.selectedAddress[AreaKey] = city[@"areas"][row]; } break; } }
-
关于 UIPickerView 的数据源
-
UIPickerView 的数据源 通过 IDAddressPickerViewDataSource 协议获得
- (NSArray *)addressArray { if (_addressArray == nil) { if ([self.dataSource respondsToSelector:@selector(addressArray)]) { _addressArray = [self.dataSource addressArray]; } else { _addressArray = [NSArray array]; } } return _addressArray; }
-
-
联动效果的实现
-
原理
- 基本的原理是通过更新数据源的方式,来实现选中一列中的某一行时,更新后继(更深层次)的列。
-
具体实现
-
在此使用三个属性分别记录省市区三个层次的对应的列中选中的行,UIPickerView 通过这三个属性来获取对应的数据源。
-
选中一列中的某一行时,需要更新当前列及其后继列所对应的选中行信息。
/** 选中的省份 */ @property (nonatomic, assign) NSInteger provinceIndex; /** 选中的城市 */ @property (nonatomic, assign) NSInteger cityIndex; /** 选中的省份 */ @property (nonatomic, assign) NSInteger areaIndex;
-
-
更新后继的列
- 来实现选中一列中的某一行时,更新 所有 后继列,默认选中第一行。即,选中第一列时,更新第二列和第三列;选中第二列时,更新第三列;选中第三列时。
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component { switch (component) { case 0: { self.provinceIndex = row; self.cityIndex = 0; self.areaIndex = 0; [pickerView reloadComponent:1]; [pickerView reloadComponent:2]; [pickerView selectRow:0 inComponent:1 animated:NO]; [pickerView selectRow:0 inComponent:2 animated:NO]; /** * 更新选中的 addresss,包括:市,区 */ NSDictionary *province = self.addressArray[self.provinceIndex]; NSDictionary *city = province[@"cities"][self.cityIndex]; self.selectedAddress[ProvinceKey] = self.addressArray[row][@"state"]; if ([province[@"cities"] count] > 0) { self.selectedAddress[CityKey] = province[@"cities"][0][@"city"]; } else { self.selectedAddress[CityKey] = @""; } if ([city[@"areas"] count] > 0) { self.selectedAddress[AreaKey] = city[@"areas"][0]; } else { self.selectedAddress[AreaKey] = @""; } } break; case 1: { self.cityIndex = row; self.areaIndex = 0; [pickerView reloadComponent:2]; [pickerView selectRow:0 inComponent:2 animated:NO]; /** * 更新选中的 addresss,包括:区 */ NSDictionary *province = self.addressArray[self.provinceIndex]; NSDictionary *city = province[@"cities"][self.cityIndex]; self.selectedAddress[CityKey] = province[@"cities"][row][@"city"]; if ([city[@"areas"] count] > 0) { self.selectedAddress[AreaKey] = city[@"areas"][0]; } else { self.selectedAddress[AreaKey] = @""; } } break; case 2: { self.areaIndex = row; /** * 更新选中的 addresss */ NSDictionary *province = self.addressArray[self.provinceIndex]; NSDictionary *city = province[@"cities"][self.cityIndex]; self.selectedAddress[AreaKey] = city[@"areas"][row]; } break; } }
IDAddressPickerViewDataSource 协议
@protocol IDAddressPickerViewDataSource <NSObject>
/**
* 地址信息,指定格式的数组
*/
- (NSArray *)addressArray;
@end
使用示例
-
设置 textField 的 inputView 为 IDAddressPickerView
_textField.inputView = self.addressPickerView; // getter - (IDAddressPickerView *)addressPickerView { if (_addressPickerView == nil) { _addressPickerView = [[IDAddressPickerView alloc] init]; _addressPickerView.dataSource = self; } return _addressPickerView; }
-
IDAddressPickerViewDataSource 提供数据
#pragma mark - IDAddressPickerViewDataSource - (NSArray *)addressArray { NSString *path = [[NSBundle mainBundle] pathForResource:@"address" ofType:@"plist"]; NSArray *addressInfo = [NSArray arrayWithContentsOfFile:path]; return addressInfo; }
-
获取选中的地址
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { NSLog(@"%@", self.addressPickerView.selectedAddress); }
声明
项目代码已经上传到 gitHub,若需要请自行获取:IDAddressPickerView