文档章节

通过实现一个横向Tableview,了解UITableview工作原理

hanbing94
 hanbing94
发布于 2017/05/08 21:20
字数 1609
阅读 27
收藏 0

作者coderZ

UITableview代理方法介绍

UITableview有两个相关代理UITableViewDelegate、UITableViewDataSource
dataSource是数据源代理,delegate则是相关操作代理

dataSource

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section

通过返回值,告诉tableview的某个section应该显示多少个单元格

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath

通过返回值,告诉tableview,indexPath索引下的单元格的高度,tableview单元格的宽度与tableview相同

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

通过返回cell,告诉tableview,indexPath索引下应该展现的单元格
以上便是tableview dataSource最基本的,也是必须实现的三个代理,通过这三个代理可以展现一个最基本的tableview

delegate

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath

点击单元格回调方法

UITableViewCell以及重用

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
    if (cell == nil) {
        UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
    }
    return cell;
}

以上是tableview实现重用的基本写法,通过创建一个带有重用标识符的cell,tableview每次通过代理获取cell时,都会先从重用池中获取,节省内存消耗。 以上是tableview最基础的几个代理方法,下面通过代码实现这几个代理方法,一窥tableview内在工作的原理

实现一个横向的tableview:MinScrollMenu

ps:源代码已上传至github:MinScrollMenu

introduce.gif

1 定义代理

- (NSInteger)numberOfMenuCount:(MinScrollMenu *)menu;
- (CGFloat)scrollMenu:(MinScrollMenu*)menu widthForItemAtIndex:(NSInteger)index;
- (MinScrollMenuItem *)scrollMenu:(MinScrollMenu*)menu itemAtIndex:(NSInteger)index;
- (void)scrollMenu:(MinScrollMenu*)menu didSelectedItem: (MinScrollMenuItem *)item atIndex: (NSInteger)index;

模仿之前介绍的四个代理方法。

2 布局

创建一个继承UIView的子类,命名为MinScrollMenu。
(1)添加一个scrollView属性,初始化加到MinScrollMenu上,frame大小和父视图一样。正如系统的UITableView一样,我们也使用scrollView来实现功能

@property (nonatomic, strong) UIScrollView *scrollView;/*!< 横向滚动的scrollView */

(2)添加一个继承自UIView的属性,命名为contentView,初始化加到之前创建好的scrollView上。frame可以先不设置,这个view主要用来装载将来要显示的单元格,frame大小需要以后计算。

@property (nonatomic, strong) UIView *contentView;/*!< 装载item的view */

(3)以下几个属性主要用来缓存单元格数据源的数据

@property (nonatomic, strong) NSMutableArray *visibleItems;/*!< 屏幕范围内的item数组 */
@property (nonatomic, strong) NSMutableSet *reuseableItems;/*!< 重用池 */
@property (nonatomic, strong) NSMutableDictionary *infoDict;/*!< 缓存item被选中信息 */
@property (nonatomic, strong) NSMutableDictionary *frameDict;/*!< 缓存item的frame */

3 处理数据源数据

(1) 根据代理获取item个数

    if (self.delegate != nil && [self.delegate respondsToSelector:@selector(numberOfMenuCount:)]) {
        _count = [self.delegate numberOfMenuCount:self];
    }

