一个弹窗实现组件化方案

作者: Gradlyarn | 来源:发表于2018-04-14 14:18 被阅读117次

Getting Started

CSPopKit是从项目中抽离出的一套弹窗框架,旨在为app内所有的弹窗业务提供一套规范的实现。

仓库地址:https://github.com/dormitory219/CSPopKit

欢迎大家fork。

在我司项目中,经过CSPopkit这套方案,重构的弹窗业务已经独立成一个模块,结构如下图

屏幕快照 2018-04-04 下午11.44.53.png

背景:

多人维护的项目,针对产品需求需要定制各种弹窗,因为没有一套规范实现,每个人根据自己的代码习惯定制不同风格的弹窗,久而久之,项目内出现了各种各种的弹窗代码,有的是直接定制view,添加到superView上,有的是用controller定制,有的加window上,这些杂乱的代码分散在各处,无法清晰梳理出弹窗相关业务的逻辑,而且由于各种各种的弹窗增多,触发时机不同,有可能会出现界面上出现多个弹窗的case,这时候如果要做弹窗的优先级展示,因没有一个底层去维护这些弹窗逻辑,根本就无力做到弹窗的分级控制,基于这些,对项目中的弹窗进行了全部重构。

CSPopKit使用:

//CSPopKit定义的弹窗三要素组件拼装
CSCustomPopHighProprityHandler *handler = [CSCustomPopHighProprityHandler handler];
CSCustomTestViewLoader *viewloader = [CSCustomTestViewLoader loader];
CSCustomTestPopLoader *popLoader = [CSCustomTestPopLoader loader];

//CSCustomPopManager底层方法调用
[[CSCustomPopManager shareManager] showPopViewWithHandler:handler viewLoader:viewloader popControllerLoader:popLoader completeBlock:^(id data) {

} fromViewController:nil];

你可以理解CSPopKit这套方案为:
CSCustomPopManager为一个厨子(工具类),通过不同的食材元素(弹窗元素)拼接,烹饪出各种不同的食品(弹窗)。

CSPopKit设计:

设计历程

弹窗这类业务较轻,而且多重业务弹窗业务相近,而且同一个app内的弹窗风格相近,所以在设计时容易直接想到用一个manager去中心化管理弹窗,用一个统一的空间作为弹窗展示,同时视图在尽量重用的基础上根据业务的不同做各种区分判断。
这样暴露的问题是:

  1. 弹窗业务变多之后,manager成了一个业务处理的中心爆炸类,包含各类弹窗业务展示条件判断,弹窗视图上元素点击业务处理,数据埋点处理等等,
  2. 视图这层同样也因为被复用的太多,各种元素选择性的hidden,同一按钮事件根据业务区分不同的delegate回调出去;
    整个业务线已经纠缠的傻傻分不清了。

在此基础上可以再根据弹窗业务线将不同的弹窗业务划分到不同的manager当中,a类业务弹窗用aManager,b类弹窗用bManager,这样从业务职责上就区分出了不同业务,每个业务间相对独立,但这样同样面临以下问题:

  1. 单例太多,浪费内存;
  2. 弹窗manager各自独立,没有统一规则,无法做到优先级展示;

回到该模块最初的需求,是希望每一个弹窗都能灵活的个性化内容配置,能定制各种弹出,消失动画,能单独处理自己的业务逻辑,基于这些,突然想到用组件的形式去拼接一个弹窗业务。
最终在项目中经过实践证明,用组件方式,解决了所有以上的痛点,并且该模块已经形成一个模板,对接触这类业务的新人,也能轻松接入新的弹窗业务。

使用介绍:

在CSPopKit中,弹窗的完整实现由以下三要素组成:

  • CSCustomPopViewLoader

每个弹窗都有独特定制的view,该view仅仅表达弹窗的内容元素,它与弹窗在什么位置,通过什么方法展示,消失都无关,CSCustomPopViewLoader就是负责装载这个内容view;

//CSCustomPopViewLoader.m

- (CSCustomPopView *)view
{
    return [CSCustomPopView popView];
}
  • CSCustomPopControllerLoader

每个弹窗都有自己的展示,消失方式,渐变出现,消失,展示在屏幕的中间,这些都是可以个性化控制,CSCustomPopControllerLoader负责个性化展示;

//CSCustomPopControllerLoader.m

