從一張圖開始:
按模組資料繫結顯示樣式
像這種介面,佈局會比較複雜,每一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。。