授人以鱼不如授人以渔,“授”这个字,不敢当,只把做这个效果的思路和大家分享一下。
需求和饿了么商店菜单效果是一样一样的,如下图
一、需求分析
作为一个码农,当拿到一个任务的时候,第一要务必然是需求分析,略过这一步早早的开工,很大可能会给自己埋下大坑,最后还得自己填了这个坑。甚至可以说,需求分析占到整个开发周期的三分之一都不为过。明确需要做什么,就有了开发的方向,如果一开始就冲动开发,一边开发一边捋业务逻辑,脑袋频繁切换任务类型,这种形式的开发效率是很低的。好的开始,是成功的一半! 废话少说,进入正题,分析如下: 从直观上看,该效果是由左右两个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所在的indexPathNSArray *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];_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,有更好方式方法的朋友,还望不吝赐教