最近在想关于 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, 实现起来还是很方便的。