最终效果图:
核心代码:
NSObject+Dict.h
// // NSObject+Dict.h // 帅哥_团购 // // Created by beyond on 14-8-14. // Copyright (c) 2014年 com.beyond. All rights reserved. // 使用运行时,将dict转成对象 #import <Foundation/Foundation.h> @interface NSObject (Dict) // 一个对象,调用此方法,参数 传递一个字典,便可以通过运行时,自动将字典中所有的值V,赋值到对象对应的成员属性上面去 - (void)setValuesWithDict:(NSDictionary *)dict; @endNSObject+Dict.m
// // NSObject+Dict.m // 帅哥_团购 // // Created by beyond on 14-8-14. // Copyright (c) 2014年 com.beyond. All rights reserved. // #import "NSObject+Dict.h" // 运行时需要 #import <objc/message.h> #import <objc/runtime.h> @implementation NSObject (Dict) // 一个对象,调用此方法,参数 传递一个字典,便可以通过运行时,自动将字典中所有的值V,赋值到对象对应的成员属性上面去 - (void)setValuesWithDict:(NSDictionary *)dict { // 本类的类名 Class c = [self class]; while (c) { // 1.获得本类 所有的成员变量 unsigned int outCount = 0; Ivar *ivars = class_copyIvarList(c, &outCount); for (int i = 0; i<outCount; i++) { Ivar ivar = ivars[i]; // 2.属性名 NSMutableString *name = [NSMutableString stringWithUTF8String:ivar_getName(ivar)]; // 删除最前面的_ [name replaceCharactersInRange:NSMakeRange(0, 1) withString:@""]; // 3.取出属性值 NSString *key = name; if ([key isEqualToString:@"desc"]) { key = @"description"; } if ([key isEqualToString:@"ID"]) { key = @"id"; } // 取出参数字典中对应的值 id value = dict[key]; // 健壮性判断,如果字典中没有这个键对应的值,则进入下一次for循环 if (!value) continue; // 4.构造SEL // 首字母 NSString *cap = [name substringToIndex:1]; // 首字母变大写 cap = cap.uppercaseString; // 将大写字母调换掉原首字母 [name replaceCharactersInRange:NSMakeRange(0, 1) withString:cap]; // 拼接set [name insertString:@"set" atIndex:0]; // 拼接冒号: [name appendString:@":"]; SEL selector = NSSelectorFromString(name); // 5.属性类型,如果是基本类型,要转成对象 NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)]; if ([type hasPrefix:@"@"]) { // 若为对象类型,直接发送消息 objc_msgSend(self, selector, value); } else { // 若为非对象类型,即基本对象类型,则要转成对象类型 if ([type isEqualToString:@"d"]) { objc_msgSend(self, selector, [value doubleValue]); } else if ([type isEqualToString:@"f"]) { objc_msgSend(self, selector, [value floatValue]); } else if ([type isEqualToString:@"i"]) { objc_msgSend(self, selector, [value intValue]); } else { objc_msgSend(self, selector, [value longLongValue]); } } } // for循环遍历完了本类中所有的成员之后,获取其父类继续上述操作,直至其父类为空 c = class_getSuperclass(c); } } @end /* class_getSuperclass Returns the superclass of a class. Class class_getSuperclass(Class cls) Parameters cls A class object. Return Value The superclass of the class, or Nil if cls is a root class, or Nil if cls is Nil. Discussion You should usually use NSObject‘s superclass method instead of this function. Availability Available in iOS 2.0 and later. Declared In objc/runtime.h */
Cities.plist文件转对象模型示意图:
一组对应的模型Section.m
// // CitySectionByLetter.m // 帅哥_团购 // // Created by beyond on 14-8-14. // Copyright (c) 2014年 com.beyond. All rights reserved. // 数据模型,一个组Section,成员1:组名,如B组 成员2:是一个数组,cities,装着B组这个组下面所有的城市,数组中每个成员是一个City对象 #import "Section.h" //城市模型 #import "City.h" @implementation Section // 关键!字典中cities对应的值还是字典数组,而我们需要的是city对象数组,所以要拦截setCities方法,将字典数组,遍历,转成一个个对象,并添加到一个数组中,再将对象数组赋值给成员属性cities - (void)setCities:(NSMutableArray *)cities { //拦截【运行时】将字典中的key--cities对应的值:字典数组 赋值到成员cities,而应该是把它转成一个个对象之后,再赋值给成员属性 // 当cities为空或者里面装的已经是对象模型,就可以直接赋值,而不用再调用分类方法将字典转成对象模型了 id obj = [cities lastObject]; if (![obj isKindOfClass:[NSDictionary class]]){ _cities = cities; return; } NSMutableArray *cityArr = [NSMutableArray array]; for (NSDictionary *dict in cities) { City *city = [[City alloc] init]; // 调用分类中运行时方法,将字典中的K - V 转成对象 [city setValuesWithDict:dict]; [cityArr addObject:city]; } // 最后,再将对象数组,赋值给成员属性cities数组 _cities = cityArr; } @end
一个城市对应的模型
// // City.m // 帅哥_团购 // // Created by beyond on 14-8-14. // Copyright (c) 2014年 com.beyond. All rights reserved. // 数据模型,一个city,成员1:城市名,如北京 成员2:是一个数组,districts,装着北京这个城市下面所有的行政区,数组中每个成员是一个District对象 #import "City.h" // 数据模型 #import "District.h" @implementation City // 关键!字典中districts对应的值还是字典数组,而我们需要的是district对象数组,所以要拦截setDistricts方法,将字典数组,遍历,转成一个个对象,并添加到一个数组中,再将对象数组赋值给成员属性districts - (void)setDistricts:(NSMutableArray *)districts { //拦截【运行时】将字典中的key--cities对应的值:字典数组 赋值到成员cities,而应该是把它转成一个个对象之后,再赋值给成员属性 NSMutableArray *districtArr = [NSMutableArray array]; for (NSDictionary *dict in districts) { District *district = [[District alloc] init]; // 调用分类中运行时方法,将字典中的K - V 转成对象 [district setValuesWithDict:dict]; [districtArr addObject:district]; } // 最后,再将对象数组,赋值给成员属性districts数组 _districts = districtArr; } @end
CityLocationController.m
// // CityLocationController.m // 帅哥_团购 // // Created by beyond on 14-8-14. // Copyright (c) 2014年 com.beyond. All rights reserved. // 点击dock下面的倒数第2个定位按钮,弹出的用Popover包装的城市选择控制器,其上面是一个搜索框,下面是一个tableView(按城市的拼音分的组) #import "CityLocationController.h" // 蒙板 #import "CoverOnTableView.h" // 元数据工具 #import "MetaDataTool.h" // 数据模型---分组 #import "Section.h" // 数据模型---城市 #import "City.h" // 数据模型---行政区 #import "District.h" // 上面的searchBar高度 #define kSearchBarH 44 @interface CityLocationController ()<UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate> { // 从plist中加载的数组,共23个成员,每个成员是个字典,每个字典有两对KV,一对是name-->A,另一对是cities--->数组(数组中的成员是字典,一个字典对应一个城市,该字典又有三对KV,分别是:name--->北京,hot---->1,districts--->数组(该数组对应的又是一个字典,代表一个区,字典中有两对KV,分别是:name--->朝阳区,neighbours--->数组(该数组的成员是string.....))) NSMutableArray *_sections; // 所有的城市组信息 // view上方是UISearchBar,下方是UITableView UISearchBar *_searchBar; UITableView *_tableView; // UITableView上面有一层蒙板,遮盖 CoverOnTableView *_cover; // TGSearchResultController *_searchResult; } @end @implementation CityLocationController - (void)viewDidLoad { [super viewDidLoad]; // 1.添加上方的搜索框UISearchBar [self addSearchBar]; // 2.添加下方的tableView [self addTableView]; // 3.使用工具类,加载城市数组数据 [self loadCitiesMetaData]; } // 1.添加搜索框UISearchBar - (void)addSearchBar { _searchBar = [[UISearchBar alloc] init]; _searchBar.autoresizingMask = UIViewAutoresizingFlexibleWidth; _searchBar.frame = CGRectMake(0, 0, self.view.frame.size.width, kSearchBarH); // 监听searchBar的获得焦点,失去焦点,字符变化等事件 _searchBar.delegate = self; _searchBar.placeholder = @"请输入城市名或拼音"; // search.tintColor 渐变色 // search.barStyle 样式 [self.view addSubview:_searchBar]; } // 2.添加下方的tableView - (void)addTableView { _tableView = [[UITableView alloc] init]; CGFloat tableViewH = self.view.frame.size.height - kSearchBarH; _tableView.frame = CGRectMake(0, kSearchBarH, self.view.frame.size.width, tableViewH); _tableView.dataSource = self; _tableView.delegate = self; // 重要~因为本控制器是在Popover控制器里面,Popover又设置了内容SIZE只有320, 480 _tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; [self.view addSubview:_tableView]; } // 3.使用工具类,加载城市数组数据 - (void)loadCitiesMetaData { // 从plist中加载的数组,共23个成员,每个成员是个字典,每个字典有两对KV,一对是name-->A,另一对是cities--->数组(数组中的成员是字典,一个字典对应一个城市,该字典又有三对KV,分别是:name--->北京,hot---->1,districts--->数组(该数组对应的又是一个字典,代表一个区,字典中有两对KV,分别是:name--->朝阳区,neighbours--->数组(该数组的成员是string.....))) _sections = [NSMutableArray array]; NSArray *sections = [MetaDataTool sharedMetaDataTool].allSections; // 将工具类返回的section数组赋值给成员变量,以供tableView的数据源使用 [_sections addObjectsFromArray:sections]; } #pragma mark - 数据源方法 #pragma mark - 数据源方法 // 共有多少分组(23个字母组) - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return _sections.count; } // 每一组有多少行(多少个城市就有多少行) - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { // 第几组 Section *s = _sections[section]; // 第几组的城市数组的个数 return s.cities.count; } // 每一行的cell独一无二的内容 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"CityListCell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]; } // 第几组 Section *s = _sections[indexPath.section]; // 第几行(城市) City *city = s.cities[indexPath.row]; // 城市名 cell.textLabel.text = city.name; return cell; } // 每一组的HeaderTitle - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { // 第几组 Section *s = _sections[section]; // 组名,如ABCD return s.name; } // 表格右侧的分组索引标题 - (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView { // 重要~取出_sections数组中每一组的键为name的值(如ABCD...),并且将这些值全放到一个新的数组中,返回的这个新数组,就是分组索引的标题 return [_sections valueForKeyPath:@"name"]; } #pragma mark - 搜索框代理方法 // 搜索框开始编辑(开始聚焦,取得焦点) - (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar { // 1.动画效果,显示其右边的 取消按钮 [searchBar setShowsCancelButton:YES animated:YES]; // 2.动画显示遮盖(蒙板),并在内部绑定了一个,tap手势监听器 if (_cover == nil) { _cover = [CoverOnTableView coverWithTarget:self action:@selector(coverClick)]; } // 3.必须让cover完全覆盖在tableView上面 _cover.frame = _tableView.frame; [self.view addSubview:_cover]; // 4.开始全透明(看不见) _cover.alpha = 0.0; [UIView animateWithDuration:0.3 animations:^{ // 让cover变成黑色 [_cover alphaReset]; }]; } // 监听 遮盖 被tap点击,移除遮盖,隐藏取消按钮,退出键盘 - (void)coverClick { // 1.动画完成后,移除遮盖 [UIView animateWithDuration:0.3 animations:^{ _cover.alpha = 0.0; } completion:^(BOOL finished) { [_cover removeFromSuperview]; }]; // 2.隐藏_searchBar最右边的取消按钮 [_searchBar setShowsCancelButton:NO animated:YES]; // 3.让_searchBar取消第一响应者,即退出键盘 [_searchBar resignFirstResponder]; // [self.view endEditing:YES]; } // 当点击了 搜索框的键盘上面取消键时(即_searchBar失去了焦点) - (void)searchBarTextDidEndEditing:(UISearchBar *)searchBar { [self coverClick]; } // 当点击了 搜索框的右边的取消按钮时 - (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar { [self coverClick]; } @end