从一张图开始:
按模块数据绑定显示样式
像这种界面,布局会比较复杂,每一section的头尾显示及点击事件都是动态的,而且里面的内容是几个row也不确定,这个界面逻辑整理后是:
NoDataCell-相关商城-相关医生-相关医院-相关问答-相关资讯-微脉好物-热门医生-热门医院-热门问答-热门资讯
复制代码
然后就是设置11个section,每个section里有三个cell,通用的titleSectionCell和moreSectionCell,还有每个section对应的自己的内容cell,每个cell负责自己的UI显示及点击事件。
整理后 逻辑思路是清晰了,但是怎么写代码呢? 如果按照思路说的这样每个section都得判断去写对应的三个cell还包括个字赋值及点击事件,想一想cellForRow里代码,额。。得写多大一坨啊?
是不是可以更有效更优雅的写这段代码呢? 想一下这段里的共性,通用的头尾好处理,内容cell的个数由各自的数组控制 cell的选择和index有关,而且整个tableView布局是固定的.
所以我们可以这样:
//排版方式:相关商品>医生>医院>问答>疾病标签>资讯>热门商品>医生>医院>问答>资讯
NSDictionary *mallSectionDic = @{kCellTitle:@"相关商品", kCellHasMore:@(self.mallHasMore), kCellHasResult:@(self.mallHasResult), kCellClass:@"WMSearchResultMallCell", kCellTitleMoreSelector:@"relativeMallMoreClick", kCellDataArray:self.resultMallArrray, kCellMoreText:@"查看更多商品"};
NSDictionary *doctorSectionDic = @{kCellTitle:@"相关医生", kCellHasMore:@(self.doctorHasMore), kCellHasResult:@(self.doctorHasResult), kCellClass:@"WMSearchResultDoctorCell", kCellTitleMoreSelector:@"relativeDoctorMoreClick", kCellDataArray:self.resultDoctorArrray, kCellMoreText:@"查看更多医生"};
NSDictionary *hospitalSectionDic = @{kCellTitle:@"相关医院", kCellHasMore:@(self.hospitalHasMore), kCellHasResult:@(self.hospitalHasResult), kCellClass:@"WMSearchResultHospitalCell", kCellTitleMoreSelector:@"relativeHospitalMoreClick", kCellDataArray:self.resultHospitalArrray, kCellMoreText:@"查看更多医院"};
NSDictionary *qaSectionDic = @{kCellTitle:@"相关问答", kCellHasMore:@(self.QAHasMore), kCellHasResult:@(self.QAHasResult), kCellClass:@"WMSearchResultQACell", kCellTitleMoreSelector:@"relativeQAMoreClick", kCellDataArray:self.resultQAArrray,kCellMoreText:@"查看更多问答"};
// NSDictionary *sicknessSectionDic = @{kCellTitle:@"疾病", kCellHasMore:@(self.sicknessHasMore), kCellHasResult:@(self.sicknessHasResult), kCellClass:@"WMSearchResultQACell", kCellTitleMoreSelector:@"relativeSicknessMoreClick", kCellDataArray:self.resultSicknessArray,kCellMoreText:@"查看更多疾病"};
NSDictionary *newsSectionDic = @{kCellTitle:@"相关资讯", kCellHasMore:@(self.newsHasMore), kCellHasResult:@(self.newsHasResult), kCellClass:@"WMSearchResultNewsCell", kCellTitleMoreSelector:@"relativeNewsMoreClick", kCellDataArray:self.resultNewsArrray, kCellMoreText:@"查看更多资讯"};
NSDictionary *recommentMallSectionDic = @{kCellTitle:@"热门商品", kCellHasMore:@(self.mallHasMore), kCellHasResult:@(self.mallHasResult), kCellClass:@"WMSearchResultMallCell", kCellTitleMoreSelector:@"relativeMallMoreClick", kCellDataArray:self.resultMallArrray, kCellMoreText:@"查看更多商品"};
NSDictionary *recommentDoctorSectionDic = @{kCellTitle:@"周边热门医生", kCellHasMore:@(self.doctorHasMore), kCellHasResult:@(self.doctorHasResult), kCellClass:@"WMSearchResultDoctorCell", kCellTitleMoreSelector:@"recomendDoctorMoreClick", kCellDataArray:self.resultDoctorArrray, kCellMoreText:@"查看更多医生"};
NSDictionary *recommentHospitalSectionDic = @{kCellTitle:@"周边热门医院", kCellHasMore:@(self.hospitalHasMore), kCellHasResult:@(self.hospitalHasResult), kCellClass:@"WMSearchResultHospitalCell", kCellTitleMoreSelector:@"recomendHospitalMoreClick", kCellDataArray:self.resultHospitalArrray, kCellMoreText:@"查看更多医院"};
NSDictionary *recommentQaSectionDic = @{kCellTitle:@"热门问答", kCellHasMore:@(self.QAHasMore), kCellHasResult:@(self.QAHasResult), kCellClass:@"WMSearchResultQACell", kCellTitleMoreSelector:@"recomendQAMoreClick", kCellDataArray:self.resultQAArrray, kCellMoreText:@"查看更多问答"};
NSDictionary *recommentNewsSectionDic = @{kCellTitle:@"热门资讯", kCellHasMore:@(self.newsHasMore), kCellHasResult:@(self.newsHasResult), kCellClass:@"WMSearchResultNewsCell", kCellTitleMoreSelector:@"recomendNewsMoreClick", kCellDataArray:self.resultNewsArrray, kCellMoreText:@"查看更多资讯"};
self.impSearchResultDataArray = @[mallSectionDic, doctorSectionDic, hospitalSectionDic, qaSectionDic, newsSectionDic, recommentMallSectionDic, recommentDoctorSectionDic, recommentHospitalSectionDic, recommentQaSectionDic, recommentNewsSectionDic];
复制代码
-
用一个数组控制,里面对应各自section的字典,字典里面各自负责自己的titleSection和moreSection的显示及点击事件/结果bool/各自的自定义cell/各自的数据源。
-
因为字典里的数据源NSMutableArray是浅拷贝 由指针指着地址,所以后面在数据请求后能正常一直使用。
-
字典里的cell,通过字符串转class的方法,获取到对应的cell,这些cell都作统一的数组赋值 再在各自的赋值方法里用各自的model取值,用共性来解耦。
-
字典里的点击事件,用imp来实现。通过Runtime的消息传递机制,直接执行imp指向的函数实现,这样省去了Runtime消息传递过程中所做的一系列查找操作,会比直接向对象发送消息还要高效一些。
代码如下:
/** 搜索结果 -- cellForRow **/
- (UITableViewCell *)searchResultTableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// 11 section --- nodata,热门商城,医生,医院,问答,资讯,相关附近商城,医生,附近医院,问答,资讯
if (indexPath.section == 0) {
// noData - Cell
kWeakSelf
WMSearchResultNoDataCell *cell = [tableView dequeueReusableCellWithIdentifier:[WMSearchResultNoDataCell reuseIdentifier] forIndexPath:indexPath];
[cell configSearchText:self.searchKeyWords searchNoDataType:SearchNoDataTypeTabSummary];
cell.moreBtnClickBlock = ^{
[weakSelf skipToAskViewController];
};
return cell;
}else {
NSDictionary *impDictionary = self.impSearchResultDataArray[indexPath.section-1];
if (indexPath.row == 0) {
/*** sectionTitle - cell ***/
WMSearchResultSectionTitleCell *cell = [tableView dequeueReusableCellWithIdentifier:[WMSearchResultSectionTitleCell reuseIdentifier] forIndexPath:indexPath];
[cell configSectionTitle:impDictionary[kCellTitle] hasMore:impDictionary[kCellHasMore]];
// imp方法转换
[cell performSelector:@selector(setSectionTitleMoreBtnClickedBlock:) withObject:^(NSIndexPath *indexPath) {
SEL selector = NSSelectorFromString(impDictionary[kCellTitleMoreSelector]);
if ([self respondsToSelector:selector])]) {
IMP imp = [self methodForSelector:selector])];
void (*func)(id, SEL) = (void *)imp;
func(self, selector));
}
}];
return cell;
}else {
NSArray *dataArray = impDictionary[kCellDataArray];
BOOL hasResult = impDictionary[kCellHasResult];
BOOL hasMore = impDictionary[kCellHasMore];
NSString *moreText = impDictionary[kCellMoreText];
if (hasMore && (indexPath.row == (dataArray.count+1))) {
/*** sectionMore - cell ***/
WMSearchResultSectionMoreCell *cell = [tableView dequeueReusableCellWithIdentifier:[WMSearchResultSectionMoreCell reuseIdentifier] forIndexPath:indexPath];
[cell configMoreText:moreText];
return cell;
}else {
/*** 各自内容cell *****/
// 获取cell类名的重用标识符
NSString *cellIndentifer = impDictionary[kCellClass];
// 通过重用标识字符串创建类
Class cellClass = NSClassFromString(cellIndentifer);
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIndentifer];
if (!cell) {
cell = [[cellClass alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIndentifer];
}
if (dataArray.count > (indexPath.row-1)) {
if ([cell respondsToSelector:@selector(configDataArray:indexItem:)]) {
// 执行赋值方法
[cell performSelector:@selector(configDataArray:indexItem:) withObject:dataArray withObject:[NSNumber numberWithInteger:(indexPath.row-1)]];
}
}
return cell;
}
}
}
}
复制代码
结合runtime优化tableView
performSelector是在iOS中的一种方法调用方式,是运行时系统负责去找方法的,在编译时候不做任何校验。
他可以向一个对象传递任何消息,而不需要在编译的时候声明这些方法。所以这也是runtime的一种应用方式。
所以performSelector和直接调用方法的区别就在与runtime。直接调用编译是会自动校验。如果方法不存在,那么直接调用在编译时候就能够发现,编译器会直接报错。
但是使用performSelector的话一定是在运行时候才能发现,如果此方法不存在就会崩溃。所以如果使用performSelector时,为了程序的健壮性,会使用检查方法respondsToSelector。
通过Runtime的消息传递机制,直接执行imp指向的函数实现:
SEL selector = NSSelectorFromString(impDictionary[kCellTitleMoreSelector]);
IMP imp = [self methodForSelector:selector];
void (*func)(id, SEL) = (void *)imp;
func(self, selector);
复制代码
SEL : 类成员方法的指针,其实只是方法编号。
IMP:一个函数指针,保存了方法的地址。IMP是”implementation”的缩写,它是objetive-C 方法(method)实现代码块的地址。
IMP和SEL关系:
每一个继承于NSObject的类都能自动获得runtime的支持。
在这样的一个类中,有一个isa指针,指向该类定义的数据结构体,这个结构体是由编译器编译时为类(需继承于NSObject)创建的。
在这个结构体中有包括了指向其父类类定义的指针以及 Dispatch table。
Dispatch table是一张SEL和IMP的对应表。
也就是说方法编号SEL最后还是要通过Dispatch table表寻找到对应的IMP,IMP就是一个函数指针,然后执行这个方法。
实现步骤:
- 通过方法获得SEL 方法编号:
SEL methodId=@selector(methodName);或者SEL methodId = NSSelectorFromString(methodName);
复制代码
- 通过方法编号获得IMP:
IMP imp = [self methodForSelector:methodId];
复制代码
- 执行IMP:
void (*func)(id, SEL, id) = (void *)imp;
func(self, methodName,param);
复制代码
然后直接在类里写methodName对应的点击事件方法就可以了,不用在cellForRow里通过代理或block写事件或在didSeclectRow里再根据index来判断了。
到这cellForRow里面的内容就差不多写完了,再说说tableViewCell高度计算。
简单说说tableView自定义Cell的写法,个人不建议用Xib,建议手代码用Masonry布局。
masonry主要用三种写法,make/remake/update,根据具体样式的变化程度决定用哪个,一般make就足够了。
代码规范: 导入头文件、命名、UILoad、DataLoad、PrivateAction、Delegate、LazyLoad。。