/JKAlertManager

【iOS】深度封装UIAlertController,良好兼容UIAlertControllerStyleAlert和UIAlertControllerStyleActionSheet

Primary LanguageObjective-CMIT LicenseMIT

version platform ios

JKAlertManager

JKAlertManager是基于UIAlertController封装的管理类,将UIAlertController分散的ActionBlcok集中到一个Blcok中,并且实现主动释放的Block,内存管理更安全,代码精简高效。

CocoaPods

platform :ios, '8.0'

source 'https://github.com/CocoaPods/Specs.git'

pod 'JKAlertManager', '~> 1.2.1'

目录

JKAlertManager简介

  • 基于UIAlertController封装,兼容UIAlertControllerStyleAlert和UIAlertControllerStyleActionSheet
  • UIAlertControllerStyleActionSheet类型兼容了iPad中的PopoverController
  • 主动释放Block,内存管理更安全,有效规避Block循环引用
  • 支持监听多个UITextField的文字内容变化,并通过Block回调监听结果

JKAlertManager迭代记录

  • 1.0.4,增加JKAlertView类用于全局替换UIAlertView。
  • 1.1.0,JKAlertManager继承NSObject, 不再继承UIView。

JKAlertManager用法

UIAlertControllerStyleAlert样式

	 JKAlertManager * manager = [[JKAlertManager alloc] initWithPreferredStyle:UIAlertControllerStyleAlert title:<#title#> message:<#nil#>];
 	 [manager configueCancelTitle:<#@"取消"#> destructiveIndex:JKAlertDestructiveIndexNone otherTitles:<#array#>];
	 [manager showAlertFromController:self actionBlock:^(JKAlertManager *tempAlertManager, NSInteger actionIndex, NSString *actionTitle) {
	 	if (actionIndex != tempAlertManager.cancelIndex) {
 			<#doSomething#>
 		}
	 }];

UIAlertControllerStyleAlert类型带TextField

	JKAlertManager * manager = [[JKAlertManager alloc]initWithPreferredStyle:UIAlertControllerStyleAlert title:self.dataArray[indexPath.row] message:self.dataArray[indexPath.row] ];
	[manager configueCancelTitle:@"取消" destructiveIndex:1 otherTitles:@"其他按钮1",@"Destructive按钮",@"其他按钮2", nil];
	[manager addTextFieldWithPlaceholder:@"请输入账号" secureTextEntry:NO ConfigurationHandler:nil textFieldTextChanged:^(UITextField *textField) {
         NSLog(@"1   %@",textField.text);
	}];
	[manager addTextFieldWithPlaceholder:@"请输入密码" secureTextEntry:YES ConfigurationHandler:^(UITextField *textField) {
        textField.clearsOnBeginEditing = YES;
	} textFieldTextChanged:^(UITextField *textField) {
        NSLog(@"2   %@",textField.text);
	}];
    [manager showAlertFromController:self actionBlock:^(JKAlertManager *tempAlertManager, NSInteger actionIndex, NSString *actionTitle) {
         UITextField * userNameTextField = tempAlertManager.textFields[0];
         UITextField * passwordTextField = tempAlertManager.textFields[1];
    }];

UIAlertControllerStyleActionSheet样式

如果App支持iPad,需调用configuePopoverControllerForActionSheetStyleWithSourceView:适配iPad。

	JKAlertManager * manager = [[JKAlertManager alloc]initWithPreferredStyle:UIAlertControllerStyleActionSheet title:@"title" message:@"massage" ];
	[manager configueCancelTitle:nil destructiveIndex:0 otherTitles:@"Destructive按钮",@"其他按钮1",@"其他按钮2", nil];
	[manager configuePopoverControllerForActionSheetStyleWithSourceView:cell sourceRect:cell.bounds popoverArrowDirection:UIPopoverArrowDirectionAny];
	[manager showAlertFromController:self actionBlock:^(JKAlertManager *tempAlertManager, NSInteger actionIndex, NSString *actionTitle) {
       self...
	}];

JKAlertManager部分代码用意介绍

  • 之前封装的难点主要在于拆分(NSString *)otherTitle, ...NS_REQUIRES_NIL_TERMINATION,将otherTitle转换成数组,用法见Demo或者NS_REQUIRES_NIL_TERMINATION。但后来发现并没啥优越性,可以直接传NSArray。 解除Block循环引用的思路参考了 AFNetworking,即调用了Block后,再将Block = nil置空,即可解除循环引用,这种思路只适合调用一次的Block,多次调用的Block还是需要进行self强弱转换。

  • 1.1.0版中,JKAlertManager 改成继承 NSObject,被内部__JKAlertManagerPrivateHolder类私有View强引用,JKAlertManager则会弱引用这个私有View。详情请看下面的代码:

@interface __JKAlertManagerPrivateHolder : UIView
@property (nonatomic, strong) JKAlertManager * alertManager;
@end
@implementation __JKAlertManagerPrivateHolder
@end


@interface JKAlertManager ()
@property (nonatomic, weak) __JKAlertManagerPrivateHolder * privateHolder;
@end

私有View privateHolder showAlertFromController:(UIViewController *)ViewController方法中添加到控制器ViewController.view上,间接实现控制器对JKAlertManager的强引用。privateHolder调用removeFromSuperView来解除强引用,破坏原来的引用链,从而实现自释放。

- (void)showAlertFromController:(UIViewController *)controller actionBlock:(JKAlertActionBlock)actionBlock{

    if (self.privateHolder) {
        [self.privateHolder removeFromSuperview];
        self.privateHolder = nil;
    }
    
    
    /// 私有类,强引用JKAlertManager
    __JKAlertManagerPrivateHolder * privateHolder = [[__JKAlertManagerPrivateHolder alloc] initWithFrame:CGRectZero];
    privateHolder.backgroundColor = [UIColor clearColor];
    privateHolder.alertManager = self;
    self.privateHolder = privateHolder;
    [controller.view addSubview:privateHolder];
    
    
    [controller presentViewController:self.alertController animated:YES completion:nil];
    
    // 解除Block循环引用,释放内存
    __weak typeof(self) weakSelf = self;
    self.privateBlock = ^(NSInteger actionIndex, NSString * actionTitle){
        __strong typeof(weakSelf) strongSelf = weakSelf;
        if (actionBlock) actionBlock(strongSelf, actionIndex, actionTitle);
        
        [[NSNotificationCenter defaultCenter]removeObserver:strongSelf];
        strongSelf.alertController = nil;
        strongSelf.otherTitles = nil;
        
        /// 主动释放Block,解除循环引用
        [strongSelf.privateHolder removeFromSuperview];
        strongSelf.textFieldChangedBlockMutDict = nil;
        strongSelf.privateBlock = nil;
    };
}

快速替换掉工程中所有的UIAlertView,换用UIAlertController

如果项目中用到的UIAlertView相当多,同时又想全部换成UIAlertController,可用JKAlertView全局替换UIAlertView,大部分的UIAlertView都能替换,同时不用改原来的逻辑代码和代理方法。现在UIAlertView使用过程中并无异常,所以替不替换都无所谓。如果不需要全局替换UIAlertView,JKAlertView可删除掉,详情请查看工程中ReplaceViewController.m文件中的代码。

  1. #import "JKAlertView.h"

  2. 同时按Command + Option + Shift + F组合键`,用JKAlertView全局替换UIAlertView

基于Runtime+Block封装的UIAlertView和UIActionSheet分类

  • 一个UIAlertView或UIActionSheet对象对应一个Block,代码紧凑。
  • 实现Block主动释放,规避Block循环引用,不会出现内存泄露。

如果控制器使用到多个UIAlertView或者UIActionSheet,那么需要在多个地方创建对象并设置delegate和tag,然后集中在一个代理方法中先根据tag区分对象,再区分buttonIndex,这样写的代码比较分散,可读性不强。封装后的UIAlertView或者UIActionSheet使用代码可读性更强,代码更紧凑。

不建议使用strong修饰alertView或者actionSheet属性,如果非得用属性,建议使用weak。

@property (nonatomic, strong)UIAlertView * alertView;
@property (nonatomic, strong)UIActionSheet * actionSheet;
    self.alertView = [[UIAlertView alloc]initWithTitle:@"title" message:@"message" delegate:nil cancelButtonTitle:@"取消" otherButtonTitles:@"其他", nil];
    [self.alertView showAlertViewWithActionBlock:^(UIAlertView *newAlertView, NSInteger buttonIndex) {
        self...
    }];
    
    
    self.actionSheet = [[UIActionSheet alloc]initWithTitle:@"title" delegate:nil cancelButtonTitle:@"取消" destructiveButtonTitle:@"destructiveButtonTitle" otherButtonTitles:@"otherButtonTitles", nil];
    [self.actionSheet showActionSheetInView:self.view completion:^(UIActionSheet *originalActionSheet, NSInteger buttonIndex) {
        self...
    }];