scrollview复用节点_利用ScrollView实现TableView效果(实现复用)
TableView在iOS中的作用毋庸置疑,每一款App或多或少的都會使用到UITableView這個控件。作為iOS開發者,我們知道UITableView是繼承自UIScrollView的,那么UITableView是如何在繼承UIScrollView的基礎上實現的呢?今天我們就嘗試用代碼一步步分析,在UIScrollView的基礎上實現一個簡單的TableView。
** 在這里需要感謝各路大神的分享,這篇文章是參考Colin Eberhardt的一系列文章最后總結記錄于簡書,為自己的iOS之路添磚加瓦。**
創建自定義表格視圖
新建一個工程,這里命名為CustomTableView,新建一個類,繼承UIScrollView,類名為YFTableView:
1.png
仿照原生的UITableView為YFTableView添加一個簡便的@protocol:
@protocol YFTableViewDataSource
//表示表中的行數
- (NSInteger)numberOfRows;
//獲取給定的單元格
- (UIView *)cellForRow:(NSInteger)row;
@end
同時在YFTableView.h文件中添加dataSource:
@property (nonatomic,assign) id dataSource;
在YFTableView.m中實現以下方法,每個方法都有詳細的注釋:
//初始化
- (instancetype)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
self.backgroundColor = [UIColor clearColor];
}
return self;
}
//更新布局
- (void)layoutSubviews{
[super layoutSubviews];
[self refreshView];
}
//固定行高
const float SHC_ROW_HEIGHT = 50.0f;
- (void)refreshView{
//設置scrollView的高度
self.contentSize = CGSizeMake(self.bounds.size.width, [_dataSource numberOfRows] * SHC_ROW_HEIGHT);
//添加cell
for (int row = 0; row < [_dataSource numberOfRows]; row ++) {
//獲得cell
UIView * cell = [_dataSource cellForRow:row];
//每個cell的y坐標
float topEdgeForRow = row * SHC_ROW_HEIGHT;
//每個cell的位置
CGRect frame = CGRectMake(0, topEdgeForRow, self.frame.size.width, SHC_ROW_HEIGHT);
cell.frame = frame;
//添加到視圖
[self addSubview:cell];
}
}
#pragma mark - property setters
- (void)setDataSource:(id)dataSource{
_dataSource = dataSource;
[self refreshView];
}
新建一個類YFTableViewCell繼承自UITableViewCell:
2.png
回到ViewController.m創建YFTableView,并遵行協議實現簡單的TableView效果:
#import "ViewController.h"
#import "YFTableView.h"
#import "YFTableViewCell.h"
#define SCREEB_SIZE [UIScreen mainScreen].bounds.size
@interface ViewController ()
@property (nonatomic,strong) YFTableView * tableView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView = [[YFTableView alloc] initWithFrame:CGRectMake(0, 20, SCREEB_SIZE.width, SCREEB_SIZE.height - 20)];
self.tableView.dataSource = self;
[self.view addSubview:self.tableView];
}
- (NSInteger)numberOfRows{
return 30;
}
- (UIView *)cellForRow:(NSInteger)row{
NSString * ident = @"cell";
YFTableViewCell * cell = [[YFTableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:ident];
cell.textLabel.text = @"測試";
return cell;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
@end
此時運行我們的程序,如下圖:
3.png
這時,我們在ScrollView的基礎上仿寫了一個簡單的TableView。但細心的我們會發現,仿寫的這個TableView存在一個很嚴重的問題,subViews沒有復用,在有限的手機內存里,這時非常可怕的。so,我們將改寫這個ScrollView,讓它擁有更棒的性能。
實現復用
首先我們需要一個表的結構來存儲單元格以供復用,因此我們需要在YFTableView.m中添加實例變量:
//可復用的一組單元格
NSMutableSet * _reuseCells;
現在,我們需要使RefreshView更加智能化。例如,屏幕滾動的單元格需要放入復用池_reuseCells中。此外,當出現空白時,需要從池中取出可復用的單元格填充空白處。
優化refreshView如下:
- (void)refreshView{
//設置scrollView的高度
self.contentSize = CGSizeMake(self.bounds.size.width, [_dataSource numberOfRows] * SHC_ROW_HEIGHT);
//刪除不再可見的細胞
for (UIView * cell in [self cellSubViews]) {
//滑出頂部的cell
if (cell.frame.origin.y + cell.frame.size.height < self.contentOffset.y) {
[self recycleCell:cell];
}
//底部沒有出現的cell
if (cell.frame.origin.y > self.contentOffset.y + self.frame.size.height) {
[self recycleCell:cell];
}
}
//確保每一行都有一個單元格
int firstVisibleIndex = MAX(0, floor(self.contentOffset.y / SHC_ROW_HEIGHT));
int lastVisibleIndex = MIN([_dataSource numberOfRows], firstVisibleIndex + 1 + ceil(self.frame.size.height / SHC_ROW_HEIGHT));
//添加cell
for (int row = firstVisibleIndex; row < lastVisibleIndex; row ++) {
//獲得cell
UIView * cell = [self cellForRow:row];
if (!cell) {
//如果cell不存在(沒有復用的cell),則創建一個新的cell添加到scrollView中
UIView * cell = [_dataSource cellForRow:row];
float topEdgeForRow = row * SHC_ROW_HEIGHT;
cell.frame = CGRectMake(0, topEdgeForRow, self.frame.size.width, SHC_ROW_HEIGHT);
[self insertSubview:cell atIndex:0];
}
}
}
//從滾動視圖返回一個單元格數組,self子視圖時單元格
- (NSArray *)cellSubViews{
NSMutableArray * cells = [[NSMutableArray alloc] init];
for (UIView * subView in self.subviews) {
if ([subView isKindOfClass:[YFTableViewCell class]]) {
[cells addObject:subView];
}
}
return cells;
}
//通過添加一組復用單元格,并從視圖中刪除它來循環單元格
- (void)recycleCell:(UIView *)cell{
[_reuseCells addObject:cell];
[cell removeFromSuperview];
}
//返回給定的單元格,如果不存在則返回nil
- (UIView *)cellForRow:(NSInteger)row{
float topEdgeForRow = row * SHC_ROW_HEIGHT;
for (UIView * cell in [self cellSubViews]) {
if (cell.frame.origin.y == topEdgeForRow) {
return cell;
}
}
return nil;
}
上面的代碼做了一個很好的回收單元格的工資,但是如果沒有提供一個機制來允許數據源將cell從池中拉出來,它將毫無用處。因此,我們需要在YFTableView.h中添加一下方法:
//出現一個可以重用的單元格
- (UIView *)dequeueReusableCell;
//注冊一個用作新單元格的類
- (void)registerClassForCells:(Class)cellClass;
在實現上述方法之前,您需要為YFTableView.m添加一個實例變量:
//表示單元格類型的類
Class _cellClass;
現在添加方法實現:
- (void)registerClassForCells:(Class)cellClass{
_cellClass = cellClass;
}
- (UIView *)dequeueReusableCell{
//首先從復用池獲取一個單元格
UIView * cell = [_reuseCells anyObject];
if (cell) {
NSLog(@"從池中返回單元格");
[_reuseCells removeObject:cell];
}
if (!cell) {
NSLog(@"創建新單單元格");
cell = [[_cellClass alloc] init];
}
return cell;
}
回到ViewController.m中添加注冊代碼:
[self.tableView registerClassForCells:[YFTableViewCell class]];
并在ViewController.m中替換代碼:
//YFTableViewCell * cell = [[YFTableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:ident];
YFTableViewCell * cell = (YFTableViewCell *)[self.tableView dequeueReusableCell];
到此,我們就完成了一個簡易的TableView,但我們發現,這是一個固定高度的TableView,當我們需要修改行高時,需要在YFTableView.m里修改,這時非常不合理的。
為實現任意的行高,我們在YFTableViewDataSource中添加新的方法:
//返回表格的高度
- (CGFloat)rowHeight;
在YFTableView.m中實現該方法:
//單元格高度
- (CGFloat)getRowHeight{
CGFloat rowHeight = 50.0f;
@try {
if ([self.dataSource rowHeight]) { //自定義的高度
rowHeight = [self.dataSource rowHeight];
}
} @catch (NSException *exception) {//默認高度
rowHeight = 50.0f;
} @finally {
}
return rowHeight;
}
并且用該方法替換所有SHC_ROW_HEIGHT常量:
//self.contentSize = CGSizeMake(self.bounds.size.width, [_dataSource numberOfRows] * SHC_ROW_HEIGHT);
self.contentSize = CGSizeMake(self.bounds.size.width, [_dataSource numberOfRows] * [self getRowHeight]);
...
這時回到ViewController.m中,當實現rowHeight方法是,行高是該方法中設置的高度,當沒有實現該方法是,行高默認為50.0f:
//設置行高為100
- (CGFloat)rowHeight{
return 100;
}
此時運行程序如下:
4.png
我們發現表格沒有分割線,這時我們可以在YFTableViewCell.m中添加代碼實現分割線效果:
#import "YFTableViewCell.h"
@implementation YFTableViewCell{
CAGradientLayer * _gradientLayer;
}
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
_gradientLayer = [CAGradientLayer layer];
_gradientLayer.colors = @[(id)[[ UIColor colorWithWhite:1.0f alpha:0.2f] CGColor],
(id)[[ UIColor colorWithWhite:1.0f alpha:0.1f] CGColor],
(id)[[ UIColor clearColor] CGColor],
(id)[[ UIColor colorWithWhite:0.0f alpha:0.1f] CGColor]];
_gradientLayer.locations = @[@0.00f,@0.01f,@0.95f,@1.00f];
[self.layer insertSublayer:_gradientLayer above:0];
}
return self;
}
- (void)layoutSubviews{
[super layoutSubviews];
_gradientLayer.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
}
- (void)awakeFromNib {
[super awakeFromNib];
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
}
@end
到這里我們就實現了一個自定義的表格視圖。
我知道你會看到這里的,那誰還生氣嗎?
總結
以上是生活随笔為你收集整理的scrollview复用节点_利用ScrollView实现TableView效果(实现复用)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 农银理财和农行理财区别
- 下一篇: botley编程机器人测评_浅谈少儿编程