iOS如何写一个tableView

iOS如何写一个tableView

本人的文字功夫不行,请大家多多谅解!

序言:首先,本篇不是教大家怎么怎么使用苹果已经自定义好的UITableView控件,而是怎样自己写一个tableView 。纳尼?有人肯定想写来有个毛线用啊,苹果的那么Perfect,直接用就简单完事了。是的,道理是这样的,但我是实现tableView的基本功能-重用,为以后聊重用机制时可以侃侃而谈罢了(你懂的)。那么开始:

  • 先模拟好UITableView。建JSTableView继承自UIScrollView。写好两个协议JSTableViewDelegate和JSTableViewDataSource。JSTableView的.h代码如下:
#import <UIKit/UIKit.h>
#import "JSTableCell.h"

@class JSTableView;
@protocol JSTableViewDelegate<NSObject>
-(void)tableView:(JSTableView *)tableView didSelectRow:(NSInteger)row;
@end

@protocol JSTableViewDataSource<NSObject>
@required
- (NSInteger)cellNumbersWithTableView:(JSTableView*)tableView;
- (JSTableCell*)tableView:(JSTableView*)tableView cellForRow:(NSInteger)row;
- (CGFloat)tableView:(JSTableView*)tableView cellHightForRow:(NSInteger)row;
@end



@interface JSTableView : UIScrollView
@property (nonatomic,weak)id<JSTableViewDelegate>jsDelegate;
@property (nonatomic,weak)id<JSTableViewDataSource>jsDataSource;

- (JSTableCell*)dequeueReusableCell;//获取可重用的cell
- (void)reloadData;

@end

  • JSTableView 重用池该怎么建立,看看tableView官方定义属性:
@property (nonatomic, readonly) NSArray<__kindof UITableViewCell *> *visibleCells;
visibleCells 可见的cell数组,那是否有不可见的cell数组呢,我想也是有的,只是苹果认为这没有什么价值,不让我等看见。那我们建重用池就好办了,直接建两个数组。以下是私有成员列表:
//展示中的cell
@property (nonatomic,strong)NSMutableArray*visibleCells;
//未展示的cell
@property (nonatomic,strong)NSMutableArray*invalidCells;
//scrollView-旧的偏移值y
@property (nonatomic,assign)CGFloat original_y;
//数据总数
@property (nonatomic,assign)NSInteger totalCounts;
//记录最小的cell的高度
@property (nonatomic,assign)CGFloat minimumCellHight;
  • 初始化按基本的来就行
- (instancetype)initWithFrame:(CGRect)frame{
    self = [super initWithFrame:frame];
    if(self){
        self.showsVerticalScrollIndicator = NO;
        self.delegate = self;
        self.scrollEnabled = YES;
        self.userInteractionEnabled = YES;
        self.original_y = 0.f;
        //初始设置为一个比较大的值
        self.minimumCellHight = self.frame.size.height;
    }
    return self;
}


#懒加载两个数组
- (NSMutableArray*)visibleCells{
    if(!_visibleCells){
        _visibleCells = [[NSMutableArray alloc] init];
    }
    return _visibleCells;
}
- (NSMutableArray*)invalidCells{
    if(!_invalidCells){
        _invalidCells = [[NSMutableArray alloc] init];
    }
    return _invalidCells;
}
  • 构建reloadData方法
- (void)reloadData{
    //布局基本数据
    self.totalCounts = [self.jsDataSource cellNumbersWithTableView:self];
    CGFloat start_y = 0.f;
    NSInteger loadNumber = self.totalCounts;
    for(NSInteger i=0;i<self.totalCounts;i++){
        CGFloat cellHight = [self.jsDataSource tableView:self cellHightForRow:i];
        if(self.minimumCellHight>cellHight) self.minimumCellHight=cellHight;
        JSTableCell*cell = [self.jsDataSource tableView:self cellForRow:i];
        cell.row = i;
        cell.frame = CGRectMake(0, start_y, self.frame.size.width, cellHight);
        start_y = start_y + cellHight;
        [self addSubview:cell];
        //将cell加入已展示的数组中
        [self.visibleCells addObject:cell];
        loadNumber = i;
        if(start_y>self.frame.size.height) break;
    }
    [self resetContentSize];
}