- (void)setContent:(UIView *)content
{
    CSPopController *popController = [[CSPopController alloc] initWithContent:content];
    self.popController = popController;

    //通过popTheme来定义不同的展示风格
    CSPopTheme *theme = [CSPopTheme defaultTheme];
    popController.theme = theme;
}

  • CSCustomPopHandler

每个弹窗都应该有自己的业务处理,弹窗是否需要展示,展示时机是什么,弹窗视图上的元素点击后如何响应,这些都交给CSCustomPopHandler处理。

//CSCustomPopHandler.m

- (void)dismissPopView:(CSCustomPopView *)popView
{
    [[CSPopViewManager sharedManager] removePop:self];
    [self.popController dismissPopControllerAnimated:YES];
    self.reformer = nil;
    self.popView = nil;
    self.popController = nil;
    self.completeBlock = nil;
    self.fromViewController = nil;
    //remove self from manager handlers array
    if (self.delegate && [self.delegate respondsToSelector:@selector(removeHandler:)])
    {
       [self.delegate removeHandler:self];
    }
}

定制不同的元素后,如何将这些元素装载再一起,展示在界面上形成一个弹窗呢,这就是CSCustomPopManager做的事情。
具体实现逻辑参照这一底层最核心方法:


- (void)showPopViewWithHandler:(id<CSCustomPopHandlerProtocol,CSCustomPopViewProrocol,CSPopPriorityProtocol>)popHandler
                    viewLoader:(id<CSCustomPopViewLoaderProtocol>)popViewLoader
                     popControllerLoader:(id<CSCustomPopControllerLoaderProtocol,CSPopControllerProviderProtocol>)popControllerLoader
                    popReformer:(id<CSCustomReformerProtocol>)popReformer
                 completeBlock:(void (^)(id))completeBlock
            fromViewController:(UIViewController *)viewController;
{
    NSLog(@"will show popView");
    
    id<CSCustomReformerProtocol> reformer = nil;
    CSCustomPopView *popView = nil;
    id <CSCustomPopControllerLoaderProtocol,CSPopControllerProviderProtocol> popController = nil;
    id<CSCustomPopHandlerProtocol,CSCustomPopViewProrocol,CSPopPriorityProtocol> handler = nil;
   
    //handler组件处理
    if (popHandler == nil)
    {
        handler = [CSCustomPopHandler handler];
    }
    else
    {
        handler = popHandler;
    }
    handler.delegate = self;
    handler.completeBlock = completeBlock;
    handler.fromViewController = viewController;
    [self.handlers addObject:handler];
    
    //reformer组件处理
    if (popReformer == nil)
    {
        reformer = [CSCustomPopReformer reformer];
    }
    else
    {
        reformer = popReformer;
    }
    handler.reformer = reformer;
    
    //viewloader组件处理:reformer内部处理viewloader是否为空以及方法调用
    if (reformer && [reformer respondsToSelector:@selector(fetchPopViewWithData:viewLoader:)])
    {
        popView = [reformer fetchPopViewWithData:handler.data viewLoader:popViewLoader];
    }
    else
    {
        NSLog(@"reformer not implemention fetchPopViewWithModel:viewLoader method");
    }
    popView.delegate = handler;
    handler.popView = popView;
    
    if (handler && [handler respondsToSelector:@selector(willPresentPopView)])
    {
        [handler willPresentPopView];
    }
    else
    {
        NSLog(@"handler not implemention willPresentPopView menthod");
    }
    BOOL cancel = [[CSPopViewManager sharedManager] addPop:handler];
    if (cancel)
    {
        NSLog(@"cancel pop for popView,priority:%ld",[handler priority]);
        if (handler && [handler respondsToSelector:@selector(cancelPresentPopView)])
        {
            [handler cancelPresentPopView];
        }
        else
        {
            NSLog(@"handler not implemention cancelPresentPopView menthod");
        }
        return;
    }
    else
    {
        NSLog(@"popView did show,priority:%ld",[handler priority]);
    }
    
    //popController组件处理:reformer内部处理popController组件是否为空以及方法调用
    if (reformer && [reformer respondsToSelector:@selector(fetchPopControllerWithData:popControllerLoader:view:)])
    {
        popController = [reformer fetchPopControllerWithData:handler.data popControllerLoader:popControllerLoader view:popView];
    }
    else
    {
        NSLog(@"reformer not implemention fetchPopControllerWithData:popControllerLoader:view: method");
    }
   
    
    handler.popController = popController;
  
    [handler.popController presentPopControllerAnimated:YES];
    if (handler && [handler respondsToSelector:@selector(didPresentPopView)])
    {
        [handler didPresentPopView];
    }
    else
    {
        NSLog(@"handler not implemention didPresentPopView menthod");
    }
    NSLog(@"present popView type:%@,data:%@",NSStringFromClass([popView class]),handler.data);
};

