最近在想關于 UITableView 的一些事,對于界面搭建來說,它是再常用不過了,對于它可以說的事,實在是太多了,然而我這兩天想的是應用場景的一些情形,其中有一些疑問。
- 1、多種點擊的列表,假如還是傳統方式,那么判斷是否太多了一點:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
switch (indexPath.row) {
case 0: {
}
break;
default:
break;
}
}
2、多種樣式的列表,用一個 UITableViewCell 肯定是不行的
此時肯定是多個 Cell ,甚至有種感覺此時不如直接用 UIButton 是否都會更方便一些,更好擴展一些呢?3、另外例如淘寶首頁這種界面,此時又該怎么辦呢?
當然據說他們是采取了他們自己的 ListView, 此處不做討論,只是假如我們自己要實現的情況下,又應該怎么辦呢?
我想我的實現首先肯定是整體的 UICollectionView + HeaderView, 而此處復雜的是 HeaderView, 高度和滑動的實現,所以此處還是可以考慮到 UITableView 的,當然是特殊的 UITableView。
一、小嘗試,解決多種點擊(樣式相同)
之前我們在做個人頁面的時候,就是如上圖微信中的個人頁面一樣,樣式相同有多種點擊 ,我們之前組長封裝了一個方便點擊,樣式可調的工具。
- 數據處理, 可以通過自己建立模型,給予分類。
SettingArrowItem *oneItem = [SettingArrowItem itemWithIcon:@"test_icon" title:@"First" destVcClass:[FirstViewController class]];
SettingArrowItem *twoItem = [SettingArrowItem itemWithIcon:@"test_icon" title:@"Second" destVcClass:[SecondViewController class]];
SettingGroup *groupOne = [[SettingGroup alloc] init];
groupOne.items = [NSMutableArray arrayWithArray:@[oneItem,twoItem]];
[self.dataArray addObject:groupOne];
- 點擊,相當于之前已經做好選擇了,當然這樣的應用場景比較固定,也不利于擴展。
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
// 1.模型數據
SettingGroup *group = self.dataArray[indexPath.section];
SettingItem *item = group.items[indexPath.row];
// 2、箭頭可以點擊
if ([item isKindOfClass:[SettingArrowItem class]]) {
SettingArrowItem *arrowItem = (SettingArrowItem *)item;
// 如果沒有需要跳轉的控制器
if (arrowItem.destVcClass == nil) return;
UIViewController *vc = [[arrowItem.destVcClass alloc] init];
vc.title = arrowItem.title;
[self.navigationController pushViewController:vc animated:YES];
}
}
這一種處理,相當于是簡單的封裝數據,讓點擊時更方便,當然一些小部分的Cell 樣式也是可以任意改變的,然而還是認為應用場景有限,只能針對于Account 頁面的點擊,如微信那個人界面。
二、嘗試,解決多種樣式,多種點擊(樣式不同,點擊不同)
這是我們組長為更好的用 UITableView 特意做的一個數據管理器,ZHTableViewGroup,雖說有些細節處理不是很認同,特別是 Demo 中某兩處小細節,同時 Demo 也沒有呈現chu,但整體來說思路是很棒的,使用也是很方便的。
- 數據處理,此處的點擊事件,已經放出來啦,樣式也是隨時可以改變啦
ZHTableViewGroup *group = [[ZHTableViewGroup alloc]init];
ZHTableViewCell *cellOne = [[ZHTableViewCell alloc]initWithTableView:self.homeTableView range:NSMakeRange(0, 6) cellHeight:44 cellClass:[HomeCellStyleOne class] identifier:KHomeCellStyleOneIdentifier];
cellOne.configCellComplete = ^(UITableViewCell *cell, NSIndexPath *indexPath) {
HomeCellStyleOne *cellOne = (HomeCellStyleOne *)cell;
cellOne.textLabel.text = @"One Title";
cellOne.detailTextLabel.text = @"One Detail";
};
cellOne.didSelectRowComplete = ^(UITableViewCell *cell, NSIndexPath *indexPath) {
NSLog(@"cell->%@,indexPath->%@",cell,indexPath);
};
[group addTableViewCell:cellOne];
ZHTableViewCell *cellTwo = [[ZHTableViewCell alloc]initWithTableView:self.homeTableView range:NSMakeRange(6, 5) cellHeight:44 cellClass:[HomeCellStyleTwo class] identifier:KHomeCellStyleOneIdentifier];
cellTwo.configCellComplete = ^(UITableViewCell *cell, NSIndexPath *indexPath) {
HomeCellStyleOne *cellTwo = (HomeCellStyleOne *)cell;
cellTwo.textLabel.text = @"Two Title";
cellTwo.detailTextLabel.text = @"Two Detail";
};
cellTwo.didSelectRowComplete = ^(UITableViewCell *cell, NSIndexPath *indexPath) {
NSLog(@"cell->%@,indexPath->%@",cell,indexPath);
};
[group addTableViewCell:cellTwo];
[self.dataSource addTableV
- 而代理,一如既往是照舊,只是需要固定一些寫法。
#pragma mark - UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return self.dataSource.sectionNumber;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
ZHTableViewGroup *group = [self.dataSource groupWithIndex:section];
return group.rowNumber;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ZHTableViewGroup *group = [self.dataSource groupWithIndex:indexPath.section];
UITableViewCell *cell = [group cellWithIndexPath:indexPath];
return cell;
}
#pragma mark - UITableViewDelegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
ZHTableViewGroup *group = [self.dataSource groupWithIndex:indexPath.section];
[group didSelectRowAtIndexPath:indexPath];
}
- 關鍵方法:
/*!
* @brief 初始化cell托管的對象
*
* @param tableView cell所注冊的tableview
* @param range cell的范圍
* @param cellHeight cell的高度
* @param cellClass 注冊cell的class
* @param identifier 注冊cell的標識符
*
* @return ZHTableViewCell
*/
- (instancetype)initWithTableView:(UITableView *)tableView range:(NSRange)range cellHeight:(CGFloat)cellHeight cellClass:(Class)cellClass identifier:(NSString *)identifier;
此處,有很多細節方面,我不是很贊同其處理,例如 range Cell 的范圍,當然此種方式已經能滿足我們大部分的場景的(不是統一數據的類型那種)。
另外,類命名也不認同, ZHTableViewCell 是屬于一個 Manager 的,不應該直接命名為 Cell 結尾,容易讓人誤解。
三、看 《RETableViewManager》
于是我先去看看 RETableViewManager ,希望可以解決一些疑惑。這個輪子就是為了解決多種樣式多種點擊的列表。
直接看一下其 最基本用法:
- (void)viewDidLoad {
[super viewDidLoad];
// 總的 Manager
self.manager = [[RETableViewManager alloc] initWithTableView:self.tableView];
// Section
RETableViewSection *section = [RETableViewSection sectionWithHeaderTitle:@"Test"];
[self.manager addSection:section];
// Cell Item (基本的 Item)
RETableViewItem *customItem = [RETableViewItem itemWithTitle:@"String cell" accessoryType:UITableViewCellAccessoryDisclosureIndicator selectionHandler:^(RETableViewItem *item) {
NSLog(@"Test: %@", item);
}];
// Section Add Item
[section addItem:customItem];
}
大致可以了解到 Cell ,Section 通通都由一個 RETableViewManager 來整體管理,而且 Cell 處的點擊和樣式都是單獨控制的,低耦合高內聚,這樣可以任意添加 Cell(此處的 Cell 都是基于其自定義的Cell),此處我的想法是自己是否也可以這樣做呢?當然 Cell 是任意的,并不要遵循某個BaseCell。
四、理解改善
其實ZHTableViewGroup 已經可以說對其樣式和對其點擊已經抽離出來了,但可以對 ZHTableViewGroup 進行三點改善,當然也是可商榷的。
- 1、類命名更規范,將
ZHTableViewCell
==>ZHTableViewManager
相對來說更正確。 -
2、將 Rang 抽離出來,這樣更利于理解和更利于優化。。
如果不寫,就是直接按照順序,一步一步添加上去,默認順序 - 3、group 分組,實際還是可以蓋上成當真的分組時,自動有空行空出來,實際上換句話說就是可以說此處的結構還可以優化。
晉級: 將高度自適應,暫時沒進行,但是可以嘗試,不過從另一個角度來說,通常如果類似我想應用的場景,一般高度都是固定的,所以也沒必要,否則這個場景頁面就不知道什么情況啦。
例如:下面是我將其 Rang 取消后一個 Cell 往一個 Cell 的結果
@implementation ZHTableViewCell {
NSRange _tempRange;
UITableView *_tableView;
}
static NSUInteger number = 0;
- (instancetype)initWithTableView:(UITableView *)tableView cellHeight:(CGFloat)cellHeight cellClass:(Class)cellClass identifier:(NSString *)identifier {
if (self = [super init]) {
NSParameterAssert(tableView);
NSParameterAssert(identifier);
_tableView = tableView;
NSDictionary *cellClassDict = [tableView valueForKey:@"_cellClassDict"];
__block BOOL isExitRegister = NO;
[cellClassDict.allKeys enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([obj isEqualToString:identifier]) {
isExitRegister = YES;
}
}];
if (!isExitRegister) {
[tableView registerClass:cellClass forCellReuseIdentifier:identifier];
}
_tempRange = NSMakeRange(number, 1);
number++;
// _range = range;
_cellHeight = cellHeight;
_identifier = identifier;
}
return self;
}
- (BOOL)cellIsExitRangeWithIndex:(NSUInteger)index {
// return NSLocationInRange(index, _range);
return NSLocationInRange(index, _tempRange);
}
- (UITableViewCell *)cellWithIndexPath:(NSIndexPath *)indexPath {
return [_tableView dequeueReusableCellWithIdentifier:_identifier forIndexPath:indexPath];
}
@end
此處這樣改完后,又發現沒必要,一步一步加上去雖說好用,但是脫離出來其實也沒必要,只是老覺的這樣一連串參數初始化 感覺怪怪的。
如后期繼續擴展維護,寫法和規范像 RETableViewManager 學習是必要的。
PS : 目前ZHTableViewGroup 上傳的 DEMO 還有問題,仔細看就知道了,哈哈。
五、知識點
_cellClassDict 作為 UITableView 的私有變量,保存 cellClass 和 cellIden 用的。
NSLocationInRange
NS_INLINE BOOL NSLocationInRange(NSUInteger loc, NSRange range) {
return (!(loc < range.location) && (loc - range.location) < range.length) ? YES : NO;
}
類似這樣的內斂,有時效果還是很巧妙的
-
當然更多的是對 UITableView 的熟悉度更加深了。
效果圖
類似一連串的 View 的布局,我們就還是可以采用 UITableView, 可以解決上述淘寶 home 首頁那樣的問題,讓每一排 View 當做一個 Cell, 實現起來還是很方便的。