(2) 循环创建单元格item,因为是横向滚动的,所以主要获取宽度和改变x轴的值计算frame。计算出所有的item的frame并装在字典中缓存,然后就可以得出之前没有设置的contentView的frame了,贴出代码:

    for (NSInteger i = 0; i < _count; ++i) {
        //获取item的宽度
        width = [self itemWidthWithIndex:i];
        CGRect itemFrame = CGRectMake(x, y, width, height);

        // 超过屏幕可显示范围不加入到visibleItems数组
        CGFloat maxX = CGRectGetMaxX(itemFrame);
        CGFloat overItemWidth = width*3;
        if (i < _count-3) {
            overItemWidth = width + [self itemWidthWithIndex:i+1] + [self itemWidthWithIndex:i+2];
        }
        isOverScreenWidth = maxX > ScreenWidth + overItemWidth;
        if (!isOverScreenWidth) {
            // 获取item,设置Frame, 添加到contentView上
            MinScrollMenuItem *item = [self itemWithIndex:i];
            if (item) {
                item.frame = itemFrame;
                [_contentView addSubview:item];

                // 添加点击手势
                UITapGestureRecognizer *tapGst = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapItem:)];
                [item addGestureRecognizer:tapGst];

                item.tag = ITEMTAG + i;

                // 加入到visibleItems数组
                [_visibleItems addObject:item];
            }
        }

        // 缓存数据
        [_frameDict setObject:@(i) forKey:NSStringFromCGRect(itemFrame)];
        [_infoDict setObject:@(NO) forKey:@(i)];

        // 计算scrollView的contentSize
        scrollContentWidth = maxX;

        x += width;
    }

    _scrollView.contentSize = CGSizeMake(scrollContentWidth, height);
    _contentView.frame = CGRectMake(0, 0, scrollContentWidth, height);

完成以上代码,运行一下。就可以看见item显示了,但是滚动处理还没有完成,所以手指拖动scrollView右边区域还是空白一片,接下来就是核心的滚动处理和重用机制的实现
(3) 重用和滚动处理
重用和滚动处理是同时进行的,当tableView向右滚动时,如果最左边的item已经离开屏幕范围,那么就可以将它放进重用池中存储,同时也要根据item的标识符从重用池里取出item,设置frame,添加到visibleItems数组。如此就可以循环使用几个item来展现n个item的内容了。
实现UIScrollView代理方法

- (void)scrollViewDidScroll:(UIScrollView *)scrollView

通过这个方法可以获取当前scrollView移动的位移contentOffset
具体思路如下图所示:

滚动.png

重用机制代码:
内部查找重用的item:

    NSSet *tempSet = [_reuseableItems filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"reuseIdentifer == %@", reuseItem.reuseIdentifer]];
    // 查询复用池中有没有相同复用符的item
    if (![tempSet isSubsetOfSet:_reuseableItems] || tempSet.count == 0) {
        // 没有则添加item到复用池中
        [_reuseableItems addObject:reuseItem];
    }

公开API实现的代码:

- (MinScrollMenuItem *)dequeueItemWithIdentifer:(NSString *)identifer {
    NSSet *tempSet = [_reuseableItems filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"reuseIdentifer == %@", identifer]];
    MinScrollMenuItem *item = tempSet.anyObject;
    return item;
}

也可以不用谓词查询,直接使用循环查找,因为重用池每种标识符item一般只需要一个就足够了。所以Set元素个数比较少。
(4)数据刷新reloadData方法实现
思路如下图所示;

数据刷新.png

(5)点击item回调响应方法实现:
Menu内的实现:
首先,将遍历之前保存选中状态的字典,如果value是YES,则修改为NO
第二,遍历屏幕显示item数组visibleItems,将item的isSelected属性设为NO
第三,将选中的item状态改为选中,通过tag值获取index索引,保存到缓存字典中。
第四,回调代理方法,通知控制器
贴上具体代码:

- (void)tapItem: (UITapGestureRecognizer *)tapGst {
    [_infoDict enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, NSNumber *obj, BOOL * _Nonnull stop) {
        *stop = obj.boolValue;
        if (*stop) {
            _infoDict[key] = @(NO);
        }
    }];
    for (MinScrollMenuItem *item in _visibleItems) {
        item.isSelected = NO;
        [_infoDict setObject:@(NO) forKey:@(item.tag-ITEMTAG)];
    }

    if ([tapGst.view isKindOfClass:[UIView class]]) {

        UIView *tempView = tapGst.view;
        MinScrollMenuItem *item = (MinScrollMenuItem *)tempView;

        if ([item isKindOfClass:[MinScrollMenuItem class]]) {
            item.isSelected = YES;
            [_infoDict setObject:@(YES) forKey:@(item.tag-ITEMTAG)];
            if (self.delegate && [self.delegate respondsToSelector:@selector(scrollMenu:didSelectedItem:atIndex:)]) {
                [self.delegate scrollMenu:self didSelectedItem:item atIndex:item.tag - ITEMTAG];
            }
        }
    }
}

