UITableView編程之現階段的問題ios
作ios開發的同窗都知道使用UITableView開發的時候必須實現其dataSource、delegate兩個協議,而且須要實現其相應的方法,感受還算簡單。可是隨着開發的深刻愈來愈發現幾乎每一個使用UITableView的頁面都須要實現以上2哥協議實現其方法,並且發現每次實現的邏輯大同小異;感受本身就是代碼的搬運工。並且UITableViewCell和UITableView幾乎是強綁定關係耦合度過高。因爲須要在代理方法中經過indexPath來返回相應的內容所以對之後的業務擴展也很差,哪天產品說這個cell和上面的cell換個位置,這是好可能就須要修改好幾處的delegate方法。git
如下爲傳統使用UITableView的代碼,真實狀況會更加凌亂。github
#import "ViewController.h" @interface ViewController ()<UITableViewDelegate,UITableViewDataSource> @property (weak, nonatomic) IBOutlet UITableView *tableView; @property (nonatomic,copy) NSArray *data; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; } - (nonnull UITableViewCell *)tableView:(nonnull UITableView *)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath { // 根據不一樣的view獲取cell UITableViewCell * cell; if (indexPath.section == 0) { if (indexPath.row == 0) { cell = [tableView dequeueReusableCellWithIdentifier:@""]; cell.textLabel.text = @""; cell.imageView.image = [UIImage imageNamed:@""]; } else if(indexPath.row == 1) { cell = [tableView dequeueReusableCellWithIdentifier:@""]; cell.textLabel.text = @""; cell.imageView.image = [UIImage imageNamed:@""]; } else if (indexPath.row == 0) { cell = [tableView dequeueReusableCellWithIdentifier:@""]; cell.textLabel.text = @""; cell.imageView.image = [UIImage imageNamed:@""]; } else if(indexPath.row == 1) { cell = [tableView dequeueReusableCellWithIdentifier:@""]; cell.textLabel.text = @""; cell.imageView.image = [UIImage imageNamed:@""]; } } return cell; } - (NSInteger)tableView:(nonnull UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.data.count; } -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.section == 0) { if (indexPath.row == 0) { return 70; } else if(indexPath.row == 1) { return 80; } else if (indexPath.row == 0) { return 90; } else if(indexPath.row == 1) { return 100; } } return 0; } @end
那麼咱們再想既然以上代碼每一個UITableView的頁面都有而且邏輯差很少,無非就是根據IndexPath或者其餘條件做爲判斷來返回結果。那麼根據軟件的設計思路,可不能夠把和業務邏輯無關的內容封裝起來呢?答案固然是確定的。那麼咱們就想了,UITableView無非就是須要給Cell提供數據、高度、位置、處理cell點擊等內容,並且這些和業務邏輯確實一點關係都沒有。那麼咱們看看兄弟平臺的Android是如何處理相似的問題的呢?若是瞭解Android的同窗可能會很熟悉,Android的ListView,GridView等等相似控件都是使用Adapter提供數據給ListView、GridView使用以解決以上ios的問題。那麼咱們也能夠參照Android的設計思路來設計ios開發。所以就開發了一款相似Android平臺下的處理方式的框架,而且山寨其命名叫「CHGAdapter」。廢話很少說,咱們先看一下的使用。編程
使用CHGAdapter構建UITableViewswift
ViewController中的寫法 只須要給UITableView提供數據模型網絡
#import <UIKit/UIKit.h> @interface Test1ViewController : UIViewController @end
#import "Test1ViewController.h" #import "Test1Model.h" @interface Test1ViewController () @property (weak, nonatomic) IBOutlet UITableView *tableView; @property (nonatomic,strong) NSArray *data; @end @implementation Test1ViewController - (void)viewDidLoad { [super viewDidLoad]; self.tableView.cellDatas = @[self.data]; } ///構造cell的數據,此處模擬 這些數據能夠從網絡獲取 -(NSArray*)data { if (!_data) { _data = @[ [Test1Model initWithCellClassName:@"Test1TableViewCell" cellHeight:50 name:@"測試1"], [Test1Model initWithCellClassName:@"Test1TableViewCell" cellHeight:50 name:@"測試2"], [Test1Model initWithCellClassName:@"Test1TableViewCell" cellHeight:50 name:@"測試3"], [Test1Model initWithCellClassName:@"Test1TableViewCell" cellHeight:50 name:@"測試4"] ]; } return _data; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; } @end
Model寫法 Test1Model類 Model類必須實現CHGTableViewCellModelProtocol,主要返回Model對應的Cell的一些參數,好比當前Model應該使用哪個Cell類,當前Cell的高度應該是多少。另外還能夠根據需求在Model中計算Cell的高度等操做。框架
#import <Foundation/Foundation.h> #import "CHGAdapter.h" @interface Test1Model : NSObject<CHGTableViewCellModelProtocol> @property (nonatomic,strong) NSString *cellClassName; @property (nonatomic,assign) CGFloat cellHeight; //如下爲業務參數 @property (nonatomic,strong) NSString *name; @property (nonatomic,strong) NSString *otherProperty2; +(instancetype)initWithCellClassName:(NSString*)cellClassName cellHeight:(CGFloat)cellHeight name:(NSString*)name; @end
#import "Test1Model.h" @implementation Test1Model +(instancetype)initWithCellClassName:(NSString*)cellClassName cellHeight:(CGFloat)cellHeight name:(NSString *)name{ Test1Model * t1 = [Test1Model new]; t1.cellHeight = cellHeight; t1.cellClassName = cellClassName; t1.name = name; return t1; } - (NSString *)cellClassNameInTableView:(UITableView *)tableView indexPath:(NSIndexPath *)indexPath { //若是一個model只對應一個cell 這裏能夠寫死,就不須要外部傳入。也能夠根據model的某一個字段來判斷使用哪個cell return self.cellClassName; } - (CGFloat)cellHeighInTableView:(UITableView *)tableView indexPath:(NSIndexPath *)indexPath { return self.cellHeight; } @end
UITableViewCell的寫法 Test1TableViewCell類 必須繼承CHGTableViewCell類ide
#import "CHGTableViewCell.h" @interface Test1TableViewCell : CHGTableViewCell @property (weak, nonatomic) IBOutlet UILabel *name; @end
#import "Test1TableViewCell.h" #import "Test1Model.h" @implementation Test1TableViewCell - (void)awakeFromNib { [super awakeFromNib]; // Initialization code } - (void)setSelected:(BOOL)selected animated:(BOOL)animated { [super setSelected:selected animated:animated]; // Configure the view for the selected state } - (void)cellForRowAtIndexPath:(NSIndexPath *)indexPath tableView:(UITableView *)tableView withData:(id)data { [super cellForRowAtIndexPath:indexPath tableView:tableView withData:data]; Test1Model * t1 = data; self.name.text = t1.name; } @end
運行效果圖以下測試
咱們發現TableView的上下有一塊空白區域,那個空白區域是tableView的HeaderView和FooterView,框架中默認設置HeaderView和FooterView的高度爲30,所以咱們能夠設置其默認高度,代碼以下:ui
#import "Test1ViewController.h" #import "Test1Model.h" @interface Test1ViewController () @property (weak, nonatomic) IBOutlet UITableView *tableView; @property (nonatomic,strong) NSArray *data; @end @implementation Test1ViewController - (void)viewDidLoad { [super viewDidLoad]; self.tableView.cellDatas = @[self.data]; //設置默認高度 self.tableView.tableViewAdapter.headerHeight = 0.01; self.tableView.tableViewAdapter.cellHeight = 50; self.tableView.tableViewAdapter.footerHeight = 0.01; } ///構造cell的數據,此處模擬 這些數據能夠從網絡獲取 -(NSArray*)data { if (!_data) { _data = @[ [Test1Model initWithCellClassName:@"Test1TableViewCell" cellHeight:50 name:@"測試1"], [Test1Model initWithCellClassName:@"Test1TableViewCell" cellHeight:50 name:@"測試2"], [Test1Model initWithCellClassName:@"Test1TableViewCell" cellHeight:50 name:@"測試3"], [Test1Model initWithCellClassName:@"Test1TableViewCell" cellHeight:50 name:@"測試4"] ]; } return _data; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; } @end
以上代碼設置tableView的header、cell、footer的高度:
self.tableView.tableViewAdapter.headerHeight = 0.01;//設置headerView的默認高度若是在model的協議中有設置則優先使用model中返回的
self.tableView.tableViewAdapter.cellHeight = 50;//設置cell的默認高度,若是在model的協議中有設置則優先使用model中返回的
self.tableView.tableViewAdapter.footerHeight = 0.01;//設置footerView的默認高度若是在model的協議中有設置則優先使用model中返回的
設置後運行效果以下:
咱們看到只有cell,那麼如何添加headerView和footerView呢?請往下看
首先建立header的Model 而且實現CHGTableViewHeaderFooterModelProtocol協議而且實現其required方法
#import <Foundation/Foundation.h> #import "CHGAdapter.h" @interface Test1HeaderFooterViewModel : NSObject<CHGTableViewHeaderFooterModelProtocol> @property (nonatomic,copy) NSString *name; +(instancetype)initWithName:(NSString*)name; @end
#import "Test1HeaderFooterViewModel.h" @implementation Test1HeaderFooterViewModel +(instancetype)initWithName:(NSString*)name { Test1HeaderFooterViewModel * t1hf = [Test1HeaderFooterViewModel new]; t1hf.name = name; return t1hf; } - (NSString *)headerFooterClassInTableViw:(UITableView *)tableView section:(NSInteger)section type:(CHGTableViewHeaderFooterViewType)type { return @"Test1HeaderFooterView"; } - (CGFloat)headerFooterHeighInTableViw:(UITableView *)tableView section:(NSInteger)section type:(CHGTableViewHeaderFooterViewType)type { return 30; } @end
而後添加TableView的headerView Test1HeaderFooterView類 必須繼承CHGTableViewHeaderFooterView類
#import "CHGTableViewHeaderFooterView.h" #import "CHGAdapter.h" @interface Test1HeaderFooterView : CHGTableViewHeaderFooterView @property (nonatomic,weak) IBOutlet UILabel *name; @end
#import "Test1HeaderFooterView.h" #import "Test1HeaderFooterViewModel.h" @implementation Test1HeaderFooterView /* // Only override drawRect: if you perform custom drawing. // An empty implementation adversely affects performance during animation. - (void)drawRect:(CGRect)rect { // Drawing code } */ - (void)headerFooterForSection:(NSInteger)section inTableView:(UITableView *)tableView withData:(id)data type:(CHGTableViewHeaderFooterViewType)type { [super headerFooterForSection:section inTableView:tableView withData:data type:type]; Test1HeaderFooterViewModel * model = data; self.name.text = model.name; } @end
這裏偷懶使用xib。可是須要注意使用xib來關聯headerFooterView必需要作以下設置,不然會失敗
而後是ViewController中的設置
#import "Test1ViewController.h" #import "Test1Model.h" #import "Test1HeaderFooterViewModel.h" @interface Test1ViewController () @property (weak, nonatomic) IBOutlet UITableView *tableView; @property (nonatomic,strong) NSArray *data; @property (nonatomic,strong) NSArray *headerData; @property (nonatomic,strong) NSArray *footerData; @end @implementation Test1ViewController - (void)viewDidLoad { [super viewDidLoad]; self.tableView.cellDatas = @[self.data]; self.tableView.headerDatas = self.headerData; self.tableView.footerDatas = self.footerData;//header和footer的全部元素均可以通用包括model和UI //設置默認高度 self.tableView.tableViewAdapter.headerHeight = 0.01; self.tableView.tableViewAdapter.cellHeight = 50; self.tableView.tableViewAdapter.footerHeight = 0.01; } -(NSArray*)headerData { if (!_headerData) { _headerData = @[ [Test1HeaderFooterViewModel initWithName:@"header 標題"] ]; } return _headerData; } -(NSArray*)footerData { if (!_footerData) { _footerData = @[ [Test1HeaderFooterViewModel initWithName:@"footer 標題"] ]; } return _footerData; } ///構造cell的數據,此處模擬 這些數據能夠從網絡獲取 -(NSArray*)data { if (!_data) { _data = @[ [Test1Model initWithCellClassName:@"Test1TableViewCell" cellHeight:50 name:@"測試1"], [Test1Model initWithCellClassName:@"Test1TableViewCell" cellHeight:50 name:@"測試2"], [Test1Model initWithCellClassName:@"Test1TableViewCell" cellHeight:50 name:@"測試3"], [Test1Model initWithCellClassName:@"Test1TableViewCell" cellHeight:50 name:@"測試4"] ]; } return _data; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; } @end
運行效果圖
上面demo只有一種cell,接下來咱們再建立一種類型的Cell。首先建立一個新model明明Test2Model及cell
Test2Model類
#import <Foundation/Foundation.h> #import "CHGAdapter.h" @interface Test2Model : NSObject<CHGTableViewCellModelProtocol> @property (nonatomic,copy) NSString *placeholder;//輸入框佔位符 @property (nonatomic,copy) NSString *inputText;//輸入的內容 +(instancetype)initWithPlaceholder:(NSString*)placeholder inputText:(NSString*)inputText; @end
#import "Test2Model.h" @implementation Test2Model +(instancetype)initWithPlaceholder:(NSString*)placeholder inputText:(NSString*)inputText { Test2Model * t2 = [Test2Model new]; t2.placeholder = placeholder; t2.inputText = inputText; return t2; } - (NSString *)cellClassNameInTableView:(UITableView *)tableView indexPath:(NSIndexPath *)indexPath { //若是一個model只對應一個cell 這裏能夠寫死,就不須要外部傳入。也能夠根據model的某一個字段來判斷使用哪個cell return @"Test2TableViewCell"; } - (CGFloat)cellHeighInTableView:(UITableView *)tableView indexPath:(NSIndexPath *)indexPath { return 80; } @end
Test2TableViewCell類
#import "CHGTableViewCell.h" @interface Test2TableViewCell : CHGTableViewCell @property (nonatomic,weak) IBOutlet UITextField *textField; @end
#import "Test2TableViewCell.h" #import "Test2Model.h" @implementation Test2TableViewCell - (void)awakeFromNib { [super awakeFromNib]; // Initialization code } - (void)setSelected:(BOOL)selected animated:(BOOL)animated { [super setSelected:selected animated:animated]; // Configure the view for the selected state } - (void)cellForRowAtIndexPath:(NSIndexPath *)indexPath tableView:(UITableView *)tableView withData:(id)data { [super cellForRowAtIndexPath:indexPath tableView:tableView withData:data]; Test2Model * model = data; self.textField.placeholder = model.placeholder; self.textField.text = model.inputText; } @end
運行效果
以上是運行效果,是否是在想如何把輸入框的內容實時的在UIViewController中顯示?因爲篇幅問題,講在下一篇文章中詳細介紹。
GitHub地址:
碼雲地址: