做過移動端開發的人都知道,列表控件是最常用的控件之一。iOS裡的列表控件是UITableView,其實Apple的開發人員對於UITableView的設計已經夠好的了(簡單易用,擴展性非常強等等)。
但對於展示邏輯單一的移動端系統軟件,你還是能感覺到有些繁瑣(或許是程序員天生就有些懶惰的毛病吧)。
來看看它到底繁瑣在哪兒了。首先,它的使用頻率太高了;第二,它通常不是只呈現一下數據就完事了,一般都會跟隨下拉刷新、上提加載更多功能,當然通常還要跟網絡下載數據、圖片打交道;第三,MVC模式是ios開發的慣用模式,隨之而來的是一大堆協議的實現(無論你是再寫一次也好,拷貝也罷,反正做這些工作都讓人覺得索然無味)。
沖著這些,今天就把UITableView常見的使用模式封裝了一下。具體做了以下幾件事:
1、 內嵌了下拉刷新(EGORefreshTableHeaderView)、上提加載更多(LoadMoreTableFooterView)
2、 內置實現了UITableViewDataSource、UITableViewDelegate這兩個通常必須實現的協議,對於自實現的邏輯以Block的形式對客戶代碼開放
3、 內置實現了1中提到的兩個組件的回調協議,同上,自實現的邏輯以Block的形式對外開放
4、 內置實現了EGORefreshTableHeaderView、LoadMoreTableFooterView與UITableView交互必須實現的UIScrollViewDelegate協議
5、 內置實現了異步圖片下載(可選)
你可以到我的Github上,查看源碼。稱它為ELTableViewController是取了EGORefreshTableHeaderView以及LoadMoreTableFooterView的首字母。
這份代碼中包含了一個示例程序以及三個必備組件:
1、 EGORefreshTableHeaderView
2、 LoadMoreTableFooterView(修改版,原版不能適應任何尺寸的高度)
3、 Apple官方提供的異步下載UITableView中的圖片的示例組件(IconDownLoader),這個只適用於下載類似於社交網絡中的用戶頭像,不建議使用它來下載那些大圖片,因為它甚至都沒有緩存(如果圖片很大,推薦使用SDImage)
代碼解讀
它已經內置實現了這些協議,所以在你使用它的時候,無需設置和實現。
[cpp]
@interface ELTableViewController : UIViewController
<
UITableViewDelegate,
UITableViewDataSource,
EGORefreshTableHeaderDelegate,
LoadMoreTableFooterDelegate,
IconDownloaderDelegate
>
對於不斷變化的業務邏輯,這裡提供了所有需要實現的block:
[cpp]
//blocks for UITableView delegate
typedef UITableViewCell* (^cellForRowAtIndexPathDelegate) (UITableView *,NSIndexPath *);
typedef CGFloat (^heightForRowAtIndexPathDelegate) (UITableView *,NSIndexPath *);
typedef void (^didSelectRowAtIndexPathDelegate) (UITableView *,NSIndexPath *);
//blocks for refresh and load more
typedef void (^refreshDataSourceFunc) (void);
typedef void (^loadMoreDataSourceFunc) (void);
typedef void (^refreshDataSourceCompleted) (void);
typedef void (^loadMoreDataSourceCompleted) (void);
//use to load image (async)
typedef void (^loadImagesForVisiableRowsFunc) (void);
typedef void (^appImageDownloadCompleted) (NSIndexPath *);
它們以屬性的形式對外公開:
[cpp]
//property for blocks
@property (nonatomic,copy) cellForRowAtIndexPathDelegate cellForRowAtIndexPathDelegate;
@property (nonatomic,copy) heightForRowAtIndexPathDelegate heightForRowAtIndexPathDelegate;
@property (nonatomic,copy) didSelectRowAtIndexPathDelegate didSelectRowAtIndexPathDelegate;
@property (nonatomic,copy) loadMoreDataSourceFunc loadMoreDataSourceFunc;
@property (nonatomic,copy) refreshDataSourceFunc refreshDataSourceFunc;
@property (nonatomic,copy) refreshDataSourceCompleted refreshDataSourceCompleted;
@property (nonatomic,copy) loadMoreDataSourceCompleted loadMoreDataSourceCompleted;
@property (nonatomic,copy) loadImagesForVisiableRowsFunc loadImagesForVisiableRowsFunc;
@property (nonatomic,copy) appImageDownloadCompleted appImageDownloadCompleted;
對於上提加載更多、下拉刷新、圖片異步加載這幾個功能都是可選的,它們以組件的形式存在。比如,在實例化該controller的時候你就可以設置上提和下拉是否可用。而對於圖片下載,你只要不實現其相應得block,它也不會對你造成額外的負擔。
[cpp]
- (id)initWithRefreshHeaderViewEnabled:(BOOL)enableRefreshHeaderView
andLoadMoreFooterViewEnabled:(BOOL)enableLoadMoreFooterView;
- (id)initWithRefreshHeaderViewEnabled:(BOOL)enableRefreshHeaderView
andLoadMoreFooterViewEnabled:(BOOL)enableLoadMoreFooterView
andTableViewFrame:(CGRect)frame;
[cpp]
#pragma mark - UITableView Delegate -
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
if (nil==self.dataSource) {
return 0;
}
return [self.dataSource count];
}
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
if (!self.cellForRowAtIndexPathDelegate) {
@throw [NSException exceptionWithName:@"Framework Error"
reason:@"Must be setting cellForRowAtIndexPathBlock for UITableView" userInfo:nil];
}
return self.cellForRowAtIndexPathDelegate(tableView,indexPath);
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
if (!self.heightForRowAtIndexPathDelegate) {
@throw [NSException exceptionWithName:@"Framework Error"
reason:@"Must be setting heightForRowAtIndexPathDelegate for UITableView" userInfo:nil];
}
return self.heightForRowAtIndexPathDelegate(tableView,indexPath);
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
if (self.didSelectRowAtIndexPathDelegate) {
self.didSelectRowAtIndexPathDelegate(tableView,indexPath);
}
}
#pragma mark - LoadMoreTableFooterDelegate Methods -
- (void)loadMoreTableFooterDidTriggerRefresh:(LoadMoreTableFooterView *)view{
if (self.loadMoreDataSourceFunc&&self.loadMoreDataSourceCompleted) {
self.loadMoreDataSourceFunc();
double delayInSeconds = 3.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(),
self.loadMoreDataSourceCompleted);
}
}
- (BOOL)loadMoreTableFooterDataSourceIsLoading:(LoadMoreTableFooterView *)view{
return self.isLoadingMore;
}
#pragma mark - EGORefreshTableHeaderDelegate Methods -
-(void)egoRefreshTableHeaderDidTriggerRefresh:(EGORefreshTableHeaderView *)view{
if (self.refreshDataSourceFunc&&self.refreshDataSourceCompleted){
self.refreshDataSourceFunc();
double delayInSeconds = 3.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(),
self.refreshDataSourceCompleted);
}
}
-(BOOL)egoRefreshTableHeaderDataSourceIsLoading:(EGORefreshTableHeaderView *)view{
return self.isRefreshing;
}
-(NSDate *)egoRefreshTableHeaderDataSourceLastUpdated:(EGORefreshTableHeaderView *)view{
return [NSDate date];
}
#pragma mark - UIScrollViewDelegate Methods -
-(void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView{
self.currentOffsetPoint=scrollView.contentOffset;
}
-(void)scrollViewDidScroll:(UIScrollView *)scrollView{
CGPoint pt=scrollView.contentOffset;
if (self.currentOffsetPoint.y<pt.y) {
[self.loadMoreFooterView loadMoreScrollViewDidScroll:scrollView];
}else {
[self.refreshHeaderView egoRefreshScrollViewDidScroll:scrollView];
}
}
-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
CGPoint pt=scrollView.contentOffset;
if (self.currentOffsetPoint.y<pt.y) {
[self.loadMoreFooterView loadMoreScrollViewDidEndDragging:scrollView];
}else {
[self.refreshHeaderView egoRefreshScrollViewDidEndDragging:scrollView];
}
if (!decelerate&&self.loadImagesForVisiableRowsFunc) {
self.loadImagesForVisiableRowsFunc();
}
}
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
if (self.loadImagesForVisiableRowsFunc) {
self.loadImagesForVisiableRowsFunc();
}
}
#pragma mark - download image async -
-(void)appImageDidLoad:(NSIndexPath *)indexPath{
if (self.appImageDownloadCompleted) {
self.appImageDownloadCompleted(indexPath);
}
}
ELTableViewController 的使用
創建一個新的controller繼承自:ELTableViewController;
override父類的initBlocks方法:
[cpp]
#pragma mark - private methods -
- (void)loadDataSource{
self.dataSource=[NSMutableArray array];
[self.dataSource addObject:@"dataSource_1"];
[self.dataSource addObject:@"dataSource_2"];
[self.dataSource addObject:@"dataSource_3"];
[self.dataSource addObject:@"dataSource_4"];
[self.dataSource addObject:@"dataSource_5"];
[self.dataSource addObject:@"dataSource_6"];
[self.dataSource addObject:@"dataSource_7"];
[self.dataSource addObject:@"dataSource_8"];
[self.dataSource addObject:@"dataSource_9"];
[self.dataSource addObject:@"dataSource_10"];
}
- (void)initBlocks{
__block TestViewController *blockedSelf=self;
//load more
self.loadMoreDataSourceFunc=^{
[blockedSelf.dataSource addObject:@"loadMoreDataSourceBlock_1"];
[blockedSelf.dataSource addObject:@"loadMoreDataSourceBlock_2"];
[blockedSelf.dataSource addObject:@"loadMoreDataSourceBlock_3"];
[blockedSelf.dataSource addObject:@"loadMoreDataSourceBlock_4"];
[blockedSelf.dataSource addObject:@"loadMoreDataSourceBlock_5"];
blockedSelf.isLoadingMore=YES;
[self.tableView reloadData];
NSLog(@"loadMoreDataSourceBlock was invoked");
};
//load more completed
self.loadMoreDataSourceCompleted=^{
blockedSelf.isLoadingMore=NO;
[blockedSelf.loadMoreFooterView loadMoreScrollViewDataSourceDidFinishedLoading:self.tableView];
NSLog(@"after loadMore completed");
};
//refresh
self.refreshDataSourceFunc=^{
blockedSelf.dataSource=[NSMutableArray array];
[blockedSelf.dataSource addObject:@"refreshDataSourceBlock_1"];
[blockedSelf.dataSource addObject:@"refreshDataSourceBlock_2"];
[blockedSelf.dataSource addObject:@"refreshDataSourceBlock_3"];
[blockedSelf.dataSource addObject:@"refreshDataSourceBlock_4"];
[blockedSelf.dataSource addObject:@"refreshDataSourceBlock_5"];
blockedSelf.isRefreshing=YES;
[self.tableView reloadData];
NSLog(@"refreshDataSourceBlock was invoked");
};
//refresh completed
self.refreshDataSourceCompleted=^{
blockedSelf.isRefreshing=NO;
[blockedSelf.loadMoreFooterView loadMoreScrollViewDataSourceDidFinishedLoading:self.tableView];
NSLog(@"after refresh completed");
};
self.cellForRowAtIndexPathDelegate=^(UITableView *tableView, NSIndexPath *indexPath){
static NSString *cellIdentifier=@"cellIdentifier";
UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell) {
cell=[[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier]autorelease];
}
cell.textLabel.text=[blockedSelf.dataSource objectAtIndex:indexPath.row];
NSLog(@"block:cellForRowAtIndexPathBlock has been invoked.");
return cell;
};
self.heightForRowAtIndexPathDelegate=^(UITableView *tableView, NSIndexPath *indexPath){
NSLog(@"block:heightForRowAtIndexPathBlock has been invoked.");
return 60.0f;
};
self.didSelectRowAtIndexPathDelegate=^(UITableView *tableView, NSIndexPath *indexPath){
NSLog(@"block:didSelectRowAtIndexPathDelegate has been invoked.");
};
}