Item内的实现:
首先,item添加一个选中状态的CALayer类作为属性,创建好添加到item的layer上,不要忘记了设置为隐藏,hidden=YES。再提供一个对外开放的BOOL值isSelected属性。
第二,重写isSelected属性set方法,被选中时修改layer的hidden为NO即可。

后记:tableview的四个基本代理方法已经都实现了。通过这个横向滚动的类tableview控件,对tableview的工作原理有了更深一层的认识,当然tableview还有很多功能没有实现,但是基本框架完成了,一些功能性的东西后面陆续可以添加。大家可以通过github地址:MinScrollMenu 下载源码查看,不嫌弃的点个星吧:)

本文转载自:

共有 人打赏支持
hanbing94
粉丝 2
博文 66
码字总数 38602
作品 0
朝阳
私信 提问
UI_09 UITableView(表视图)

⼀、表视图 在iOS中,要实现表格数据展示,最常用的做法就是使用UITableView。UITableView继承自UIScrollView,因此支持垂直滚动,而且性能极佳 1、表示图的样式 UITableViewStylePlain UITa...

黑伞将军
2015/08/26
0
0
UITableView的优化原理

当我们下啦一个 UITableView时,如果没有做优化,只是简单的实现功能代码如下,这样当我们有上百条tableviewcell的时候,我们滑动的非常快时会非常费内存,当然苹果公司不会让我们这样干,苹...

哥特复心
2013/11/29
0
3
UITableView 的更多属性

UITableView的初始化 [csharp] UITableView tableview= [[UITableView alloc] initWithFrame:CGRectMake(0, 0, 320, 420)]; [tableview setDelegate:self]; [tableview setDataSource:self]......

meilidashijie
2013/01/04
0
1
iOS UITableView代理方法详解

iOS UITableView的代理方法详解 一、补充 在上一篇博客中,http://my.oschina.net/u/2340880/blog/404605,我将IOS中tableView(表视图)的一些常用方法总结了一下,这篇将tableView的代理方法...

珲少
2015/04/22
0
2
UITableView 基本使用方法总结

首先,Controller需要实现两个 delegate ,分别是 UITableViewDelegate 和 UITableViewDataSource 2.然后 UITableView对象的 delegate要设置为 self。 3. 然后就可以实现这些delegate的一些方...

悠哉悠哉e
2012/11/13
0
0

没有更多内容

加载失败,请刷新页面

加载更多

码云项目100,水一发

简单回顾一下: 早期构想最多的,是希望能将PHP一些类和编码分区做得更细,所以很多尝试。但不得不说,PHP的功能过于单一,是的,也许写C/C++扩展,可以解决问题,那我为什么不用C#或者Golan...

曾建凯
18分钟前
1
0
Spring应用学习——AOP

1. AOP 1. AOP:即面向切面编程,采用横向抽取机制,取代了传统的继承体系的重复代码问题,如下图所示,性能监控、日志记录等代码围绕业务逻辑代码,而这部分代码是一个高度重复的代码,也就...

江左煤郎
今天
4
0
eclipse的版本

Eclipse各版本代号一览表 Eclipse的设计思想是:一切皆插件。Eclipse核心很小,其它所有功能都以插件的形式附加于Eclipse核心之上。 Eclipse基本内核包括:图形API(SWT/Jface),Java开发环...

mdoo
今天
3
0
SpringBoot源码:启动过程分析(一)

本文主要分析 SpringBoot 的启动过程。 SpringBoot的版本为:2.1.0 release,最新版本。 一.时序图 还是老套路,先把分析过程的时序图摆出来:时序图-SpringBoot2.10启动分析 二.源码分析 首...

Jacktanger
今天
4
0
小白带你认识netty(二)之netty服务端启动(上)

上一章 中的标准netty启动代码中,ServerBootstrap到底是如何启动的呢?这一章我们来瞅下。 server.group(bossGroup, workGroup);server.channel(NioServerSocketChannel.class).optio...

天空小小
今天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部