效果圖
概述
關於 省市區 三級聯動的 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