TableView之Component

auu.space

这种模式是我在上家公司的项目里用到的一种方式,整体来说就像是拼积木一样把各个展示组件、响应事件拆分出去,作为一个单独的类,然后通过工厂模式生产出来再组装展示。

image

在这里,我们把每个最小的封装组件叫做一个Component,每个事件的响应动作都叫做一个Action

约定

这种模式对于后台的依赖性很强,前端只需要封装好不同的小组件和大概的框架即可,由后台提供来规定布局的样式和响应的事件,所以,前后端就必须有一个完全统一的约定。
下面是json数据格式:

{
    "component": {
        # 这种类型下的数据资源,当然也能`component`嵌套
        
        "action": {
            # 响应动作
        },
        # 组件类型
        "componentType": "word"
    },
}

举个例子,这是评论内容的一个视图:

      {
        "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"
      }

我们在前端将模块分为了很多种,包括文字模块、图片模块、图文混排模块、视频播放模块、商品模块、推荐模块、标签模块等等数十上百个小模块,然后通过不同的嵌套达到页面展示的目的,如图(截取的是蘑菇街的图片,我们的APP貌似下架了):

image

创建基类

因为所有的可封装的组件都是约定好的,所以可以做一个基类对设定的数据做统一的分离提取

  • 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;

//点击action
-(void)fireAction;
-(void)fireActions:(NSInteger)index;
-(void)fireActionWith:(NSDictionary *)data;

@end

这里的点击事件或者根据Action自定义的事件是加在单独的Component里,一般都是加的一个点击的手势,其他的根据需求来做修改。
里面的具体实现就是View的定制,这里就不举例了。

  • Action
@interface CustomAction : NSObject

// 统计信息
@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

创建工厂类

  • ComponentFactory

用于根据给定的数据创建每个小的组件

@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{
    // 根据给定的数据内容来计算当前模块的高度
}

+(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]) {    //消息回复我的,社区通知,活动通知
            action = [[ActionReplyMine alloc] initWith:data];
        }
        if ([@"msgEvent" isEqualToString:type]) {
            action = [[ActionActivityNoticeDetail alloc]initWith:data];
        }
    }

    return action;
}

@end

这里也是根据给定的数据来创建具体事件的实例。

页面使用实例

当然,由于数据的结构化,这完全可以做一层封装。

- (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;
}

总结

这种模式很适合阅读内容的展示,特别是文章类型的页面,由于页面的展示样式有后台控制,所以就提供了更多可定制的可能性。
上面也只是一种粗糙的代码展示,要想深究,也可以在很多细节上做优化。
不过缺点也很明显,后台依赖性很强,而且表格视图的交互性不好,对于内容的更改、cell位置的调整都不方便。
总之来说,这也是对于某种需求而产生的一种书写的方式,找对应用场景,做好优化,这也会给我们的APP提供丰富的功能和开发体验。

数据的依赖性太强,而且有原来项目现成的代码,就懒得写demo了,将就着看吧。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343