github倉庫地址 https://github.com/gaoyangclub/GYTableViewController
前言
TableView是在項目開發的時候經常用到的組件,幾乎百分之八十以上的頁面都需要使用,一個最基本的Table需要實現DataSource的協議才能成功展示;那麼當頁面越來越多,根據不同的業務場景就需要實現不同的協議,將會出現不少相似冗餘代碼;當需求改變後,某些複雜頁面的排版和業務需求耦合在一起,後期將變得越來越難以維護;大部分情況下,Table需要和下拉刷新上拉加載控件配合使用模擬網絡請求;爲了滿足項目的可維護性,將UI排版和業務需求剝離出來,且自帶一些常用的功能,GYTableViewController系列應運而生。
技術特點
- 無需繼承自定義類,引入頭文件UIViewController+GYTableView.h即可使用
- 支持OC和Swift(混合)
- Section和Cell層次更加清晰,根據傳入的Section數據結構內部已經全部實現Section和Cell相關delegate方法
- Cell實例可獲得外部動態數據,索引位置,上下關係,選中狀態等,隨時更換樣式
- 自帶MJRefresh框架,提供下拉刷新和上拉加載功能
- 提供Section,Cell間距設置,提供選中行高亮、選中行自動居中,提供設置Cell動態高度設置等API
- 框架中的元素全部繼承於原生的tableView,除部分代理方法外,其他原生方法扔然可以使用
安裝方法
- pod安裝: pod ‘GYTableViewController’
- 手動安裝:手動安裝需要添加兩個庫,將GYTableViewController項目文件中Framework文件下的文件導入自身項目,同時此框架基於MJRefresh,所以也需要導入MJRefresh框架文件,手動或者pod都可以,MJRefresh安裝方法請戳
- demo項目圖標基於iconfont技術棧,請戳這裏
框架用法
請使用該框架中的元素來代替原生Table控件,對應關係如下:
UIViewController+GYTableView -> UIViewController
GYTableBaseView -> UITableView
GYTableViewCell -> UITableViewCell
GYTableViewSection 原生使用UIView展示section內容,這裏使用GYTableViewSection
SectionNode 用來設置Section樣式與GYTableViewSection實例綁定
CellNode 用來設置Cell樣式與GYTableViewCell實例綁定
使用時有Table控件的界面直接引入頭文件UIViewController+GYTableView.h即可,.h示例如下
- Objective-C
#import "UIViewController+GYTableView.h"
@interface YourViewController : UIViewController
swift項目在Bridging_Header橋接文件中引入UIViewController+GYTableView.h,參照demo示例
.m或swift實現文件必須開啓gy_useTableView開關來使用Table控件GYTableView
- Objective-C
- (BOOL)gy_useTableView {
return YES;
}
- Swift
override func gy_useTableView() -> Bool {
return true
}
.m或swift文件中重寫headerRefresh添加元素,當自帶的下拉刷新控件下拉時調用;從而開始Table內容層次搭建,以及各種類型的Cell位置如何擺放等
- (void)headerRefresh:(GYTableBaseView *)tableView {
//下拉刷新後開始請求後臺提供數據,請求到數據後根據解析的內容展開cell實例和位置等操作,代碼結構如下(僞代碼)
request {
tableView {
sectionNode {
cellNode,
cellNode,
...
}
sectionNode {
cellNode,
...
}
...
}
[tableView headerEndRefresh:YES];//界面搭建完畢後停止刷新
}
}
Cell控件直接繼承GYTableViewCell,.h示例如下
- Objective-C
#import "GYTableViewCell.h"
@interface YourViewCell : GYTableViewCell
- Swift
class YourViewCell: GYTableViewCell
.m文件中重寫showSubviews方法進行佈局,利用getCellData獲取Table控件中傳入的數據
- Objective-C
- (void)showSubviews {
id yourData = [self getCellData];//先獲取外部傳入的數據
//開始界面佈局...
}
- Swift
override func showSubviews() {
let yourData = self.getData()
//開始界面佈局...
}
添加Cell
Table控制器內部實現
- Objective-C
- (void)headerRefresh:(GYTableBaseView *)tableView {
[tableView addSectionNode:[SectionNode initWithParams:^(SectionNode *sNode) {
//添加一個高度爲230,類型爲BannerViewCell,展示banner圖片序列的Cell
[sNode addCellNode:[CellNode initWithParams:230 cellClass:RefreshBannerViewCell.class cellData:Mock.bannerUrlGroup]];
}]];
[tableView headerEndRefresh:YES];//不要忘了結束刷新,否則刷新動畫會停留原地
}
- Swift
override func headerRefresh(_ tableView: GYTableBaseView!) {
tableView.add(SectionNode.initWithParams({ sNode in
//添加一個高度爲230,類型爲BannerViewCell,展示banner圖片序列的Cell
sNode?.add(CellNode.initWithParams(230, cellClass: RefreshBannerViewCell.self, cellData: Mock.bannerUrlGroup))
}))
tableView.headerEndRefresh(true)
}
批量添加Cell
Table控制器內部實現(暴力添加,Swift略過…)
- (void)headerRefresh:(GYTableBaseView *)tableView {
[tableView addSectionNode:[SectionNode initWithParams:^(SectionNode *sNode) {
//添加一個高度爲230,類型爲BannerViewCell,展示banner圖片序列的Cell
[sNode addCellNode:[CellNode initWithParams:230 cellClass:RefreshBannerViewCell.class cellData:self.bannerUrlGroup]];
}]];
//注意banner和基金產品列表屬於不同區域,應存放到各自section中添加,管理section視圖會比較方便
[tableView addSectionNode:[SectionNode initWithParams:^(SectionNode *sNode) {
//添加多個高度爲80,類型爲RefreshFundViewCell,展示基金信息的Cell
[sNode addCellNode:[CellNode initWithParams:80 cellClass:RefreshFundViewCell.class cellData:self.fundModels[0]]];
[sNode addCellNode:[CellNode initWithParams:80 cellClass:RefreshFundViewCell.class cellData:self.fundModels[1]]];
[sNode addCellNode:[CellNode initWithParams:80 cellClass:RefreshFundViewCell.class cellData:self.fundModels[2]]];
//...
}]];
tableView.headerEndRefresh(true);//不要忘了結束刷新,否則刷新動畫會停留原地
}
相同類型的Cell添加可以修改成通過原數組批量添加
- Objective-C
[tableView addSectionNode:[SectionNode initWithParams:^(SectionNode *sNode) {
//添加多個高度爲80,類型爲RefreshFundViewCell,展示基金信息的Cell
[sNode addCellNodeByList:[CellNode dividingCellNodeBySourceArray:80 cellClass:RefreshFundViewCell.class sourceArray:Mock.fundModels]];
}]];
- Swift
tableView.add(SectionNode.initWithParams({ sNode in
sNode?.addCellNode(byList: CellNode.dividingCellNode(bySourceArray: 80, cellClass: RefreshFundViewCell.self, sourceArray: Mock.fundNewModels))
}))
添加Section
如果一節內容需要添加section頁眉視圖,只要在sectionNode實例設置sectionHeaderClass即可,同理section頁腳設置sectionFooterClass
- Objective-C
- (void)headerRefresh:(GYTableBaseView *)tableView {
[tableView addSectionNode:[SectionNode initWithParams:36 sectionHeaderClass:RefreshFundViewSection.class sectionHeaderData:@"精品專區" nextBlock:^(SectionNode *sNode) {
//添加section內的cell...
}]];
[tableView headerEndRefresh:YES];
}
- Swift
override func headerRefresh(_ tableView: GYTableBaseView!) {
tableView.add(SectionNode.initWithParams(36, sectionHeaderClass: RefreshFundViewSection.self, sectionHeaderData: "精品專區", nextBlock: { sNode in
//添加section內的cell...
}))
}
分類結構如下
isUnique唯一性
默認所有相同Class的Cell實例都是相互複用,每次下拉刷新或者table設置reloadData,被複用的Cell實例都會重新觸發刷新調用showSubviews,從而根據傳遞的data展開;然而,一些特殊的Cell不需要複用或只實例化一次,比如標籤按鈕區域的Cell或者banner區域的Cell,每次下拉都是隻用這個實例,可以設置爲isUnique作爲唯一Cell實例優化提高性能
- Objective-C
- (void)headerRefresh:(GYTableBaseView *)tableView endRefreshHandler:(HeaderRefreshHandler)endRefreshHandler {
[tableView addSectionNode:[SectionNode initWithParams:^(SectionNode *sNode) {
//添加一個高度爲230,類型爲BannerViewCell,展示banner圖片序列的Cell
[sNode addCellNode:[CellNode initWithParams:230 cellClass:RefreshBannerViewCell.class cellData:self.bannerUrlGroup isUnique:YES]];
//添加一個高度爲90,類型爲RefreshHotViewCell,展示banner圖片序列的Cell
[sNode addCellNode:[CellNode initWithParams:90 cellClass:RefreshHotViewCell.class cellData:self.hotModels isUnique:YES]];
}]];
[tableView headerEndRefresh:YES];
}
- Swift
override func headerRefresh(_ tableView: GYTableBaseView!) {
tableView.add(SectionNode.initWithParams({ sNode in
//添加一個高度爲230,類型爲BannerViewCell,展示banner圖片序列的Cell
sNode?.add(CellNode.initWithParams(230, cellClass: RefreshBannerViewCell.self, cellData: Mock.bannerUrlGroup, isUnique:true))
//添加一個高度爲90,類型爲RefreshHotViewCell,展示標籤按鈕區域的Cell
sNode?.add(CellNode.initWithParams(90, cellClass: RefreshHotViewCell.self, cellData:Mock.hotModels , isUnique:true))
}))
tableView.headerEndRefresh(true)
}
上拉加載更多
Table控制器內部設置顯示上拉加載控制器
- Objective-C
- (BOOL)gy_useLoadMoreFooter {
return YES;
}
- Swift
override func gy_useLoadMoreFooter() -> Bool {
return true
}
Table控制器內部重寫footerLoadMore
- Objective-C
//lastSectionNode:上一節sectionNode數據,即當前Table頁最後一節
- (void)footerLoadMore:(GYTableBaseView *)tableView lastSectionNode:(Section *)lastSectionNode {
[lastSectionNode addCellNodeByList:[CellNode dividingCellNodeBySourceArray:80 cellClass:RefreshFundViewCell.class sourceArray:self.fundNewModels]];//將新增的CellNode實例繼續添加到上一節SectionNode實例中
[tableView footerEndLoadMore:YES];//不要忘了結束上拉加載刷新
}
- Swift
override func footerLoadMore(_ tableView: GYTableBaseView!, last lastSectionNode: SectionNode!) {
lastSectionNode.addCellNode(byList: CellNode.dividingCellNode(bySourceArray: 80, cellClass: RefreshFundViewCell.self, sourceArray: Mock.fundNewModels))//將新增的CellNode實例繼續添加到上一節SectionNode實例中
tableView.footerEndLoadMore(true)//不要忘了結束上拉加載刷新
}
根據需求添加到Table頁最後一節,或者添加到新的一節數據中,並設置添加上限,業務相關代碼swift示例略…
if ([tableView getTotalCellNodeCount] > 30) {//總共超出30條數據不添加數據
[tableView footerEndLoadMore:NO];//直接結束上拉加載刷新,並顯示"已經全部加載完畢"
return;
}
//根據業務需求的不同,可以繼續添加到上一節sectionNode,也可以添加到新的一節sectionNode中
if ([lastSectionNode getCellNodeCount] < 15) {//上一節少於15條繼續添加到上一節sectionNode
[lastSectionNode addCellNodeByList:[CellNode dividingCellNodeBySourceArray:80 cellClass:RefreshFundViewCell.class sourceArray:self.fundNewModels]];
} else {//上一節超了 添加到新的一節sectionNode
[tableView addSectionNode:[SectionNode initWithParams:36 sectionHeaderClass:RefreshFundViewSection.class sectionHeaderData:@"推薦專區" nextBlock:^(SectionNode *sNode) {
[sNode addCellNodeByList:[CellNode dividingCellNodeBySourceArray:80 cellClass:RefreshFundViewCell.class sourceArray:self.fundNewModels]];
}]];
}
[tableView footerEndLoadMore:YES];//不要忘了結束上拉加載刷新
更改UITableView的frame
Table控制器內部重寫getTableViewFrame
如存在和容器底部對齊的元素,請在此方法對齊底部位置(默認佔滿controller邊界);autoLayerout無需重寫此方法,自行設置tableView和其他元素佈局關係
- Objective-C
- (CGRect)gy_getTableViewFrame {
self.noticeBack.frame = CGRectMake(0, 0, self.view.width, 30);
self.submitButton.maxY = self.view.height;//底部按鈕對齊容器底部
//返回設置好的tableView位置frame 高度=總高度-公告區高-底部按鈕高
return CGRectMake(0, self.noticeBack.height, self.view.width, self.view.height - self.noticeBack.height - self.submitButton.height);
}
- Swift
override func gy_getTableViewFrame() -> CGRect {
//...業務代碼同上
//返回設置好的tableView位置frame 高度=總高度-公告區高-底部按鈕高
return CGRect.init(x: 0, y: self.noticeBack.height, width: self.view.width, height: self.view.height - self.noticeBack.height - self.submitButton.height);
}
自定義下拉刷新控件
Table控制器內部重寫gy_getRefreshHeader
- Objective-C
- (MJRefreshHeader *)gy_getRefreshHeader {
return [[DiyRotateRefreshHeader alloc] init];
}
- Swift
override func gy_getRefreshHeader() -> MJRefreshHeader! {
return DiyRotateRefreshHeader()
}
偵聽選中的Cell
Table控制器內部實現代理 (tableView:didSelectRowAtIndexPath:已廢棄)
- Objective-C
- (void)didSelectRow:(GYTableBaseView *)tableView indexPath:(NSIndexPath *)indexPath {
CellNode* cNode = [tableView getCellNodeByIndexPath:indexPath];//獲取到綁定的CellNode
YourClass* yourData = cNode.cellData;//獲得cell的原始數據
//根據數據添加業務邏輯...
}
- Swift
override func didSelectRow(_ tableView: GYTableBaseView!, indexPath: IndexPath!) {
let cNode:CellNode? = tableView.getCellNode(by: indexPath)
let yourData:YourClass? = cNode?.cellData as? YourClass
//根據數據添加業務邏輯...
}
設置cell點擊效果,cell實例內部重寫showSelectionStyle
- Objective-C
- (BOOL)showSelectionStyle {
return YES;
}
- Swift
override func showSelectionStyle() -> Bool {
return true
}
設置Cell或Section元素間距
Table控制器內部設置tableView屬性cellGap或sectionGap
- Objective-C
- (void)viewDidLoad {
self.tableView.sectionGap = 6;//設置每一節區域之間間距
self.tableView.cellGap = 3;//設置每個Cell之間間距(包含每一節區域)
}
- Swift
override func viewDidLoad() {
self.tableView.sectionGap = 6//設置每一節區域之間間距
self.tableView.cellGap = 3//設置每個Cell之間間距(包含每一節區域)
}
設置選中某個位置的Cell
當刷新完成後設置,Table控制器內部設置tableView屬性selectedIndexPath
- Objective-C
- (void)headerRefresh:(GYTableBaseView *)tableView {
[tableView addSectionNode:[SectionNode initWithParams:^(SectionNode *sNode) {
//..添加cell數據
}]];
tableView.selectedIndexPath = [NSIndexPath indexPathForRow:0 inSection:0];//設置選中某個indexPath
[tableView headerEndRefresh:YES];
}
- Swift
override func headerRefresh(_ tableView: GYTableBaseView!) {
tableView.add(SectionNode.initWithParams({ sNode in
//..添加cell數據
}))
tableView.selectedIndexPath = IndexPath.init(item: 0, section: 0)
tableView.headerEndRefresh(true)
}
Cell實例設置選中效果,重寫setSelected方法,選中樣式請根據需求自行添加
- Objective-C
- (void)setSelected:(BOOL)selected {
[super setSelected:selected];
[self checkCellRelate];//自定義選中樣式方法,非框架內部方法,實現如下
}
- Swift
override var isSelected:Bool {
set {
super.isSelected = newValue
self.checkCellRelate()//自定義選中樣式方法,非框架內部方法,業務實現略過...
}
get{
return super.isSelected
}
}
Cell實例位置關係isFirst,isLast,位於第一個或最後一個和中間段的Cell樣式不同
- (void)checkCellRelate {
if (self.isFirst) {
[self drawFirstStyle:nodeColor];
}else if(self.isLast){
[self drawLastStyle:nodeColor];
}else{
[self drawNormalStyle:nodeColor];
}
}
設置交互點擊某個位置Cell並高亮
- Objective-C
- (void)viewDidLoad {
self.tableView.clickCellHighlight = YES;
}
- Swift
override func viewDidLoad() {
self.tableView.clickCellHighlight = true
}
設置點擊Cell自動居中
- Objective-C
- (void)viewDidLoad {
self.tableView.clickCellMoveToCenter = YES;
}
- Swift
override func viewDidLoad() {
self.tableView.clickCellMoveToCenter = true
}
Cell自動調整高度
Table控制器內部設置CellNode傳入高度CELL_AUTO_HEIGHT
- Objective-C
- (void)headerRefresh:(GYTableBaseView *)tableView {
[tableView addSectionNode:[SectionNode initWithParams:^(SectionNode *sNode) {
[sNode addCellNodeByList:[CellNode dividingCellNodeBySourceArray:CELL_AUTO_HEIGHT cellClass:AutoHeightWeiboCell.class sourceArray:Mock.weiboModels]];
}]];
[tableView headerEndRefresh:YES];
}
- Swift
override func headerRefresh(_ tableView: GYTableBaseView!) {
tableView.add(SectionNode.initWithParams({ sNode in
sNode?.addCellNode(byList: CellNode.dividingCellNode(bySourceArray: CELL_AUTO_HEIGHT, cellClass: AutoHeightWeiboCell.self, sourceArray: Mock.weiboModels))
tableView.headerEndRefresh(true)
}
Cell實例重寫getCellHeight方法獲取動態高度,獲取高度內容會被緩存不會二次計算
- Objective-C
- (CGFloat)getCellHeight:(CGFloat)cellWidth {
WeiboModel *weiboModel = [self getCellData];//獲取Model
NSString *content = weiboModel.content;//獲取動態內容字符串
CGRect contentSize = [content boundingRectWithSize:CGSizeMake(cellWidth - LEFT_PADDING - RIGHT_PADDING, FLT_MAX)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:TVStyle.sizeTextSecondary]}
context:nil];//計算給定範圍內最佳尺寸
return TOPIC_AREA_HEIGHT + contentSize.size.height + IMAGE_AREA_HEIGHT + BOTTOM_PADDING * 2;//返回計算後的最終高度
}
- Swift
override func getHeight(_ cellWidth: CGFloat) -> CGFloat {
let weiboModel:WeiboModel? = getData() as? WeiboModel//獲取Model
let content:String = weiboModel!.content //獲取動態內容字符串
let contentSize:CGRect = content.boundingRect(with: CGSize.init(width: cellWidth - AutoHeightWeiboCell.LEFT_PADDING - AutoHeightWeiboCell.RIGHT_PADDING, height: CGFloat(Float.greatestFiniteMagnitude)), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font:UIFont.systemFont(ofSize: TVStyle.sizeTextSecondary)], context: nil)
//計算給定範圍內最佳尺寸
return AutoHeightWeiboCell.TOPIC_AREA_HEIGHT + contentSize.size.height + AutoHeightWeiboCell.IMAGE_AREA_HEIGHT + AutoHeightWeiboCell.BOTTOM_PADDING * 2;//返回計算後的最終高度
}
自定義創建TableView
無上拉加載和下拉刷新控件的乾淨TableView實例
- Objective-C
self.tableView = [GYTableBaseView table:self];//創建並設置delegate
[self.tableView addSectionNode:[SectionNode initWithParams:^(SectionNode *sNode) {
//添加元素...
}]];
[self.tableView gy_reloadData];//不要忘了刷新Table
- Swift
self.tableView = GYTableBaseView.table(self);//創建並設置delegate
self.tableView?.add(SectionNode.initWithParams({ sNode in
//添加元素...
}))
self.tableView?.gy_reloadData();//不要忘了刷新Table
ChangeLog
1.1.0 當前版本,框架結構整體修改,兼容swift混編,底層delegate優化爲動態綁定
歷史版本
1.0.0 初版
1.0.1 iOS11.0 Table自動上移bug修復
1.0.2 自定義上拉加載控件支持
1.0.3 增加autolayout支持,修改TableBaseView參數傳入方式
1.0.6 添加UIViewController+GYTableView分類
1.0.7 delegate添加prepareCell用來自定義對cell進行操作
1.0.8 部分bug修復