ZZFLEX-介面構建從未如此簡單

李伯坤不想說話發表於2018-02-22

ZZFLEX是一個iOS UI敏捷開發框架,基於UIKit實現,主要包含常用控制元件的鏈式API擴充、一個資料驅動的列表框架、一個事件處理佇列。

git地址:https://github.com/tbl00c/ZZFLEX

功能模組

目前ZZFLEX主要包含以下5個功能模組:

  • UIView+ZZFLEX:為UIKit中常用的控制元件增加了鏈式API擴充;
  • ZZFlexibleLayoutViewController:基於UICollectionView的資料驅動的列表頁框架;
  • ZZFLEXAngel:ZZFlexibleLayoutViewController核心邏輯抽離出的一個列表控制器,更加輕量,支援tableView、collectionView;
  • ZZFLEXEditExtension:為ZZFLEXAngel和ZZFlexibleLayoutViewController增加了處理編輯類頁面的能力;
  • ZZFLEXRequestQueue:一個事件處理佇列,設計的初衷為解決複雜頁面多介面請求時、UI重新整理順序的問題。

UIView+ZZFLEX

UIView+ZZFLEX主要是為UIkit中的常用控制元件增加了鏈式API的擴充,引入它後,我們可以直接為viewaddButton、addLabel、addImageView等等,然後通過鏈式API可以更加連貫快捷的進行控制元件的屬性設定、Masonry佈局和事件處理。

以給檢視新增button為例說明,通常我們的做法是這樣的:

UIButton *button = [[UIButton alloc] init];
// 設定樣式
[button setBackgroundColor:[UIColor orangeColor]];
[button setTitle:@"hello" forState:UIControlStateNormal];
[button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[button setTitle:@"world" forState:UIControlStateHighlighted];
[button setTitleColor:[UIColor redColor] forState:UIControlStateHighlighted];
// 設定事件(需要額外的事件處理方法)
[button addTarget:self action:@selector(buttonDown) forControlEvents:UIControlEventTouchDown];
[button addTarget:self action:@selector(buttonUp) forControlEvents:UIControlEventTouchUpInside];
// 設定圓角和邊線
[button.layer setMasksToBounds:YES];
[button.layer setCornerRadius:3.0f];
[button.layer setBorderWidth:1.0f];
[button.layer setBorderColor:[UIColor redColor].CGColor];
// 新增到檢視
[self.view addSubview:button];
// 設定約束
[button mas_makeConstraints:^(MASConstraintMaker *make) {
    make.size.mas_equalTo(CGSizeMake(80, 35));
    make.center.mas_equalTo(0);
}];
複製程式碼

使用UIView+ZZFLEX後,你可以這樣寫:

UIButton *button = self.view.addButton(1001)
// 設定樣式
.title(@"hello").titleColor([UIColor blackColor])
.titleHL(@"world").titleColorHL([UIColor redColor])
// 設定圓角和邊線
.cornerRadius(3.0f).border(1, [UIColor redColor])
// 設定約束
.masonry(^(MASConstraintMaker *make) {
    make.size.mas_equalTo(CGSizeMake(80, 35));
    make.center.mas_equalTo(0);
})
// 設定事件
.eventBlock(UIControlEventTouchDown, ^(UIButton *sender){
    NSLog(@"touch down");
})
.eventBlock(UIControlEventTouchUpInside, ^(UIButton *sender){
    NSLog(@"touch up");
})
.view;
複製程式碼

以上可以很直觀的看出,同樣的功能,程式碼行數有原先的33行縮減到了20行,並且寫起來更加順暢。如果我們需要持有這個button指標,只需在最後加一個.view即可。

UIView+ZZFLEX是使用的是Objective-C的泛型實現的,所以可以無視繼承關係、隨意順序設定控制元件的屬性。

如需對控制元件的屬性進行編輯,可以這樣寫:

button.zz_make.frame(CGRectMake(0, 0, 100, 40)).title(@"hi").titleColor(@"how are u");
複製程式碼

如需單獨建立一個控制元件,不新增到檢視上:

UIButton *button = UIButton.zz_create(1001).title(@"hello").titleHL(@"world");
複製程式碼

目前,UIView+ZZFLEX已新增鏈式API的控制元件有:

  • UIView
  • UIImageView
  • UILabel
  • UIControl
  • UITextField
  • UIButton
  • UISwitch
  • UIScrollView
  • UITextView
  • UITableView
  • UICollectionView

ZZFlexibleLayoutViewController

ZZFlexibleLayoutViewController繼承自UIViewController,是一個基於collectionView實現的資料驅動的列表頁框架,可大幅降低複雜列表介面實現和維護的難度。

我們知道collectionView在使用過程中,各種代理方法重複度高,並且越複雜的介面各種代理方法中的程式碼就越複雜、越難以維護,一旦設計不好還容易出現效能問題。很多設計模式和第三方庫從程式碼結構和資料快取等各個角度做出了優化,也取得了一定的效果。但ZZFLEX不同的是,它將從根源解決這一個問題。

使用ZZFlexibleLayoutViewController,我們幾乎絲毫不用實現collectionView的各種代理方法。它使得列表頁的構建就如同拼圖一般,只需要一件件的add需要的模組,即可繪製出我們想要的介面:

// 清空所有資料
self.clear();

// 新增section
self.addSection(ZZFDGoodSectionTypeHeader)
// section樣式設定
.sectionInsets(UIEdgeInsetsMake(15, 15, 15, 15))
.minimumLineSpacing(15).minimumInteritemSpacing(15)
// section背景色支援
.backgrounColor([UIColor orangeColor]);
    
// 新增cell
self.addCell(NSStringFromClass([ZZFDGoodAreaCell class]))
.toSection(ZZFDGoodSectionTypeHeader).withDataModel(listModel)
// 內部事件,也可通過delegate的模式
// .delegate(self)
.eventAction(^ id(NSInteger eventType, id data) {
    NSLog(@"cell 內部事件,型別:%ld", eventType);
    return nil;
})
// cell選中事件
.selectedAction(^ (id data) {
    NSLog(@"cell 選中事件");
});

// 重新整理介面
[self reloadView];
複製程式碼

在ZZFlexibleLayoutViewController中,我們不在使用sectionIndex/indexPath確定section/cell的位置,轉而使用更唯一的sectionTag/viewTag代替。因為前者本質上是一個很不確定的資料、它會隨著介面的變化而發生改變,很多與tableView/collectionView相關的崩潰也都與此有關。

說完了collectionView容器,再說容器裡的元素。和之前不同的是,所有新增到ZZFlexibleLayoutViewController中的cell、header、footer需要額外實現一個協議—ZZFlexibleLayoutViewProtocol:

/**
 * 所有要加入ZZFlexibleLayoutViewController、ZZFLEXAngel的view/cell都要實現此協議
 * 
 * 除獲取大小/高度兩個方法需要二選一之外,其餘都可按需選擇實現
 */

@protocol ZZFlexibleLayoutViewProtocol <NSObject>

@optional;
/**
 * 獲取cell/view大小,除非手動呼叫update方法,否則只呼叫一次,與viewHeightByDataModel二選一
 */
+ (CGSize)viewSizeByDataModel:(id)dataModel;
/**
 * 獲取cell/view高度,除非手動呼叫update方法,否則只呼叫一次,與viewSizeByDataModel二選一
 */
+ (CGFloat)viewHeightByDataModel:(id)dataModel;


/**
 *  設定cell/view的資料來源
 */
- (void)setViewDataModel:(id)dataModel;

/**
 *  設定cell/view的delegate物件
 */
- (void)setViewDelegate:(id)delegate;

/**
 *  設定cell/view的actionBlock
 */
- (void)setViewEventAction:(id (^)(NSInteger actionType, id data))eventAction;

/**
 * 當前檢視的indexPath,所在section元素數(目前僅cell呼叫)
 */
- (void)viewIndexPath:(NSIndexPath *)indexPath sectionItemCount:(NSInteger)count;

@end
複製程式碼

cell/view實現這個協議的目的是,方便框架層統一處理collectionView中的的各種代理方法,並且也有一些效能優化方面的考慮,想計算大小/高度的那個方法,實際會做大小/高度的快取,即在不手動呼叫重新整理方法時,自始至終只會在新增時呼叫一次,大家可以放心使用。

目前主要支援的功能:

新增 插入 獲取 批量新增 批量插入 批量獲取 編輯 刪除 清空子資料 更新高度
section ✔️ ✔️ ✔️ ✔️ ✔️ ✔️ ✔️
cell ✔️ ✔️ ✔️ ✔️ ✔️ ✔️ ✔️ ✔️
header/footer ✔️ ✔️ ✔️ ✔️

使用方法簡述:

NSInteger sectionTag = 1001;
NSInteger cellTag = 100101;
NSArray *data = @[@"1", @"2", @"3", @"4"];
    
// 插入section,條件可以是beforeSection、afterSection、toIndex
self.insertSection(sectionTag).beforeSection(1001).minimumInteritemSpacing(5).minimumLineSpacing(5);
    
// 批量新增cell
self.addCells(@"ACell").withDataModelArray(data).toSection(sectionTag).delegate(self.dataModel).viewTag(cellTag);
    
// 批量插入cell,條件可以是beforeCell、afterCell、toIndex
self.insertCells(@"BCell").withDataModelArray(data).toSection(sectionTag).beforeCell(cellTag);
    
// 獲取並編輯section
self.sectionForTag(sectionTag).backgrounColor([UIColor yellowColor]);
    
// 刪除指定section中的某一個cell
self.sectionForTag(sectionTag).deleteCellByTag(cellTag);
// 批量刪除指定section中的符合條件的cell
self.sectionForTag(sectionTag).deleteAllCellsByTag(cellTag);
    
// 刪除全域性符合條件的cell,條件可以是cellTag、dataModel、viewCclassName、indexPath
self.deleteCell.byViewTag(cellTag);
// 批量刪除全域性符合條件的cell,條件可以是cellTag、dataModel、viewCclassName
self.deleteCells.byDataModel(data[0]);
    
// 更新指定section中items的高度
self.sectionForTag(sectionTag).update();
// 全域性更新指定條件的cell,條件可以是cellTag、dataModel、viewCclassName、indexPath
self.updateCell.atIndexPath([NSIndexPath indexPathForRow:0 inSection:0]);
    
// 清空指定section中的items
self.sectionForTag(sectionTag).clear();
// 清空指定section中的cells,保留headerFooter
self.sectionForTag(sectionTag).clearCells();
複製程式碼

ZZFLEXAngel

ZZFlexibleLayoutViewController為列表頁的開發帶來的優異的擴充性和可維護性,但它是一個VC級別的實現,在一些業務場景下還是不夠靈活的。

ZZFLEXAngel是ZZFlexibleLayoutViewController核心思想和設計提煉而成的一個“列表控制中心”,它與頁面和列表控制元件是完全解耦的。

使用它,我們只需通過任意collectionView或tableView來初始化一個ZZFLEXAngel例項(本質是將列表頁的dataSource和delegate指向ZZFLEXAngel或其子類的例項),然後就可以通過這個例項、和ZZFlexibleLayoutViewController中一樣,使用那些好用的API了。

// 建立列表檢視
UITableView *tableView = self.view.addTableView(1)
.frame(self.view.bounds)
.backgroundColor([UIColor colorGrayBG])
.tableFooterView([UIView new])
.view;

// 根據列表檢視初始化angel,hostView支援UITableView和UICollectionView
ZZFLEXAngel *angel = [[ZZFLEXAngel alloc] initWithHostView:self.tableView];

// 新增列表元素
angel.addSection(1);
angel.addCell(@"ACell").toSection(1);

// 重新整理列表
[tableView reloadData];

複製程式碼

ZZFLEXEditExtension

此擴充使得ZZFlexibleLayoutViewController和ZZFLEXAngel具有了處理編輯頁面的能力,其主要原理為規範了編輯類頁面處理流程,並使用一個額外的模型來控制它:

初始標準資料模型 -> 經ZZFLEXEditModel封裝的資料 -> UI展現 -> 使用者編輯 -> 輸入合法性判斷 -> 標準資料模型 -> 匯出資料

詳見Demo。

ZZFLEXRequestQueue

一些複雜的頁面中會存在多個非同步資料請求(net、db等),然而同時發起的非同步請求,其結果的返回順序是不確定的,這樣會導致UI展示順序的不確定性,很多情況下這是我們不希望看到的。

ZZFLEXRequestQueue的核心思想是“將一次資料請求的過程封裝成物件”,它可以保證在此業務場景下,按佇列順序載入展示UI。

詳見Demo。

如何使用

1、直接匯入方式

將專案下載到本地後,把ZZFlexibleLayoutFramework拖入到你的專案中,即可正常使用。

2、CocoaPods方式

Pod 'ZZFLEX', :git => 'git@github.com:tbl00c/ZZFLEX.git'
複製程式碼

其他

使用中的任何問題,歡迎提issure,也可與我交流:libokun@126.com

相關文章