- (void)resetContentSize{
    JSTableCell*cell = self.visibleCells.lastObject;
    CGFloat needHight = cell.frame.origin.y+cell.frame.size.height;
    if(needHight<self.frame.size.height) needHight=self.frame.size.height + 1;
    self.contentSize = CGSizeMake(self.frame.size.width,needHight);
}

  • 写重用方法
- (JSTableCell*)dequeueReusableCell{
    //如果未展示的数组中有数据,则返回一条重用
    if(self.invalidCells.count>0) return self.invalidCells.firstObject;
    return nil;
}
纳尼,这么简单?就是这么简单。。。
  • 到目前为止,就可以简单显示出来界面了。只是还不能滑动显示。接着我们根据scrollViewDidScroll代理方法更新和回收cell
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
    /*
     * 这里最初只是  [self refreshViewWithOffsety:scrollView.contentOffset.y];即可
     * 考虑到向上或向下快速拉一个很大的范围,会导致cell加载不出来。为了保证滑动中每一个cell都能够被加载,
     * 这里写了一个新旧位置中取点的循环,循环间隔是minimumCellHight(minimumCellHight是cell的最小高度)
     */
    
    CGFloat y = scrollView.contentOffset.y;
    if(y>self.original_y){
        for(CGFloat i = self.original_y+self.minimumCellHight; i<y;i = i + self.minimumCellHight){
            [self refreshViewWithOffsety:i];
        }
        [self refreshViewWithOffsety:y];
    }else{
        for(CGFloat i = self.original_y-self.minimumCellHight; i>y;i = i - self.minimumCellHight){
            [self refreshViewWithOffsety:i];
        }
        [self refreshViewWithOffsety:y];
    }
}
- (void)refreshViewWithOffsety:(CGFloat)contentOffsety{
    if(contentOffsety>self.original_y){
        //向上滑动
        self.original_y = contentOffsety;
        JSTableCell*firstCell = self.visibleCells.firstObject;
        JSTableCell*lastCell  = self.visibleCells.lastObject;
        if(lastCell.row>=self.totalCounts){
            return;
        }
        if(firstCell.frame.origin.y+firstCell.frame.size.height<contentOffsety){
            [self.invalidCells addObject:firstCell];//回收
            [self.visibleCells removeObject:firstCell];//从已展示中移除
        }
        if(lastCell.frame.size.height+lastCell.frame.origin.y>contentOffsety+self.frame.size.height){
            return;
        }else{
            //底部添加1个cell
            NSInteger newRow = lastCell.row + 1;
            if(newRow>self.totalCounts-1){
                [self resetContentSize];
                return;
            }
            CGFloat cellHight = [self.jsDataSource tableView:self cellHightForRow:newRow];
            if(self.minimumCellHight>cellHight) self.minimumCellHight=cellHight;
            JSTableCell*cell = [self.jsDataSource tableView:self cellForRow:newRow];
            cell.row = newRow;
            cell.frame = CGRectMake(0, lastCell.frame.origin.y+lastCell.frame.size.height, self.frame.size.width, cellHight);
            if(cell.superview){ //有父视图说明不是新建的
                [self.invalidCells removeObject:cell];
            }else{
                [self addSubview:cell];
            }
            [self.visibleCells addObject:cell];
            [self resetContentSize];
        }
    }else{
        //向下滑动
        self.original_y = contentOffsety;
        if(contentOffsety<0){
            return;
        }
        JSTableCell*firstCell = self.visibleCells.firstObject;
        JSTableCell*lastCell  = self.visibleCells.lastObject;
        if(lastCell.frame.origin.y>contentOffsety+self.frame.size.height){
            [self.invalidCells addObject:lastCell];
            [self.visibleCells removeObject:lastCell];
        }
        if(firstCell.frame.origin.y+firstCell.frame.size.height<contentOffsety){
            return;
        }else{
            //顶部添加1个cell
            NSInteger newRow = firstCell.row - 1;
            if(newRow<0){
                [self resetContentSize];
                return;
            }
            CGFloat cellHight = [self.jsDataSource tableView:self cellHightForRow:newRow];
            if(self.minimumCellHight>cellHight) self.minimumCellHight=cellHight;
            JSTableCell*cell = [self.jsDataSource tableView:self cellForRow:newRow];
            cell.row = newRow;
            cell.frame = CGRectMake(0, firstCell.frame.origin.y-cellHight, self.frame.size.width, cellHight);
            if(cell.superview){
                [self.invalidCells removeObject:cell];
            }else{
                [self addSubview:cell];
            }
            [self.visibleCells insertObject:cell atIndex:0];
            [self resetContentSize];
        }
    }
    self.original_y = contentOffsety;
}

  • ?JSTableCell类还没介绍,JSTableCell 非常简单
@interface JSTableCell : UIView
//cell的row值
@property (nonatomic,assign)NSInteger row;
@end
  • 最后怎么用,请看代码(请忽略我的):
#import "ViewController.h"
#import "JSTableView.h"
#import "JSTableCell.h"

@interface ViewController ()<JSTableViewDelegate,JSTableViewDataSource>{
    JSTableView*tableView;
    NSMutableArray*array;
    UITableView*a;
}
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    tableView = [[JSTableView alloc] initWithFrame:self.view.bounds];
    tableView.jsDataSource=self;
    tableView.jsDelegate  =self;
    [self.view addSubview:tableView];
    
    array = [NSMutableArray array];
    NSString*testString = @"秋天,一个金色美好的季节,它带走的不仅仅是酷暑,也为大地换上了别具一格的新装。我对秋天印象最深的不是一望无际的丰收稻田,也不是果实累累的果园,而是那最平凡不过的树叶了。作为秋天树叶的象征,满山遍野的红枫成为最醒目的主角。远远眺望,那一团团熊熊燃烧的烈火,显示出它们顽强的生命力。走进枫林,片片宛如小手掌似的枫叶环绕在你的周围。一阵清爽的秋风吹过,带着万般眷恋的树叶,悄然无声地投进大地母亲温暖的环抱。调皮的树叶有时也会与风姑娘玩起捉迷藏游戏,伴随着山鸣谷应和树叶们演奏的沙沙乐曲,跟随着小鸟动听的歌喉,尽情地展示自己,沐浴着阳光在翩翩起舞。若你拾起一片枫叶,夹杂着泥土馨香的味道就会扑鼻而来。从枫叶清晰的叶脉、色彩的变化中我对唐代著名诗人杜牧的:“停车坐爱枫林晚,霜叶红于二月花”有了颇深的体会。最常见的梧桐树叶在沐浴阳光的同时也为写生者创建了一幅瑰丽的油画。从树叶的经脉处透露出秋的萧瑟,而与此不和谐的是绿油油的叶子,它们衬托出了秋的生机勃勃,构成了一幅鲜明的对比画。数不胜数的银杏叶也不亚于梧桐,它们奇特的小扇形树叶引人注目。当你漫步在银杏的“海洋”中时,就会被金色之秋所吸引,甚至自己也会化成片片被秋风摇曳的树叶,在风中回旋";
    NSInteger length = testString.length;
    for(int i=0;i<200;i++){
        NSInteger len = arc4random()%10+10;
        NSInteger loc = arc4random()%(length-len);
        [array addObject:[NSString stringWithFormat:@"%d.%@",i,[testString substringWithRange:NSMakeRange(loc, len)]]];
    }
    [tableView reloadData];
}

#pragma mark -JSTableViewDataSource-
- (NSInteger)cellNumbersWithTableView:(JSTableView *)tableView{
    return array.count;
}
- (CGFloat)tableView:(JSTableView *)tableView cellHightForRow:(NSInteger)row{
    return 40;
}
- (JSTableCell *)tableView:(JSTableView *)tableView cellForRow:(NSInteger)row{
    JSTableCell*cell = [tableView dequeueReusableCell];
    if(!cell){
        cell = [[JSTableCell alloc] init];
    }
    cell.lb.text = array[row];
    return cell;
}
#pragma mark -JSTableViewDelegate-
- (void)tableView:(JSTableView *)tableView didSelectRow:(NSInteger)row{
    NSLog(@"点击了 %ld 个",row);
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];

    // Dispose of any resources that can be recreated.
}

一张截图

在这里插入图片描述

小结:这其实并没有什么使用价值,只是让我们更深入地理解重用的原理而已,或许苹果有更好的方法做。

git下载

谢看!转载需申明?
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值