美文网首页
链式语法封装一个简单的UIAlertController

链式语法封装一个简单的UIAlertController

作者: boy丿log | 来源:发表于2019-03-29 12:53 被阅读0次

    UIAlertView在iOS9后就不建议被使用,官方要求最好使用UIAlertViewController,它集成了UIAlertView和UIActionSheet。UIAlertView和UIActionSheet是建立在View的基础上,调用show方法可以直接显示,并通过代理来实现点击方法的回调,而UIAlertController是建立在UIViewController之上的,需要present显示,并且所有的点击按钮都通过UIAction的model进行创建,当然两者还有很多区别的。今天,我们关注的重点是在项目中有大量使用的UIAlertView和UIActionSheet如何被替换为UIAlertViewController,如果按正常的逻辑:

    UIAlertController创建和显示

    而UIAlertView要先遵循代理:

    UIAlertView创建和显示

    可以看出,如果在项目中有需要将UIAlertView中替换成UIAlertController还是需要做很多工作的。今天,我们来简化一下这个工作,需要写一个UIAlertController的分类,效果是这样:


    封装后的UIAlertViewController

    可以看到,封装后的可以很方便的兼容UIAlertView的代理方法,而且只需要一行代码。

    接下来看封装的头文件

    /**
     快速创建
     */
    static inline UIAlertController *UIAlertControllerCreate( NSString *_Nullable title, NSString *_Nullable message, UIAlertControllerStyle style){
        return [UIAlertController alertControllerWithTitle:title message:message preferredStyle:style];
    }
    static inline UIAlertController *UIAlertControllerAlertCreate(NSString *_Nullable title,NSString *_Nullable message){
        return [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
    }
    static inline UIAlertController *UIAlertControllerSheetCreate(NSString *_Nullable title, NSString *_Nullable message){
        return [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleActionSheet];
    }
    
    typedef void (^ wtcAlertTapBlock)(NSInteger index, UIAlertAction *action);
    @interface UIAlertController (WTCCategory)
    
    /**
     添加Action,并设置key值,需要在点击方法中使用
     */
    @property (nonatomic, copy, readonly) UIAlertController * (^ addAction)(NSString *title, UIAlertActionStyle style, NSInteger index);
    @property (nonatomic, copy, readonly) UIAlertController * (^ addDesAction)(NSString *title, NSInteger index);
    @property (nonatomic, copy, readonly) UIAlertController * (^ addCancelAction)(NSString *title, NSInteger index);
    @property (nonatomic, copy, readonly) UIAlertController * (^ addDefaultAction)(NSString *title, NSInteger index);
    
    
    @property (nonatomic, copy, readonly) UIAlertController * (^ addTextField) (void (^ textField) (UITextField *textField));
    /**
     在点语法中用来返回一个最近添加的UIAlertAction,用来设置样式
     */
    @property (nonatomic, copy, readonly) UIAlertController * (^ actionStyle) (void (^ actionStyle)(UIAlertAction * action));
    
    
    /**
     action点击方法,返回的key值是上面添加的key值
     */
    @property (nonatomic, copy, readonly) UIAlertController * (^ actionTap) (wtcAlertTapBlock tapIndex);
    
    
    /**
     在点语法中用来返回当前的UIAlertVController,用来设置样式
     */
    @property (nonatomic, copy, readonly) UIAlertController * (^ alertStyle) (void (^ alert)(UIAlertController *alertVC));
    
    
    /**
     title样式设置
     */
    @property (nonatomic, copy, readonly) UIAlertController * (^ alertTitleAttributeFontWithColor)(UIFont *font, UIColor *color);
    @property (nonatomic, copy, readonly) UIAlertController * (^ alertTitleAttributeWidthDictionary)(void (^attribute)(NSMutableDictionary * attributes));
    
    /**
     message样式设置
     */
    @property (nonatomic, copy, readonly) UIAlertController * (^ alertMessageAttributeFontWithColor)(UIFont *font, UIColor *color);
    @property (nonatomic, copy, readonly) UIAlertController * (^ alertMessageAttributeWidthDictionary)(void (^attribute)(NSMutableDictionary * attributes));
    //detail
    //@property (nonatomic, copy, readonly) UIAlertController * (^ alertDetailAttributeFontWithColor)(UIFont *font, UIColor *color);
    //@property (nonatomic, copy, readonly) UIAlertController * (^ alertDetailAttributeWidthDictionary)(void (^attribute)(NSMutableDictionary * attributes));
    
    
    /**
     title属性
     */
    @property (nonatomic, copy, readonly) UIAlertController * (^ alertTitleMaxNum)(NSUInteger numberOfLines);
    @property (nonatomic, copy, readonly) UIAlertController * (^ alertTitleLineBreakMode)(NSLineBreakMode mode);
    /**
     设置title字体颜色
     */
    - (void)setACTitleAttributedString:(nullable NSAttributedString *)attributedString;
    
    /**
     设置message字体颜色
     */
    - (void)setACMessageAttributedString:(nullable NSAttributedString *)attributedString;
    
    /**
     设置介绍字体颜色
     */
    - (void)setACDetailAttributedString:(nullable NSAttributedString *)attributedString;
    
    /**
     设置title最大行数
     */
    - (void)setACTitleLineMaxNumber:(NSInteger)number;
    
    /**
     设置title截断模式
     */
    - (void)setACTitleLineBreakModel:(NSLineBreakMode)mode;
    
    /**
     添加action
     */
    - (UIAlertAction *)addActionTitle:(NSString *)title style:(UIAlertActionStyle)style block:(void (^) (UIAlertAction *action))block;
    
    
    @end
    
    

    刚上来的3个函数方法用来便捷构造,接下来是一些readonly的block属性,是为了用来实现链式语法(在我们平常使用block是用来回调的,链式语法使用相反的思想,本类回调本类block并返回本类,借助block实现了链式语法)。还有一些通过kvo设置UIAlertController属性的一些方法。

    在实现的时候需要解决几个问题:

    1.如何统一action点击事件,并区分。
    2.如何在action点击时直接获取当前AlertController
    3.如何确定当前的presentingViewController

    问题一:如何统一action点击事件,并区分。

    解决方法如下:

    - (UIAlertController * _Nonnull (^)(NSString * _Nonnull, UIAlertActionStyle, NSInteger))addAction{
        return ^ (NSString *title, UIAlertActionStyle style, NSInteger index){
            
            __weak typeof(self)weakSelf = self;
            [self addActionTitle:title style:style block:^(UIAlertAction * _Nonnull action) {
                if ([weakSelf wtc_actionBlock]) {
                    [weakSelf wtc_actionBlock](index, action);
                }
            }];
            return self;
        };
    }
    

    block拥有的特性是可以保存变量,所以在执行addAction这个操作时可以给action添加一个数值类型的值,在执行点击block回调点击的actionTap方法时回调这个值用来判断。

    typedef void (^ wtcAlertTapBlock)(NSInteger index, UIAlertAction *action);
    - (UIAlertController * _Nonnull (^)(wtcAlertTapBlock _Nonnull))actionTap{
        return ^ (wtcAlertTapBlock block){
            [self setWtc_actionBlock:block];
            return self;
        };
    }
    - (wtcAlertTapBlock)wtc_actionBlock{
        return objc_getAssociatedObject(self, kwtcActionBlock);
    }
    
    - (void)setWtc_actionBlock:(wtcAlertTapBlock)block{
        objc_setAssociatedObject(self, kwtcActionBlock, block,OBJC_ASSOCIATION_COPY);
    }
    

    关于点击的实现,可以定义一个block,返回action和设置时传入的标识(Index)来进行action的区分。用runtime动态绑定一个wtcAlertTapBlock类型的block,并在执行actionTap设置这个block。

    - (UIAlertController * _Nonnull (^)(void (^ _Nonnull)(UIAlertAction * _Nonnull)))actionStyle{
        return ^ (void (^style) (UIAlertAction *action)){
            if (style) {
                style([self wtc_currentAction]);
            }
            return self;
        };
    }
    - (UIAlertAction *)wtc_currentAction{
        return [self.actions lastObject];
    }
    
    

    通过UIAlertController的actions属性(能获取当前添加所有的action)来获取最新添加的action,并设置样式。

    经过一系列的操作,我们就能够方便的设置action和处理回调了。

    问题二:如何在action点击时直接获取当前AlertController

    解决方法:
    在分类中添加属性alertViewController

    @interface UIAlertAction (WTCCategory)
    
    @property (nonatomic, weak, readonly) UIAlertController * alertViewController;
    /**
     设置action颜色
     */
    - (void)setAlertActionTitleColor:(UIColor *)color;
    
    /**
     设置action图片
     
     */
    - (void)setAlertImage:(UIImage *)image;
    
    
    @end
    

    我们都知道分类不可以添加属性,只能通过runtime来实现,但是因为UIAlertController是持有action的,所以如果action强持有AlertViewController会造成循环引用,所以这里属性只能用weak,然而,

    runtime绑定属性策略

    可以看出是没有weak这个属性的,解决这个问题有两个思路,
    第一,用:OBJC_ASSOCIATION_ASSIGN,在UIAlertController的dealloc方法中将action中的alertViewController置nil,避免野指针,但是这样还需要用到runtime的方法交换,太麻烦了。
    第二:设置一个中间者weak持有UIAlertController,action强持有中间者,这里我们就用的这种方法。

    @interface UIAlertActionWithController : NSObject
    @property (nonatomic, weak) UIAlertController * alertViewController;
    @end
    @implementation UIAlertActionWithController
    
    
    @end
    
    @implementation UIAlertAction (WTCCategory)
    
    - (void)setAlertViewController:(UIAlertActionWithController *)model{
        objc_setAssociatedObject(self, kWTCCategoryActionViewController, model, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    - (UIAlertActionWithController *)alertViewActionWithController{
        return objc_getAssociatedObject(self, kWTCCategoryActionViewController);
    }
    
    - (UIAlertController *)alertViewController{
        return [self alertViewActionWithController].alertViewController;
    }
    
    - (void)setAlertActionTitleColor:(UIColor *)color{
        [self setValue:color forKey:@"_titleTextColor"];
    }
    
    - (void)setAlertImage:(UIImage *)image{
        [self setValue:[image imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal] forKey:@"image"];
    }
    
    @end
    

    在添加Action的时候

    - (UIAlertAction *)addActionTitle:(NSString *)title style:(UIAlertActionStyle)style block:(void (^)(UIAlertAction * _Nonnull))block{
        UIAlertAction *action = [UIAlertAction actionWithTitle:title style:style handler:block];
        [self addAction:action];
        UIAlertActionWithController *model = [UIAlertActionWithController new];
        model.alertViewController = self;
        [action setAlertViewController:model];
        return action;
    }
    

    这样我们就实现了,从action中获取action的AlertViewController。

    问题三:如何确定当前的presentingViewController

    /**
     立刻显示
     */
    - (void)showInRootViewController;
    //显示延时time小时
    - (void)showAndDissmissAfterTime:(NSTimeInterval)time;
    

    如果在某个model、view或者非ViewController中我们也想像UIAlertView一样方便,设置完成后只调用一个show方法就可以弹框该怎么办呢,那就只能从AppDelegate中获取当前显示的控制器了。

    - (void)showInRootViewController{
        UIViewController *vc = [UIApplication currentTopViewController];
        [self presentedFromViewController:vc];
    }
    
    - (void)showAndDissmissAfterTime:(NSTimeInterval)time{
        if (time > 0) {
            UIViewController *vc = [UIApplication currentTopViewController];
            [vc presentViewController:self animated:YES completion:^{
                dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(time * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                    [self dismissViewControllerAnimated:NO completion:nil];
                });
            }];
        }
    }
    
    

    UIApplication分类方法

    + (UIViewController *)currentTopViewController{
        UIViewController *vc = [self rootViewController];
        Class naVi = [UINavigationController class];
        Class tabbarClass = [UITabBarController class];
        BOOL isNavClass = [vc isKindOfClass:naVi];
        BOOL isTabbarClass;
        if (!isNavClass) {
            isTabbarClass = [vc isKindOfClass:tabbarClass];
        }
        while (isNavClass || isTabbarClass) {
            UIViewController * top;
            if (isNavClass) {
              top = [(UINavigationController *)vc topViewController];
            }else{
              top = [(UITabBarController *)vc selectedViewController];
            }
            if (top) {
                vc = top;
            }else{
                break;
            }
            isNavClass = [vc isKindOfClass:naVi];
            if (!isNavClass) {
                isTabbarClass = [vc isKindOfClass:tabbarClass];
            }
        }
        return vc;
    }
    + (UIWindow *)delegateWindow{
        return [UIApplication sharedApplication].delegate.window;
    }
    + (id)rootViewController{
        return [self delegateWindow].rootViewController;
    }
    

    此处需要注意的是:

    Class naVi = [UINavigationController class];
        Class tabbarClass = [UITabBarController class];
        BOOL isNavClass = [vc isKindOfClass:naVi];
        BOOL isTabbarClass;
        if (!isNavClass) {
            isTabbarClass = [vc isKindOfClass:tabbarClass];
        }
        while (isNavClass || isTabbarClass) {
            UIViewController * top;
            if (isNavClass) {
              top = [(UINavigationController *)vc topViewController];
            }else{
              top = [(UITabBarController *)vc selectedViewController];
            }
            if (top) {
                vc = top;
            }else{
                break;
            }
            isNavClass = [vc isKindOfClass:naVi];
            if (!isNavClass) {
                isTabbarClass = [vc isKindOfClass:tabbarClass];
            }
        }
    

    这个递归,vc现获取到跟控制器,然后判断是否为NavgationController或TabbarController,如果是,则继续,否则,返回这个控制器。

    总结

    经过上面的一系列封装,我们就可以很方便的将UIAlertController封装的和UIAlertView一样简单,并添加更方便的功能。

    github地址

    相关文章

      网友评论

          本文标题:链式语法封装一个简单的UIAlertController

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