其他1:

CSPopViewManager

该弹窗方案提供一套弹窗优先级控制逻辑,每个弹窗业务都可以自定义自己的优先级priority,规定界面上只允许展示一个弹窗。当present一个弹窗a,首先判断当前是否有弹窗展示,没有则直接展示,若有弹窗b,比较弹窗a和弹窗b的优先级,如果a优先级高于b,dismiss当前展示的弹窗b,再present弹窗a,如果a优先级低,弹窗a取消这次present。这套优先级控制逻辑比较简单,如果你有更复杂的控制逻辑,可以在此基础上修改。
这套优先级控制通过该模块控制:

屏幕快照 2018-04-04 下午2.03.07.png

其他2:

CSPopController

屏幕快照 2018-04-04 下午11.39.12.png

CSPopController是一个独立的弹窗展示控件,可以单独使用,使用很简单:
内部就是通过外界传入的弹窗视图contentView进行包裹,

- (instancetype)initWithContent:(UIView *)content;

在外界直接通过present,dissmiss方法来present,dismiss弹窗。

//present
- (void)presentPopControllerAnimated:(BOOL)animated;

//dismiss
- (void)dismissPopControllerAnimated:(BOOL)animated;

唯一需要注意的是,传入的contentView需要遵循autolayout布局,而且需要添加底部约束,popController会在内部通过约束帮你做到高度自适应。

popTheme的主题定制

+ (instancetype)defaultTheme
{
    CSPopTheme *defaultTheme = [[CSPopTheme alloc] init];
    
//圆角,宽度,弹出,消失动画,动画时间,是否支持点击空白消除等等 

defaultTheme.maskTypeTheme(CSPopThemeMaskTypeDimmed).cornerRadiusTheme(16.0f).maxPopupWidthTheme(280.0f).animationPresentionDurationTheme(0.6f).animationDismissDurationTheme(0.6f).shouldDismissOnBackgroundTouchTheme(NO).popThemePresentationStyleTheme(CSPPopThemePresentationStyleSlideInFromTopAndAngleBounce).popThemeDismissStyleTheme(CSPPopThemeDismissStyleSlideInToBottomAndAngle).horizontalOffsetTheme(0.f).verticalOffsetTheme(0.f);
    return defaultTheme;
}

相关文章

  • 一个弹窗实现组件化方案

    Getting Started CSPopKit是从项目中抽离出的一套弹窗框架,旨在为app内所有的弹窗业务提供一...

  • 基于Arouter实现的组件化方案说明

    基于Arouter实现的组件化方案说明: 基于Arouter实现的组件化方案说明: 一个项目,随着业务的发展,模块...

  • Android 组件化开发

    本篇简单谈谈组件化及其搭建方案 浅谈对组件化的理解 什么是组件化?如何实现组件化? 不得不提的模块化 Androi...

  • iOS组件化方案

    iOS组件化方案 iOS组件化方案

  • [转]iOS应用架构谈 组件化方案

    前言: 本文转自前同事casa的博文,这篇文章是基于runtime实现的iOS组件化方案,其实iOS组件化方案基本...

  • iOS有关架构组件化的文章链接

    iOS应用架构谈 组件化方案 iOS 组件化方案探索 iOS移动端架构的那些事 如何优雅的实现界面跳转 之 统跳协...

  • iOS 组件化方案探索

    组件化方案相关链接: 一、Limboy的组件化方案: 原文1 原文2二、Casa的组件化方案: 原文

  • 组件化方案

    组件化方案引用 在现有工程中实施基于CTMediator的组件化方案 iOS组件化实践(一):简介 iOS组件化实...

  • 轻量组件化方案

    清量组件化方案一代目 参考得到的方案,做了部分修改项目地址 一. 组件化我们要实现什么: 1.各模块可以单独运行 ...

  • 2016笔记——组件化学习(一)

    为了实现项目的组件化,今天开始研究蘑菇街组件化。 Protocol方案: 第一步,写一个简单的项目,项目内容是 F...

网友评论

    本文标题:一个弹窗实现组件化方案

    本文链接:https://www.haomeiwen.com/subject/ijllkftx.html