這種模式是我在上家公司的項(xiàng)目里用到的一種方式,整體來說就像是拼積木一樣把各個(gè)展示組件盏道、響應(yīng)事件拆分出去猜嘱,作為一個(gè)單獨(dú)的類朗伶,然后通過工廠模式生產(chǎn)出來再組裝展示步咪。
在這里,我們把每個(gè)最小的封裝組件叫做一個(gè)Component
点晴,每個(gè)事件的響應(yīng)動(dòng)作都叫做一個(gè)Action
悯周。
約定
這種模式對(duì)于后臺(tái)的依賴性很強(qiáng),前端只需要封裝好不同的小組件和大概的框架即可屠橄,由后臺(tái)提供來規(guī)定布局的樣式和響應(yīng)的事件,所以礁哄,前后端就必須有一個(gè)完全統(tǒng)一的約定溪北。
下面是json
數(shù)據(jù)格式:
{
"component": {
# 這種類型下的數(shù)據(jù)資源之拨,當(dāng)然也能`component`嵌套
"action": {
# 響應(yīng)動(dòng)作
},
# 組件類型
"componentType": "word"
},
}
舉個(gè)例子敦锌,這是評(píng)論內(nèi)容的一個(gè)視圖:
{
"component" : {
"action" : {
"actionType" : "detail",
"clearMsgNum" : "1",
"flag" : "-3",
"go_comments" : "1",
"id" : "480151",
"title" : "釋然",
"type" : "thread"
},
"componentType" : "postsNewMessage",
"datetime" : "2016-11-02 17:44:26",
"description" : "峨眉山,云中花嶺",
"messageCount" : "1",
"messageGroupId" : "480151",
"name" : "釋然",
"picUrl" : "http://s3.mingxingyichu.cn/group6/M00/92/78/wKgBjFUDbQaADktKAAXsk4b7S1s41.jpeg?imageMogr2/quality/95",
"postSummary" : "http://ww2.sinaimg.cn/large/006AYr4pjw1f8s239mmx0j30m80de0ti.jpg",
"userFansNum" : "6"
},
"message_type" : "thread_msg"
}
我們?cè)谇岸藢⒛K分為了很多種颖变,包括文字模塊腥刹、圖片模塊衔峰、圖文混排模塊垫卤、視頻播放模塊出牧、商品模塊舔痕、推薦模塊、標(biāo)簽?zāi)K等等數(shù)十上百個(gè)小模塊慨代,然后通過不同的嵌套達(dá)到頁面展示的目的侍匙,如圖(截取的是蘑菇街的圖片组底,我們的APP貌似下架了):
創(chuàng)建基類
因?yàn)樗械目煞庋b的組件都是約定好的,所以可以做一個(gè)基類對(duì)設(shè)定的數(shù)據(jù)做統(tǒng)一的分離提取
Component
@interface CustomComponent : UIView
@property (nonatomic,strong) CustomAction *firedAction;
@property (nonatomic, weak) NSDictionary *detailStars;
@property (nonatomic, retain) NSDictionary *data;
-(id) initWithFrame:(CGRect)frame data:(NSDictionary *)data;
-(id) initWithContainer:(UIView *)container data:(NSDictionary *)data;
-(void) initUI;
-(CGFloat)getComponentHeightWithData:(NSDictionary *)data withRealWidth:(CGFloat)realWidth;
//點(diǎn)擊action
-(void)fireAction;
-(void)fireActions:(NSInteger)index;
-(void)fireActionWith:(NSDictionary *)data;
@end
這里的點(diǎn)擊事件或者根據(jù)Action自定義的事件是加在單獨(dú)的Component
里铛纬,一般都是加的一個(gè)點(diǎn)擊的手勢(shì)告唆,其他的根據(jù)需求來做修改晶密。
里面的具體實(shí)現(xiàn)就是View
的定制,這里就不舉例了懂牧。
Action
@interface CustomAction : NSObject
// 統(tǒng)計(jì)信息
@property (nonatomic, copy) NSDictionary *trackEventInfor;
-(id)initWith:(NSDictionary *)data;
-(void)initData;
-(void)fire;
@end
如視頻播放的事件:
@implementation ActionVideo
- (void)fire {
[super fire];
NSString *videoUrl = [_data objectForKey:@"videoUrl"];
if (![NSString isBlankString:videoUrl]) {
NSURL *movieURL = [NSURL URLWithString:videoUrl];
AutoRatoteMPMoviePlayerViewController *controller = [[AutoRatoteMPMoviePlayerViewController alloc] initWithContentURL:movieURL];
controller.delegateController = self.delegateNavigationController;
[controller play];
}
}
@end
創(chuàng)建工廠類
ComponentFactory
用于根據(jù)給定的數(shù)據(jù)創(chuàng)建每個(gè)小的組件
@implementation ComponentFactory
+(CustomComponent *)createComponentWithFrame:(CGRect)frame data:(NSDictionary *)data navigation:(UINavigationController *)delegteNavigarionController{
NSString * componentStr = [ComponentFactory getComponentTypeWithData:data];
Class someClass = NSClassFromString(componentStr);
CustomComponent *cell = (CustomComponent *)[[someClass alloc] initWithFrame:frame data:data];
cell.delegateNavigationController = delegteNavigarionController;
return cell;
}
+(CGFloat )getComponentHeightWithData:(NSDictionary *)data withRealWidth:(CGFloat)realWidth{
// 根據(jù)給定的數(shù)據(jù)內(nèi)容來計(jì)算當(dāng)前模塊的高度
}
+(NSString *)getComponentTypeWithData:(NSDictionary *)data{
NSString * componentStr = @"CustomComponent";
NSString *componentType = data[@"component"][@"componentType"];
if([@"word" isEqual:componentType]){
componentStr = @"ComponentWord";
}
if([@"videoCell" isEqual:componentType]){
componentStr = @"ComponentVideoCell";
}
if([@"calendar" isEqual:componentType]){
componentStr = @"ComponentCalendar";
}
if ([@"calendarWorthy" isEqualToString:componentType]) {
componentStr = @"ComponentCalendarWorthy";
}
// ...
return componentStr;
}
+(CustomComponent *)createComponentWithContainer:(UIView *)view data:(NSDictionary *)data navigation:(UINavigationController *)delegteNavigarionController{
CGRect frame = view.bounds;
CustomComponent *cell = [ComponentFactory createComponentWithFrame:frame data:data navigation:delegteNavigarionController];
return cell;
}
@end
可以看出躯保,這里主要是把if-else
的類型判斷挪到這里來了途事。
ActionFactory
@implementation ActionFactory
+(CustomAction *)createActionWithData:(NSDictionary *)data withDetaiStarData:(NSDictionary *)starDic navigation:(UINavigationController *)delegateNavigationController{
CustomAction *action;
if (![data isKindOfClass:[NSDictionary class]]) {
return nil;
}
NSString *actionType = data[@"actionType"];
if (!actionType) {
return nil;
}
NSString *type = data[@"type"];
NSString *child = data[@"child"];
if ([@"livingShow" isEqualToString:actionType]) { //直播播放
action = [[ActionLiveShow alloc] initWith:data];
}
if ([@"thread" isEqualToString:actionType]) {
if ([@"" isEqualToString:child]) {// 帖子列表
action = [[ActionThread alloc] initWith:data];
}
if ([@"topiclist" isEqualToString:child]) {// 專題列表
action = [[ActionTopicList alloc] initWith:data];
}
}
if ([@"detail" isEqual:actionType]) {
// 帖子詳情頁
if ([@"thread" isEqual:type]) {
action = [[ActionThreadDetail alloc]initWith:data];
}
// 用戶空間詳情頁
if ([@"user" isEqual:type]) {
action=[[ActionSpace alloc] initWith:data];
}
}
if ([@"list" isEqualToString:actionType]) {
if ([@"msg" isEqualToString:type]) { //消息回復(fù)我的尸变,社區(qū)通知振惰,活動(dòng)通知
action = [[ActionReplyMine alloc] initWith:data];
}
if ([@"msgEvent" isEqualToString:type]) {
action = [[ActionActivityNoticeDetail alloc]initWith:data];
}
}
return action;
}
@end
這里也是根據(jù)給定的數(shù)據(jù)來創(chuàng)建具體事件的實(shí)例。
頁面使用實(shí)例
當(dāng)然痛垛,由于數(shù)據(jù)的結(jié)構(gòu)化匙头,這完全可以做一層封裝蹂析。
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return _data.count;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSDictionary *dic = self.data[indexPath.row];
NSInteger cellHeight = [ComponentFactory getComponentHeightWithData:dic withRealWidth:self.tableView.frame.size.width];
return cellHeight;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
CustomComponent *component;
NSInteger row = [indexPath row];
NSDictionary *data = _data[row];
NSString * identifier= [NSString stringWithFormat:@"reuse%@", data[@"componentType"]];
CustomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if(cell == nil) {
cell = [[CustomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
CGFloat cellHeight = [ComponentFactory getComponentHeightWithData:data withRealWidth:tableView.frame.size.width];
CGRect frame = CGRectMake(0, 0, tableView.frame.size.width, cellHeight);
component = [ComponentFactory createComponentWithFrame:frame data:data navigation:self.navigationController];
cell.component = component;
}
component.data = data;
component.row = row;
return cell;
}
總結(jié)
這種模式很適合閱讀內(nèi)容的展示竖共,特別是文章類型的頁面俺祠,由于頁面的展示樣式有后臺(tái)控制公给,所以就提供了更多可定制的可能性。
上面也只是一種粗糙的代碼展示蜘渣,要想深究淌铐,也可以在很多細(xì)節(jié)上做優(yōu)化。
不過缺點(diǎn)也很明顯蔫缸,后臺(tái)依賴性很強(qiáng)腿准,而且表格視圖的交互性不好,對(duì)于內(nèi)容的更改拾碌、cell位置的調(diào)整都不方便吐葱。
總之來說,這也是對(duì)于某種需求而產(chǎn)生的一種書寫的方式倦沧,找對(duì)應(yīng)用場(chǎng)景唇撬,做好優(yōu)化,這也會(huì)給我們的APP提供豐富的功能和開發(fā)體驗(yàn)展融。
數(shù)據(jù)的依賴性太強(qiáng)窖认,而且有原來項(xiàng)目現(xiàn)成的代碼燕偶,就懶得寫demo了酝惧,將就著看吧晚唇。