实际开发中,软件和用户的对话非常普遍。对话框如何定义,每个开发人员的思路不尽相同,不过它本身的确就很简单,一个View加上几个按钮就行了,想必有不少程序员是自定义的来做,很方便控制。固然是方便展示大体的风格了,但是往往在细节上处理不好,比如轻微的动画效果和阴影效果,边框的圆角的设置,这些细节没有处理好,很容易影响用户的友好体验。当然也有人直接使用第三方框架了。我在这里介绍的是调用苹果系统的那些弹框,本人很喜欢这种效果。根据实际场景需求,我封装了十一种对话框。如下:
/**
* 系统弹框类型
*/
typedef NS_ENUM(NSInteger, TYZSystemDialogType) {
/**显示确认和取消*/
SystemDialogType_Default = 0,
/**显示确认和取消,着重确认*/
SystemDialogType_Destructive_Left = 1,
/**显示确认和取消,着重取消*/
SystemDialogType_Destructive_Right = 2,
/**只显示确认*/
SystemDialogType_OneSure = 3,
/**只显示确认,着重确认*/
SystemDialogType_Destructive_Center = 4,
/**显示多行(最多支持6个选项)*/
SystemDialogType_More = 5,
/**自定义选择项(同默认效果)*/
SystemDialogType_Custem_Default = 6,
/**自定义选择项(同1,左边着重)*/
SystemDialogType_Custem_Destructive_Left = 7,
/**自定义选择项(同2,右边着重)*/
SystemDialogType_Custem_Destructive_Right = 8,
/**自定义选择项(同3,只显示一个)*/
SystemDialogType_Custem_OneSure = 9,
/**自定义选择项(同4,一个也着重)*/
SystemDialogType_Custem_Destructive_Center = 10
};
首先新建类,需是继承自原生的对话框控制器。
@interface TYZSystemDialog : UIAlertController
iOS系统的对话框,本质上是moda出来对话框控制器,所以它必须要的参数就是当前的控制器!如果是在控制器内弹出对话框,一切都好说,但是经过MVC处理后以及深层次的网络请求封装,对话框的弹出很可能不在控制器里,甚至都不能确定到底是哪个控制器界面需要调用。所以一开始需要解决获取当前控制器的事。
#pragma mark - 获取当前窗口的控制器
+ (UIViewController *)getCurrentViewController {
UIViewController *ctrl = nil;
UIApplication *app = [UIApplication sharedApplication];
if (!ctrl) {
ctrl = app.keyWindow.rootViewController;
}
if (!ctrl) {
ctrl = [app.windows.firstObject rootViewController];
}
if (!ctrl) {
ctrl = [self viewController];
}
if (!ctrl) {
return nil;
}
while (!ctrl.view.window && ctrl.presentedViewController) {
ctrl = ctrl.presentedViewController;
}
if (!ctrl.view.window) {
return nil;
}
return ctrl;
}
+ (UIViewController *)viewController {
UIViewController *currentViewController = nil;
UIWindow * window = [[UIApplication sharedApplication] keyWindow];
if ([window subviews].count == 0) return nil;
if (window.windowLevel != UIWindowLevelNormal) {
NSArray *windows = [[UIApplication sharedApplication] windows];
for (UIWindow * subWindow in windows) {
if (subWindow.windowLevel == UIWindowLevelNormal) {
window = subWindow;
break;
}
}
}
UIView *frontView = [[window subviews] objectAtIndex:0];
id nextResponder = [frontView nextResponder];
if ([nextResponder isKindOfClass:[UIViewController class]]) {
currentViewController = nextResponder;
} else {
currentViewController = window.rootViewController;
}
return currentViewController;
}
然后针对十一种对话框在不同需求下进行封装,每种情况分别对应了两个方法,一个是直接传入当前控制器的,一个是通过上述方法自动获取当前控制器的。均采用类方法直接调用,一行代码实现效果,非常方便。
/**
* 创建系统弹框
* 默认悬浮中间,显示确认和取消
* @pram title 标题
* @pram complement 点击确认的回调
*/
+ (instancetype)dialogWithTitle:(NSString *)title complement:(void(^)(void))complement {
return [self dialogWithViewController:[self getCurrentViewController] title:title complement:complement];
}
+ (instancetype)dialogWithViewController:(UIViewController *)viewController title:(NSString *)title complement:(void(^)(void))complement {
return [self dialogWithViewController:viewController title:title dialogType:SystemDialogType_Default complement:complement];
}
/**
* 创建中间悬浮的系统弹框的类型
*
* @pram title 标题
* @pram dialogType 弹框类型(只对前五种有效)
* @pram complement 点击确认的回调
*/
+ (instancetype)dialogWithTitle:(NSString *)title dialogType:(TYZSystemDialogType)dialogType complement:(void(^)(void))complement {
return [self dialogWithViewController:[self getCurrentViewController] title:title dialogType:dialogType complement:complement];
}
+ (instancetype)dialogWithViewController:(UIViewController *)viewController title:(NSString *)title dialogType:(TYZSystemDialogType)dialogType complement:(void(^)(void))complement {
TYZSystemDialog *dialog = [TYZSystemDialog alertControllerWithTitle:title message:nil preferredStyle:UIAlertControllerStyleAlert];
if (dialogType == SystemDialogType_Default) {
[dialog addAction:[self creatActionWithTitle:[@"确认" S] complement:complement]];
[dialog addAction:[self creatActionWithTitle:[@"取消" S] complement:nil]];
} else if (dialogType == SystemDialogType_OneSure) {
[dialog addAction:[self creatActionWithTitle:[@"确认" S] complement:complement]];
} else if (dialogType == SystemDialogType_Destructive_Center) {
[dialog addAction:[self creatActionWithTitle:[@"确认" S] style:UIAlertActionStyleDestructive complement:complement]];
} else if (dialogType == SystemDialogType_Destructive_Left) {
[dialog addAction:[self creatActionWithTitle:[@"确认" S] style:UIAlertActionStyleDestructive complement:complement]];
[dialog addAction:[self creatActionWithTitle:[@"取消" S] complement:nil]];
} else if (dialogType == SystemDialogType_Destructive_Left) {
[dialog addAction:[self creatActionWithTitle:[@"确认" S] complement:complement]];
[dialog addAction:[self creatActionWithTitle:[@"取消" S] style:UIAlertActionStyleDestructive complement:nil]];
}
[viewController presentViewController:dialog animated:YES completion:nil];
return dialog;
}
/**
* 根据传入的数据创建底部弹出的系统弹框的类型 点击选择第几个选项的回调
*
* @pram title 标题
* @pram showData 显示的数据,弹框类型只是第六种,从底部弹出
* @pram complement 点击确认的回调
*/
+ (instancetype)dialogWithTitle:(NSString *)title showData:(NSArray<NSString *> *)showData complement:(void(^)(NSInteger index))complement {
return [self dialogWithViewController:[self getCurrentViewController] title:title showData:showData complement:complement];
}
+ (instancetype)dialogWithViewController:(UIViewController *)viewController title:(NSString *)title showData:(NSArray<NSString *> *)showData complement:(void(^)(NSInteger index))complement {
TYZSystemDialog *dialog = [TYZSystemDialog alertControllerWithTitle:title message:nil preferredStyle:UIAlertControllerStyleActionSheet];
NSInteger number = showData.count > MostSystemDialogNumber ? MostSystemDialogNumber : showData.count;
for (NSInteger i = 0; i < number; i ++) {
[dialog addAction:[self creatActionWithTitle:showData[i] complement:^{
if (complement) {
complement(i);
}
}]];
}
[dialog addAction:[self creatActionWithTitle:[@"取消" S] style:UIAlertActionStyleCancel complement:nil]];
[viewController presentViewController:dialog animated:YES completion:nil];
return dialog;
}
/**
* 根据自定义传入的中间弹框选择的数据创建系统弹框的类型 点击选择第几个选项的回调
*
* @pram title 标题
* @pram showData 显示的数据
* @pram dialogType 弹框类型(只对后五种有效)
* @pram complement 点击确认的回调
*/
+ (instancetype)dialogWithTitle:(NSString *)title showData:(NSArray<NSString *> *)showData dialogType:(TYZSystemDialogType)dialogType complement:(void(^)(NSInteger index))complement {
return [self dialogWithViewController:[self getCurrentViewController] title:title showData:showData dialogType:dialogType complement:complement];
}
+ (instancetype)dialogWithViewController:(UIViewController *)viewController title:(NSString *)title showData:(NSArray<NSString *> *)showData dialogType:(TYZSystemDialogType)dialogType complement:(void(^)(NSInteger index))complement {
TYZSystemDialog *dialog = [TYZSystemDialog alertControllerWithTitle:title message:nil preferredStyle:UIAlertControllerStyleAlert];
if (showData.count == 1) {
if (dialogType == SystemDialogType_Custem_OneSure) {
[dialog addAction:[self creatActionWithTitle:showData.firstObject complement:^{
if (complement) {
complement(0);
}
}]];
} else if (dialogType == SystemDialogType_Custem_Destructive_Center) {
[dialog addAction:[self creatActionWithTitle:showData.firstObject style:UIAlertActionStyleDestructive complement:^{
if (complement) {
complement(0);
}
}]];
}
} else if (showData.count == 2) {
if (dialogType == SystemDialogType_Custem_Default) {
[dialog addAction:[self creatActionWithTitle:showData.firstObject complement:^{
if (complement) {
complement(0);
}
}]];
[dialog addAction:[self creatActionWithTitle:showData.lastObject complement:^{
if (complement) {
complement(1);
}
}]];
} else if (dialogType == SystemDialogType_Custem_Destructive_Left) {
[dialog addAction:[self creatActionWithTitle:showData.firstObject style:UIAlertActionStyleDestructive complement:^{
if (complement) {
complement(0);
}
}]];
[dialog addAction:[self creatActionWithTitle:showData.lastObject style:UIAlertActionStyleCancel complement:^{
if (complement) {
complement(1);
}
}]];
} else if (dialogType == SystemDialogType_Custem_Destructive_Right) {
[dialog addAction:[self creatActionWithTitle:showData.firstObject style:UIAlertActionStyleCancel complement:^{
if (complement) {
complement(0);
}
}]];
[dialog addAction:[self creatActionWithTitle:showData.lastObject style:UIAlertActionStyleDestructive complement:^{
if (complement) {
complement(1);
}
}]];
}
}
[viewController presentViewController:dialog animated:YES completion:nil];
return dialog;
}
/**
* 创建带输入框的非密码形式的弹框
*
* @pram title 标题
* @pram placeholder 占位文本
* @pram textLength 输入字数上限
* @pram complement 点击确认的回调
*/
+ (instancetype)dialogWithTitle:(NSString *)title placeholder:(NSString *)placeholder textLenth:(NSInteger)textLength complement:(void(^)(NSString *text))complement {
return [self dialogWithViewController:[self getCurrentViewController] title:title placeholder:placeholder textLenth:textLength complement:complement];
}
+ (instancetype)dialogWithViewController:(UIViewController *)viewController title:(NSString *)title placeholder:(NSString *)placeholder textLenth:(NSInteger)textLength complement:(void(^)(NSString *text))complement {
return [self dialogWithViewController:viewController title:title placeholder:placeholder textLenth:textLength secureTextEntry:NO complement:complement];
}
/**
* 创建带输入框的弹框
*
* @pram title 标题
* @pram placeholder 占位文本
* @pram textLength 输入字数上限
* @pram secureTextEntry 是否是密码模式
* @pram complement 点击确认的回调
*/
+ (instancetype)dialogWithTitle:(NSString *)title placeholder:(NSString *)placeholder textLenth:(NSInteger)textLength secureTextEntry:(BOOL)secureTextEntry complement:(void(^)(NSString *text))complement {
return [self dialogWithViewController:[self getCurrentViewController] title:title placeholder:placeholder textLenth:textLength secureTextEntry:secureTextEntry complement:complement];
}
+ (instancetype)dialogWithViewController:(UIViewController *)viewController title:(NSString *)title placeholder:(NSString *)placeholder textLenth:(NSInteger)textLength secureTextEntry:(BOOL)secureTextEntry complement:(void(^)(NSString *text))complement {
TYZSystemDialog *dialog = [TYZSystemDialog alertControllerWithTitle:title message:nil preferredStyle:UIAlertControllerStyleAlert];
[dialog addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
textField.placeholder = placeholder;
textField.secureTextEntry = secureTextEntry;
textField.clearButtonMode = UITextFieldViewModeWhileEditing;
}];
UIAlertAction *action = [UIAlertAction actionWithTitle:[@"确认" S] style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
if (complement) {
complement(dialog.textFields.firstObject.text);
}
}];
[dialog addAction:action];
[dialog addAction:[self creatActionWithTitle:[@"取消" S] complement:nil]];
[viewController presentViewController:dialog animated:YES completion:nil];
return dialog;
}
/**
* 创建默认的按钮
*
* @pram title 按钮标题
* @pram complement 点击确认的回调
*/
+ (UIAlertAction *)creatActionWithTitle:(NSString *)title complement:(void(^)(void))complement {
return [self creatActionWithTitle:title style:UIAlertActionStyleDefault complement:complement];
}
/**
* 创建默认的按钮
*
* @pram title 按钮标题
* @pram style 按钮类型
* @pram complement 点击确认的回调
*/
+ (UIAlertAction *)creatActionWithTitle:(NSString *)title style:(UIAlertActionStyle)style complement:(void(^)(void))complement {
UIAlertAction *action = [UIAlertAction actionWithTitle:title style:style handler:^(UIAlertAction * _Nonnull action) {
if (complement) {
complement();
}
}];
return action;
}
注意:如果有重新获取了window,在上面添加了一层,那么这个对话框不会出现在最前面了。我试着将moda出来的对话框控制器的View提到最前面,但是失败。这种矛盾的出现只好规避之。