最终效果图:
核心代码:
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;
@end
NSObject+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