您的位置:首页 > 产品设计 > UI/UE

自定义UITableView索引动画,实现饿了么菜单效果

2016-06-29 23:21 651 查看
授人以鱼不如授人以渔,“授”这个字,不敢当,只把做这个效果的思路和大家分享一下。

需求和饿了么商店菜单效果是一样一样的,如下图



一、需求分析

     作为一个码农,当拿到一个任务的时候,第一要务必然是需求分析,略过这一步早早的开工,很大可能会给自己埋下大坑,最后还得自己填了这个坑。甚至可以说,需求分析占到整个开发周期的三分之一都不为过。明确需要做什么,就有了开发的方向,如果一开始就冲动开发,一边开发一边捋业务逻辑,脑袋频繁切换任务类型,这种形式的开发效率是很低的。好的开始,是成功的一半!

     废话少说,进入正题,分析如下:

     从直观上看,该效果是由左右两个UITableView实现的。即leftTableView(也可以看做是索引表视图indexTableView)、rightTableView

     需求1:当点击左边cell时,右边对应的cell会滑动到最顶端,例如点击主食,那么主食分类下的这组cell会滑动到顶端;

     需求2:当点击左边cell时,cell会有一个标记,代表选中了当前cell,例如上图的蓝色矩形条;

     需求3:当rightTableView滑动的时候,indexTableView要标记与rightTableView最上面cell对应的cell,例如rightTableView滑动停止时最上面的一个cell属于主食,那么indexTableView中主食这一cell应该被标记选中;

     需求4:点击右边cell的蓝色加号,进行多项的选择(该需求不在本文讨论范围,本文只讨论联动的效果)

二、寻找满足需求可能需要的API

     看需求1知道,当点击的时候需要滑动,既然要滑动,就需要知道滑动到什么位置,所以需要找的API有两种,一种和位置有关,一种和滑动有关。通过点击UITableView类进入UITableView.h找到如下API:

滑动相关 

UITableView
- (void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated;
- (void)scrollToNearestSelectedRowAtScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated;
- (void)selectRowAtIndexPath:(nullable NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(UITableViewScrollPosition)scrollPosition;
- (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection NS_AVAILABLE_IOS(5_0);
- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath NS_AVAILABLE_IOS(5_0);

UITableView继承与UIScrollView所以,查找的范围也包括UIScrollView.h,管它用不用的到,先找出来

- (void)setContentOffset:(CGPoint)contentOffset animated:(BOOL)animated;
- (void)scrollRectToVisible:(CGRect)rect animated:(BOOL)animated;

位置相关,对于UITableVIew来说等同于NSIndexPath

- (nullable NSIndexPath *)indexPathForRowAtPoint:(CGPoint)point;
- (nullable NSIndexPath *)indexPathForCell:(UITableViewCell *)cell;
- (nullable NSArray<NSIndexPath *> *)indexPathsForRowsInRect:(CGRect)rect;
- (nullable NSIndexPath *)indexPathForPreferredFocusedViewInTableView:(UITableView *)tableView NS_AVAILABLE_IOS(9_0);
- (nullable NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath;
- (nullable NSIndexPath *)tableView:(UITableView *)tableView willDeselectRowAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(3_0);
@property (nonatomic, readonly, nullable) NSArray<NSIndexPath *> *indexPathsForVisibleRows;

最终敲定
实现需求1选定的API为:
- (void)scrollToRowAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(UITableViewScrollPosition)scrollPosition animated:(BOOL)animated;

实现代码:

#pragma mark - UITableViewDelegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (tableView == _indexTableView) {
[self reloadSelectedCellAtIndex:indexPath.row];

NSIndexPath *toIndexPath = [NSIndexPath indexPathForRow:0 inSection:indexPath.row];
[_detailTableView scrollToRowAtIndexPath:toIndexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
}

实现需求2的方式比较简答,数据控制界面,再次不多说,不清楚的可以下载源码。

实现需求3选定的API为:

@property (nonatomic, readonly, nullable) NSArray<NSIndexPath *> *indexPathsForVisibleRows;

实现代码:

#pragma mark - UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView == _indexTableView) {
_left_isScrolling = YES;
}
else
{
_left_isScrolling = NO;
}

if (scrollView == _detailTableView) {
if (_left_isScrolling) {
return;
}
//获取detail当前最上方显示的cell所在的indexPath
NSArray *arr = [_detailTableView indexPathsForVisibleRows];
if (arr.count > 0) {
NSIndexPath *tempPath = [arr objectAtIndex:0];
if (tempPath) {
if (_last_indexPath.section != tempPath.section) {//减少tableView的刷洗次数,当section变化的时候才刷新
[self reloadSelectedCellAtIndex:tempPath.section];

4000
_last_indexPath = tempPath;
}
}
}
}
}

三、数据模拟,数据处理,以便于UI使用

假设后台给的未经处理的一组数据,需要将数据进行分组,重新组合成UI方便使用的数据样式。本例的UI左边需要一个索引数组,每个索引对应有某一共同特点的一组数据。Demo中我选用的样式是,省份的简称,和该省对应的城市名称。未经处理的数据单个元素模型如下:
@interface DemoModel : NSObject
@property (nonatomic ,copy) NSString *province;     //省份的简称
@property (nonatomic ,copy) NSString *city_name;    //城市的名字
@property (nonatomic ,assign) BOOL is_selected;     //是否选中,用于左侧索引的选中效果判断
@end

所以,对于索引UI需要的数据即为1.省份的简称province,2.是否在显示当前省份的城市is_selected(所有的城市数据中涉及到几个省份,索引项就有几个);对于右侧城市部分,则是以所在省份为分组依据分成的多组数据。
代码层面的操作如下:

1.过滤掉数据中的重复省份的城市,每个涉及省份只留一个数据作为索引关键字(个人认为数据处理的核心代码)

//过滤数组中重复的元素,生成左边的索引关键字数组
- (NSArray *)filteredCityArrSourceArr:(NSArray *)sourceArr
{
NSMutableArray *resultArr = [NSMutableArray array];
//找出原始城市数据
NSMutableArray *originalCityArr = [NSMutableArray array];
for (DemoModel *model in sourceArr) {
[originalCityArr addObject:model.province];
}
//去掉重复的字符串元素
for (NSString *str in originalCityArr) {
if (![resultArr containsObject:str]) {
[resultArr addObject:str];
}
else
{
continue;
}
}
return resultArr;
}

2.根据关键字将数组分组按省份组成二维数组

//创建二维数组
for (int i=0; i<keyArr.count; i++) {
NSString *currentKey = keyArr[i];
NSMutableArray *tempArr = [NSMutableArray array];
for (DemoModel *model in originalData) {
if ([model.province isEqualToString:currentKey]) {
[tempArr addObject:model];
}
}
[self.detailArr addObject:tempArr];
}

3.合成索引数据

//合成indexArr,从二维数组中每组去一个model就ok了
for (int i = 0; i<self.detailArr.count; i++) {
[self.indexArr addObject:self.detailArr[i][0]];
}

感觉数据处理的思路比较暴力,也可能有我不知道的iOS官方封装好的API,有更好方式方法的朋友,还望不吝赐教

四、结果展示



五、源码下载地址:

https://github.com/StrongerWM/IndexTableViewDemo
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  uitableview 数据