前言
一個(gè)有經(jīng)驗(yàn)的開發(fā),碰到一些特殊的UI控件饺饭,腦海中應(yīng)該是有好幾種實(shí)現(xiàn)方案的砚哗,同時(shí)也能記起一些第三方相似的開源控件。為了應(yīng)對(duì)產(chǎn)品的需求變更砰奕,UI效果的變更蛛芥,以及做到代碼的可控性,所以在開發(fā)中军援,一般的UI效果我都喜歡自己動(dòng)手去實(shí)現(xiàn)仅淑。而不是為了趕時(shí)間,或者什么的去用一些第三方的開源庫(kù)胸哥。當(dāng)然是除了一些非常通用的東西涯竟,像HUD, 下拉刷新這樣,已經(jīng)有非常好的實(shí)現(xiàn)空厌,而且很容易做定制的庐船。
示例
下面就以一個(gè)實(shí)際需求說(shuō)一下自己封裝UI的一點(diǎn)點(diǎn)經(jīng)驗(yàn):
先來(lái)看一下需求,直接上UI效果:
產(chǎn)品需求:
- 點(diǎn)擊寶貝分類后彈出一個(gè)懸浮菜單
- 菜單的內(nèi)容可能有多個(gè)嘲更,所以可能存在要上下滑動(dòng)顯示
- 點(diǎn)擊菜單外面要隱藏菜單筐钟,不做其它的操作
放到整個(gè)項(xiàng)目中做通用控件考慮可定制項(xiàng):
- 菜單是否要高亮上次選中過(guò)的菜單項(xiàng)
- 菜單項(xiàng)樣式可能不同
- 菜單顯示的位置
- 菜單上箭頭顯示的位置
- 菜單內(nèi)邊距
- 菜單邊線顏色
- 菜單項(xiàng)的文字顏色,字體大小
- ...
通過(guò)上面的分析可以看到赋朦,要做一個(gè)極通用的控件篓冲,還是要考慮非常多的可定制點(diǎn)的。接觸這個(gè)項(xiàng)目還不久宠哄,知道項(xiàng)目中是有一個(gè)類似的控件的壹将,于是翻出來(lái)看了一下代碼∶担可定制度很低诽俯。而且現(xiàn)在只有黑色的背景,顏色承粤,菜單項(xiàng)高度的定制屬性都沒有暴区,對(duì)項(xiàng)目不完全的熟悉闯团,不能動(dòng)通用控件,以防引起其它地方的bug颜启。github上也看到過(guò)很多類似的控件。但是要完全按設(shè)計(jì)給到的UI效果來(lái)完成浪讳,還是要做很多的定制工作缰盏。所以還是決定自己寫過(guò)。
實(shí)現(xiàn)
看UI效果淹遵,其實(shí)很簡(jiǎn)單的一個(gè)懸浮框顯示到一個(gè)view上口猜,以我寫這種彈窗的經(jīng)驗(yàn),用一個(gè)透明背景的view做為整個(gè)控件的根view透揣,加到要添加到的view上济炎,里面的菜單做一個(gè)view,添加到透明view上。其它的就細(xì)節(jié)處理辐真。
像上面這個(gè)view须尚,層次是這樣的,透明view 里面放 menu view 侍咱,menu view 里面放一個(gè)tableview 顯示菜單項(xiàng)耐床。箭頭跟menu view同級(jí),因?yàn)閙enu view 用了layer圓角楔脯,邊框撩轰。箭頭也可以考慮用layer畫。更好定制昧廷。最后顯示的時(shí)候?qū)⑼该鱲iew加到self.navigationController.view上堪嫂。
JXMenu *menu = [JXMenu showInView:self.navigationController.view point:CGPointMake(self.view.width / 4.0 - 50, self.view.height - 60 + 64) isTop:NO width:100 menuItems:self.viewModel.categoryArray];
menu.action = ^(id data, NSInteger index) {
ShopGoodsCategory *model = data;
if (self.viewModel.currentCategory && [self.viewModel.currentCategory.id isEqualToString:model.id]) {
// 如果點(diǎn)擊的是相同的分類,就不再請(qǐng)求
return;
}
// 傳過(guò)來(lái)的就是點(diǎn)擊到的分類木柬,不用再用索引拿數(shù)據(jù)
self.viewModel.currentCategory = model;
[self.collectionView.mj_header beginRefreshing];
};
+ (JXMenu *)showInView:(UIView *)view point:(CGPoint)point isTop:(BOOL)isTop width:(CGFloat)width menuItems:(NSArray *)items {
JXMenu *menu = [[JXMenu alloc] initWithFrame:view.bounds];
menu.leftPoint = point;
menu.attachView = view;
menu.menuWidth = width;
menu.menuItems = items;
// 一定要賦值完成后再setupViews
[menu setupViews];
[view addSubview:menu];
// 顯示動(dòng)畫
[menu showAnimation];
return menu;
}
關(guān)于這個(gè)顯示方法皆串,還有很多要改進(jìn)的地方。先完成產(chǎn)品需求眉枕,設(shè)計(jì)上考慮一些大的改進(jìn)點(diǎn)愚战。到后面有相關(guān)需求或者時(shí)間夠的時(shí)候可以進(jìn)行改進(jìn)。
/**
顯示一個(gè)菜單到指定的view上
isTop這個(gè)屬性其實(shí)是可以算出來(lái)的齐遵,沒做這個(gè)處理寂玲。待改進(jìn)。
事件也可以通過(guò)這個(gè)顯示方法傳進(jìn)來(lái)梗摇,參數(shù)太多了拓哟,待改進(jìn)。
@param view 要添加到view
@param point 左側(cè)的一個(gè)頂點(diǎn),通過(guò)/isTop/來(lái)判斷是左上角還是左下角
@param isTop 是不是左上角
@param width 菜單的整體寬度
@param items 這個(gè)array 里面的單個(gè)數(shù)據(jù)可以是對(duì)象伶授,要實(shí)現(xiàn) /JXMenuCellDataProtocol/
*/
+ (JXMenu *)showInView:(UIView *)view point:(CGPoint)point isTop:(BOOL)isTop width:(CGFloat)width menuItems:(NSArray *)items;
說(shuō)一下傳入的菜單項(xiàng)及點(diǎn)擊事件
傳入的顯示項(xiàng)是不確定的断序,可能是一個(gè)商品的分類流纹,也可能是幾個(gè)操作項(xiàng)。但是寫好的view是確定的违诗,就是說(shuō)view要顯示的數(shù)據(jù)是確定的漱凝。在這個(gè)例子中,要顯示的內(nèi)容就是一個(gè)title诸迟,但是數(shù)據(jù)是從接口拿回來(lái)的茸炒,拿回來(lái)后model化成了一個(gè)對(duì)應(yīng)的ShopGoodsCategory 類型的model 數(shù)組。不確定數(shù)據(jù)來(lái)源阵苇,數(shù)據(jù)形式的時(shí)候壁公,應(yīng)該用接口來(lái)跟源數(shù)據(jù)進(jìn)行對(duì)接,所以這里我定義了一個(gè)用于view顯示的數(shù)據(jù)接口绅项。讓傳進(jìn)來(lái)的model去實(shí)現(xiàn)接口紊册,就可以直接傳model數(shù)組了。這樣做的好處是快耿,在點(diǎn)擊了菜單項(xiàng)后囊陡,view可以直接返回點(diǎn)擊的數(shù)據(jù), 而不是一個(gè)唯一標(biāo)識(shí)或者一個(gè)索引什么的掀亥。
接口定義
#import <Foundation/Foundation.h>
// 菜單的數(shù)據(jù)接口
@protocol JXMenuCellDataProtocol <NSObject>
@property (nonatomic, copy, readonly) NSString *jxmenu_title; // 要顯示的菜單項(xiàng)的標(biāo)題
@end
傳入的model實(shí)現(xiàn)接口
#import <Foundation/Foundation.h>
#import "JXMenuCellDataProtocol.h"
@interface ShopGoodsCategory : NSObject <JXMenuCellDataProtocol>
@property (nonatomic, copy) NSString *id; // 分類id
@property (nonatomic, copy) NSString *itemName; // 分類名稱
@end
// 下面是m文件實(shí)現(xiàn)
#import "ShopGoodsCategory.h"
@implementation ShopGoodsCategory
- (NSString *)jxmenu_title {
return self.itemName;
}
@end
點(diǎn)擊事件回傳
點(diǎn)擊菜單項(xiàng)后关斜,可以用代理或者block的方式將數(shù)據(jù)返回給調(diào)用者,我一般使用block铺浇。
定義一個(gè)傳遞事件的block痢畜,把顯示view的時(shí)候傳進(jìn)來(lái)的數(shù)據(jù)中的指定項(xiàng)傳回去, 看情況再傳view本身以及索引等鳍侣。
typedef void(^JXMenuAction) (id data, NSInteger index);
添加簡(jiǎn)單的顯示動(dòng)畫
#pragma mark - private
- (void)showAnimation {
self.alpha = 0;
[UIView animateWithDuration:0.3 animations:^{
self.alpha = 1;
} completion:^(BOOL finished) {
}];
}
關(guān)于擴(kuò)展
要定制菜單項(xiàng)的話丁稀,可以把cell for row方法通過(guò)代理開放出來(lái)。
如果要定制padding,字體倚聚,顏色等线衫,可以定義一個(gè)配置類出來(lái)。不要做成單例惑折。
JXMenu的代碼可以在github上找到:JXMenu
pod 并不能用授账,只是把代碼從項(xiàng)目中搬出來(lái)了,沒有做pod支持惨驶。需要Masonry 和UIView的positioning分類支持白热。