1
This commit is contained in:
75
keyBoard/Class/Common/V/KBAlert.h
Normal file
75
keyBoard/Class/Common/V/KBAlert.h
Normal file
@@ -0,0 +1,75 @@
|
||||
//
|
||||
// KBAlert.h
|
||||
// keyBoard
|
||||
//
|
||||
// 系统 UIAlertController 的轻量封装:更易用、更健壮。
|
||||
// 特性:
|
||||
// - 任意线程调用;内部切回主线程
|
||||
// - 自动找到可展示的 VC(使用顶层 presented VC)
|
||||
// - Alert 队列:避免“正在展示时再次 present 失败”的崩溃/警告
|
||||
// - iPad ActionSheet 自动设置 popover 锚点(无需关心 sourceView)
|
||||
// - 常见场景一行调用:提示/确认/输入框/操作表
|
||||
// - 可选指定展示的 VC(如在 Extension 内)
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef void (^KBAlertBoolHandler)(BOOL ok);
|
||||
typedef void (^KBAlertIndexHandler)(NSInteger index);
|
||||
typedef void (^KBAlertTextHandler)(NSString * _Nullable text, BOOL ok);
|
||||
|
||||
@interface KBAlert : NSObject
|
||||
|
||||
/// 指定一个缺省的“用于 present 的 VC”。
|
||||
/// - 适用:App Extension 或自管理容器场景;App 内一般无需设置。
|
||||
/// - 注意:弱引用;随时可被释放。
|
||||
+ (void)setDefaultPresenter:(nullable __kindof UIViewController *)presenter;
|
||||
|
||||
#pragma mark - 简单提示(单按钮)
|
||||
/// 标准 OK 提示;按钮文案默认为“好”或“OK”。
|
||||
+ (void)showTitle:(nullable NSString *)title message:(nullable NSString *)message;
|
||||
/// 自定义按钮文案与回调。
|
||||
+ (void)showTitle:(nullable NSString *)title
|
||||
message:(nullable NSString *)message
|
||||
button:(nullable NSString *)button
|
||||
completion:(dispatch_block_t)completion;
|
||||
|
||||
#pragma mark - 确认对话框(双按钮)
|
||||
/// 默认“取消/确定”按钮,回调 ok=YES 表示点击了确定。
|
||||
+ (void)confirmTitle:(nullable NSString *)title
|
||||
message:(nullable NSString *)message
|
||||
completion:(KBAlertBoolHandler)completion;
|
||||
/// 自定义按钮文案。
|
||||
+ (void)confirmTitle:(nullable NSString *)title
|
||||
message:(nullable NSString *)message
|
||||
ok:(nullable NSString *)okTitle
|
||||
cancel:(nullable NSString *)cancelTitle
|
||||
completion:(KBAlertBoolHandler)completion;
|
||||
|
||||
#pragma mark - 输入框(单行)
|
||||
/// 带单个输入框;ok=YES 时返回输入内容。
|
||||
+ (void)promptTitle:(nullable NSString *)title
|
||||
message:(nullable NSString *)message
|
||||
placeholder:(nullable NSString *)placeholder
|
||||
ok:(nullable NSString *)okTitle
|
||||
cancel:(nullable NSString *)cancelTitle
|
||||
keyboardType:(UIKeyboardType)type
|
||||
configuration:(void (^ _Nullable)(UITextField *tf))config
|
||||
completion:(KBAlertTextHandler)completion;
|
||||
|
||||
#pragma mark - 操作表(ActionSheet)
|
||||
/// 操作表;index 为点击项序号,按 actions 顺序从 0 开始;取消返回 -1。
|
||||
+ (void)actionSheetTitle:(nullable NSString *)title
|
||||
message:(nullable NSString *)message
|
||||
actions:(NSArray<NSString *> *)actions
|
||||
cancel:(nullable NSString *)cancelTitle
|
||||
destructiveIndex:(NSInteger)destructiveIndex // 传入 -1 表示无
|
||||
fromView:(nullable UIView *)anchorView // iPad 可指定锚点;传 nil 自动居中
|
||||
completion:(KBAlertIndexHandler)completion;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
224
keyBoard/Class/Common/V/KBAlert.m
Normal file
224
keyBoard/Class/Common/V/KBAlert.m
Normal file
@@ -0,0 +1,224 @@
|
||||
//
|
||||
// KBAlert.m
|
||||
// keyBoard
|
||||
//
|
||||
|
||||
#import "KBAlert.h"
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "UIViewController+Extension.h"
|
||||
|
||||
@implementation KBAlert
|
||||
|
||||
// 简单的串行展示队列,避免重复 present 引发的异常
|
||||
static NSMutableArray<dispatch_block_t> *sQueue;
|
||||
static BOOL sPresenting = NO;
|
||||
static __weak UIViewController *sDefaultPresenter = nil; // 可选外部指定的 presenter(弱引用)
|
||||
|
||||
#pragma mark - Helpers
|
||||
|
||||
+ (void)initialize {
|
||||
if (self == KBAlert.class) {
|
||||
sQueue = [NSMutableArray array];
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)onMain:(dispatch_block_t)blk {
|
||||
if (NSThread.isMainThread) { blk(); } else { dispatch_async(dispatch_get_main_queue(), blk); }
|
||||
}
|
||||
|
||||
+ (UIViewController *)presentingVC {
|
||||
// 优先使用外部指定的 presenter;否则找顶层
|
||||
UIViewController *vc = sDefaultPresenter;
|
||||
if (!vc) {
|
||||
vc = [UIViewController kb_topMostViewController];
|
||||
}
|
||||
return vc;
|
||||
}
|
||||
|
||||
+ (void)enqueuePresent:(dispatch_block_t)presenter {
|
||||
[self onMain:^{
|
||||
[sQueue addObject:[presenter copy]];
|
||||
if (!sPresenting) { [self _dequeueAndPresentNext]; }
|
||||
}];
|
||||
}
|
||||
|
||||
+ (void)_dequeueAndPresentNext {
|
||||
if (sPresenting || sQueue.count == 0) { return; }
|
||||
sPresenting = YES;
|
||||
dispatch_block_t blk = sQueue.firstObject;
|
||||
[sQueue removeObjectAtIndex:0];
|
||||
blk();
|
||||
}
|
||||
|
||||
// 在任意 action 执行后调用,标记结束并尝试继续队列
|
||||
+ (void)_markFinishedAndContinue {
|
||||
// 延迟到下一个 runloop 以确保系统完成 dismiss 流程
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
sPresenting = NO;
|
||||
[self _dequeueAndPresentNext];
|
||||
});
|
||||
}
|
||||
|
||||
// iPad 的 ActionSheet 需要 popover 锚点;此处自动兜底
|
||||
+ (void)configurePopoverIfNeeded:(UIAlertController *)ac anchorView:(UIView *)anchorView inVC:(UIViewController *)vc {
|
||||
if (ac.preferredStyle != UIAlertControllerStyleActionSheet) { return; }
|
||||
UIPopoverPresentationController *pop = ac.popoverPresentationController;
|
||||
if (!pop) { return; }
|
||||
if (anchorView) {
|
||||
pop.sourceView = anchorView;
|
||||
pop.sourceRect = anchorView.bounds;
|
||||
} else if (vc.view) {
|
||||
pop.sourceView = vc.view;
|
||||
CGSize sz = vc.view.bounds.size;
|
||||
CGRect r = CGRectMake(MAX(0, sz.width * 0.5 - 1), MAX(0, sz.height * 0.5 - 1), 2, 2);
|
||||
pop.sourceRect = r; // 居中一个很小的矩形,视觉近似居中弹出
|
||||
pop.permittedArrowDirections = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Public API
|
||||
|
||||
+ (void)setDefaultPresenter:(UIViewController *)presenter { sDefaultPresenter = presenter; }
|
||||
|
||||
+ (void)showTitle:(NSString *)title message:(NSString *)message {
|
||||
[self showTitle:title message:message button:nil completion:nil];
|
||||
}
|
||||
|
||||
+ (void)showTitle:(NSString *)title
|
||||
message:(NSString *)message
|
||||
button:(NSString *)button
|
||||
completion:(dispatch_block_t)completion {
|
||||
[self enqueuePresent:^{
|
||||
UIViewController *vc = [self presentingVC];
|
||||
if (!vc) { // 无法获取 presenter,直接结束队列
|
||||
[self _markFinishedAndContinue];
|
||||
return;
|
||||
}
|
||||
NSString *ok = button ?: (NSLocalizedString(@"OK", nil).length ? NSLocalizedString(@"OK", nil) : @"好");
|
||||
UIAlertController *ac = [UIAlertController alertControllerWithTitle:(title ?: @"")
|
||||
message:(message ?: @"")
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
__weak UIAlertController *wac = ac;
|
||||
UIAlertAction *okAct = [UIAlertAction actionWithTitle:ok style:UIAlertActionStyleDefault handler:^(__unused UIAlertAction * _Nonnull action) {
|
||||
if (completion) { completion(); }
|
||||
// 防止异常:确保 alert 被关闭
|
||||
[wac dismissViewControllerAnimated:YES completion:nil];
|
||||
[KBAlert _markFinishedAndContinue];
|
||||
}];
|
||||
[ac addAction:okAct];
|
||||
[vc presentViewController:ac animated:YES completion:nil];
|
||||
}];
|
||||
}
|
||||
|
||||
+ (void)confirmTitle:(NSString *)title message:(NSString *)message completion:(KBAlertBoolHandler)completion {
|
||||
[self confirmTitle:title message:message ok:nil cancel:nil completion:completion];
|
||||
}
|
||||
|
||||
+ (void)confirmTitle:(NSString *)title
|
||||
message:(NSString *)message
|
||||
ok:(NSString *)okTitle
|
||||
cancel:(NSString *)cancelTitle
|
||||
completion:(KBAlertBoolHandler)completion {
|
||||
[self enqueuePresent:^{
|
||||
UIViewController *vc = [self presentingVC];
|
||||
if (!vc) { [self _markFinishedAndContinue]; return; }
|
||||
NSString *ok = okTitle ?: @"确定";
|
||||
NSString *cancel = cancelTitle ?: @"取消";
|
||||
UIAlertController *ac = [UIAlertController alertControllerWithTitle:(title ?: @"")
|
||||
message:(message ?: @"")
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
__weak UIAlertController *wac = ac;
|
||||
UIAlertAction *cancelAct = [UIAlertAction actionWithTitle:cancel style:UIAlertActionStyleCancel handler:^(__unused UIAlertAction * _Nonnull a) {
|
||||
if (completion) { completion(NO); }
|
||||
[wac dismissViewControllerAnimated:YES completion:nil];
|
||||
[KBAlert _markFinishedAndContinue];
|
||||
}];
|
||||
UIAlertAction *okAct = [UIAlertAction actionWithTitle:ok style:UIAlertActionStyleDefault handler:^(__unused UIAlertAction * _Nonnull a) {
|
||||
if (completion) { completion(YES); }
|
||||
[wac dismissViewControllerAnimated:YES completion:nil];
|
||||
[KBAlert _markFinishedAndContinue];
|
||||
}];
|
||||
[ac addAction:cancelAct];
|
||||
[ac addAction:okAct];
|
||||
[vc presentViewController:ac animated:YES completion:nil];
|
||||
}];
|
||||
}
|
||||
|
||||
+ (void)promptTitle:(NSString *)title
|
||||
message:(NSString *)message
|
||||
placeholder:(NSString *)placeholder
|
||||
ok:(NSString *)okTitle
|
||||
cancel:(NSString *)cancelTitle
|
||||
keyboardType:(UIKeyboardType)type
|
||||
configuration:(void (^)(UITextField * _Nonnull))config
|
||||
completion:(KBAlertTextHandler)completion {
|
||||
[self enqueuePresent:^{
|
||||
UIViewController *vc = [self presentingVC];
|
||||
if (!vc) { [self _markFinishedAndContinue]; return; }
|
||||
NSString *ok = okTitle ?: @"确定";
|
||||
NSString *cancel = cancelTitle ?: @"取消";
|
||||
UIAlertController *ac = [UIAlertController alertControllerWithTitle:(title ?: @"")
|
||||
message:(message ?: @"")
|
||||
preferredStyle:UIAlertControllerStyleAlert];
|
||||
__weak UIAlertController *wac = ac;
|
||||
[ac addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull tf) {
|
||||
tf.placeholder = placeholder;
|
||||
tf.keyboardType = type;
|
||||
if (config) { config(tf); }
|
||||
}];
|
||||
UIAlertAction *cancelAct = [UIAlertAction actionWithTitle:cancel style:UIAlertActionStyleCancel handler:^(__unused UIAlertAction * _Nonnull a) {
|
||||
if (completion) { completion(nil, NO); }
|
||||
[wac dismissViewControllerAnimated:YES completion:nil];
|
||||
[KBAlert _markFinishedAndContinue];
|
||||
}];
|
||||
UIAlertAction *okAct = [UIAlertAction actionWithTitle:ok style:UIAlertActionStyleDefault handler:^(__unused UIAlertAction * _Nonnull a) {
|
||||
NSString *text = wac.textFields.firstObject.text;
|
||||
if (completion) { completion(text, YES); }
|
||||
[wac dismissViewControllerAnimated:YES completion:nil];
|
||||
[KBAlert _markFinishedAndContinue];
|
||||
}];
|
||||
[ac addAction:cancelAct];
|
||||
[ac addAction:okAct];
|
||||
[vc presentViewController:ac animated:YES completion:nil];
|
||||
}];
|
||||
}
|
||||
|
||||
+ (void)actionSheetTitle:(NSString *)title
|
||||
message:(NSString *)message
|
||||
actions:(NSArray<NSString *> *)actions
|
||||
cancel:(NSString *)cancelTitle
|
||||
destructiveIndex:(NSInteger)destructiveIndex
|
||||
fromView:(UIView *)anchorView
|
||||
completion:(KBAlertIndexHandler)completion {
|
||||
[self enqueuePresent:^{
|
||||
UIViewController *vc = [self presentingVC];
|
||||
if (!vc) { [self _markFinishedAndContinue]; return; }
|
||||
UIAlertController *ac = [UIAlertController alertControllerWithTitle:(title ?: @"")
|
||||
message:(message ?: @"")
|
||||
preferredStyle:UIAlertControllerStyleActionSheet];
|
||||
__weak UIAlertController *wac = ac;
|
||||
[actions enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
UIAlertActionStyle style = (destructiveIndex >= 0 && (NSInteger)idx == destructiveIndex) ? UIAlertActionStyleDestructive : UIAlertActionStyleDefault;
|
||||
UIAlertAction *act = [UIAlertAction actionWithTitle:obj ?: @"" style:style handler:^(__unused UIAlertAction * _Nonnull a) {
|
||||
if (completion) { completion((NSInteger)idx); }
|
||||
[wac dismissViewControllerAnimated:YES completion:nil];
|
||||
[KBAlert _markFinishedAndContinue];
|
||||
}];
|
||||
[ac addAction:act];
|
||||
}];
|
||||
if (cancelTitle.length) {
|
||||
UIAlertAction *cancel = [UIAlertAction actionWithTitle:cancelTitle style:UIAlertActionStyleCancel handler:^(__unused UIAlertAction * _Nonnull a) {
|
||||
if (completion) { completion(-1); }
|
||||
[wac dismissViewControllerAnimated:YES completion:nil];
|
||||
[KBAlert _markFinishedAndContinue];
|
||||
}];
|
||||
[ac addAction:cancel];
|
||||
}
|
||||
|
||||
[self configurePopoverIfNeeded:ac anchorView:anchorView inVC:vc];
|
||||
[vc presentViewController:ac animated:YES completion:nil];
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
72
keyBoard/Class/Common/V/KBHUD.h
Normal file
72
keyBoard/Class/Common/V/KBHUD.h
Normal file
@@ -0,0 +1,72 @@
|
||||
//
|
||||
// KBHUD.h
|
||||
// keyBoard
|
||||
//
|
||||
// 基于 MBProgressHUD 的轻量封装,提供类似 SVProgressHUD 的类方法调用方式。
|
||||
// 特性:
|
||||
// - 默认加到 KeyWindow 上
|
||||
// - 支持遮罩模式(不拦截/透明拦截/半透明黑遮罩)
|
||||
// - 默认不支持点击背景关闭;可为“本次显示/当前 HUD”开启点击关闭
|
||||
// - 文案/加载/进度/自动消失
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
@class UIView; // forward declare to avoid importing UIKit in header
|
||||
|
||||
typedef NS_ENUM(NSUInteger, KBHUDMaskType) {
|
||||
/// 不加遮罩,事件可透传到后面的视图(与 SVProgressHUDMaskTypeNone 类似)
|
||||
KBHUDMaskTypeNone = 0,
|
||||
/// 透明遮罩,拦截触摸但不变暗(与 SVProgressHUDMaskTypeClear 类似)
|
||||
KBHUDMaskTypeClear,
|
||||
/// 半透明黑色遮罩(与 SVProgressHUDMaskTypeBlack 类似)
|
||||
KBHUDMaskTypeBlack,
|
||||
};
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface KBHUD : NSObject
|
||||
|
||||
#pragma mark - 显示
|
||||
/// 纯菊花
|
||||
+ (void)show;
|
||||
/// 菊花 + 文案
|
||||
+ (void)showWithStatus:(nullable NSString *)status;
|
||||
/// 菊花 + 文案(可配置:是否允许点击背景关闭)
|
||||
+ (void)showWithStatus:(nullable NSString *)status allowTapToDismiss:(BOOL)allow;
|
||||
/// 圆形进度 0~1 + 文案
|
||||
+ (void)showProgress:(float)progress status:(nullable NSString *)status;
|
||||
/// 圆形进度 0~1 + 文案(可配置:是否允许点击背景关闭)
|
||||
+ (void)showProgress:(float)progress status:(nullable NSString *)status allowTapToDismiss:(BOOL)allow;
|
||||
/// 文案提示(自动隐藏)
|
||||
+ (void)showInfo:(NSString *)status;
|
||||
/// 成功提示(自动隐藏)
|
||||
+ (void)showSuccess:(NSString *)status;
|
||||
/// 失败提示(自动隐藏)
|
||||
+ (void)showError:(NSString *)status;
|
||||
|
||||
#pragma mark - 隐藏
|
||||
/// 立即隐藏
|
||||
+ (void)dismiss;
|
||||
/// 延时隐藏
|
||||
+ (void)dismissWithDelay:(NSTimeInterval)delay;
|
||||
|
||||
#pragma mark - 配置(全局)
|
||||
/// 设置默认遮罩类型,默认 KBHUDMaskTypeClear
|
||||
+ (void)setDefaultMaskType:(KBHUDMaskType)maskType;
|
||||
/// 为“当前正在展示的 HUD”设置点击背景是否关闭;若当前没有 HUD 则忽略
|
||||
+ (void)setTapToDismissEnabled:(BOOL)enabled;
|
||||
/// 仅本次显示:菊花(无文案)可配置是否允许点击背景关闭
|
||||
+ (void)showAllowTapToDismiss:(BOOL)allow;
|
||||
/// 设置自动隐藏的时长(success/error/info),默认 1.2s
|
||||
+ (void)setAutoDismissInterval:(NSTimeInterval)interval;
|
||||
|
||||
/// 设置缺省承载视图(App Extension 环境下必须设置,例如在键盘扩展里传入 self.view)
|
||||
/// 注意:内部弱引用,不会形成循环引用。
|
||||
/// 若不设置,在非 Extension 的 App 内默认加到 KeyWindow;在 Extension 内将不会显示。
|
||||
/// 可在 viewDidLoad 或 viewDidAppear 调用一次即可。
|
||||
/// @param view 作为 HUD 的承载父视图
|
||||
+ (void)setContainerView:(nullable UIView *)view;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
200
keyBoard/Class/Common/V/KBHUD.m
Normal file
200
keyBoard/Class/Common/V/KBHUD.m
Normal file
@@ -0,0 +1,200 @@
|
||||
//
|
||||
// KBHUD.m
|
||||
// keyBoard
|
||||
//
|
||||
|
||||
#import "KBHUD.h"
|
||||
#import <MBProgressHUD/MBProgressHUD.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#ifndef KBSCREEN
|
||||
#define KBSCREEN [UIScreen mainScreen].bounds.size
|
||||
#endif
|
||||
|
||||
@implementation KBHUD
|
||||
|
||||
static __weak MBProgressHUD *sHUD = nil;
|
||||
static KBHUDMaskType sMaskType = KBHUDMaskTypeClear; // 全局默认遮罩
|
||||
static BOOL sDefaultTapToDismiss = NO; // 全局默认:不允许点击关闭
|
||||
static NSTimeInterval sAutoDismiss = 1.2;
|
||||
static __weak UIView *sContainerView = nil; // 缺省承载视图(扩展里必须设置)
|
||||
|
||||
#pragma mark - Private Helpers
|
||||
|
||||
+ (void)onMain:(dispatch_block_t)blk {
|
||||
if (NSThread.isMainThread) { blk(); } else { dispatch_async(dispatch_get_main_queue(), blk); }
|
||||
}
|
||||
|
||||
+ (MBProgressHUD *)ensureHUDWithMask:(KBHUDMaskType)mask tap:(BOOL)tap {
|
||||
// 先尝试使用外部指定的容器视图(扩展环境推荐)
|
||||
UIView *hostView = sContainerView;
|
||||
#ifndef KB_APP_EXTENSION
|
||||
// App 内退回到 KeyWindow
|
||||
if (!hostView) {
|
||||
// KB_KeyWindow 在 App 目标的 PrefixHeader 中定义;在扩展内不依赖它
|
||||
UIWindow *win = nil;
|
||||
// 避免强依赖某个前缀:这里以运行时方式访问 UIApplication 以规避编译期的 App Extension 限制
|
||||
Class uiAppClass = NSClassFromString(@"UIApplication");
|
||||
if (uiAppClass && [uiAppClass respondsToSelector:@selector(sharedApplication)]) {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
|
||||
id app = [uiAppClass performSelector:@selector(sharedApplication)];
|
||||
if ([app respondsToSelector:@selector(keyWindow)]) {
|
||||
win = [app keyWindow];
|
||||
}
|
||||
if (!win && [app respondsToSelector:@selector(windows)]) {
|
||||
NSArray *wins = [app windows];
|
||||
win = wins.firstObject;
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
hostView = win;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!hostView) { return nil; }
|
||||
|
||||
MBProgressHUD *hud = sHUD;
|
||||
if (!hud) {
|
||||
hud = [MBProgressHUD showHUDAddedTo:hostView animated:YES];
|
||||
sHUD = hud;
|
||||
// 外观:深色圆角,白色文字,模仿 SVProgressHUD 默认
|
||||
hud.removeFromSuperViewOnHide = YES;
|
||||
hud.bezelView.style = MBProgressHUDBackgroundStyleSolidColor;
|
||||
hud.bezelView.layer.cornerRadius = 12.0;
|
||||
hud.bezelView.color = [UIColor colorWithWhite:0 alpha:0.8];
|
||||
hud.contentColor = UIColor.whiteColor;
|
||||
hud.margin = 16;
|
||||
hud.label.numberOfLines = 0;
|
||||
hud.detailsLabel.numberOfLines = 0;
|
||||
}
|
||||
// 遮罩与交互(按本次 show 的入参应用)
|
||||
[self applyMaskType:mask hud:hud];
|
||||
[self applyTapToDismiss:tap hud:hud];
|
||||
return hud;
|
||||
}
|
||||
|
||||
+ (void)applyMaskType:(KBHUDMaskType)type hud:(MBProgressHUD *)hud {
|
||||
switch (type) {
|
||||
case KBHUDMaskTypeNone: {
|
||||
hud.userInteractionEnabled = NO; // 不拦截触摸
|
||||
hud.backgroundView.style = MBProgressHUDBackgroundStyleSolidColor;
|
||||
hud.backgroundView.color = UIColor.clearColor;
|
||||
} break;
|
||||
case KBHUDMaskTypeClear: {
|
||||
hud.userInteractionEnabled = YES; // 拦截触摸但不变暗
|
||||
hud.backgroundView.style = MBProgressHUDBackgroundStyleSolidColor;
|
||||
hud.backgroundView.color = [UIColor colorWithWhite:0 alpha:0.01];
|
||||
} break;
|
||||
case KBHUDMaskTypeBlack: {
|
||||
hud.userInteractionEnabled = YES; // 变暗并拦截
|
||||
hud.backgroundView.style = MBProgressHUDBackgroundStyleSolidColor;
|
||||
hud.backgroundView.color = [UIColor colorWithWhite:0 alpha:0.35];
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)applyTapToDismiss:(BOOL)enabled hud:(MBProgressHUD *)hud {
|
||||
UIView *bg = hud.backgroundView;
|
||||
// 清理旧手势
|
||||
for (UIGestureRecognizer *g in bg.gestureRecognizers.copy) {
|
||||
if ([g isKindOfClass:UITapGestureRecognizer.class] && g.view == bg) {
|
||||
[bg removeGestureRecognizer:g];
|
||||
}
|
||||
}
|
||||
if (enabled) {
|
||||
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_kb_handleTap)];
|
||||
[bg addGestureRecognizer:tap];
|
||||
// 确保可拦截
|
||||
hud.userInteractionEnabled = YES;
|
||||
if (hud.backgroundView.color == UIColor.clearColor) {
|
||||
hud.backgroundView.color = [UIColor colorWithWhite:0 alpha:0.01];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)_kb_handleTap {
|
||||
[self dismiss];
|
||||
}
|
||||
|
||||
+ (void)_showText:(NSString *)text icon:(nullable UIImage *)icon {
|
||||
[self onMain:^{
|
||||
MBProgressHUD *hud = [self ensureHUDWithMask:sMaskType tap:sDefaultTapToDismiss];
|
||||
if (!hud) { return; }
|
||||
hud.mode = icon ? MBProgressHUDModeCustomView : MBProgressHUDModeText;
|
||||
hud.label.text = text ?: @"";
|
||||
hud.detailsLabel.text = nil;
|
||||
if (icon) {
|
||||
UIImageView *iv = [[UIImageView alloc] initWithImage:icon];
|
||||
hud.customView = iv;
|
||||
} else {
|
||||
hud.customView = nil;
|
||||
}
|
||||
[hud hideAnimated:YES afterDelay:sAutoDismiss];
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Public API
|
||||
|
||||
+ (void)show { [self showWithStatus:nil]; }
|
||||
|
||||
+ (void)showWithStatus:(NSString *)status { [self showWithStatus:status allowTapToDismiss:NO]; }
|
||||
|
||||
+ (void)showWithStatus:(NSString *)status allowTapToDismiss:(BOOL)allow {
|
||||
[self onMain:^{
|
||||
MBProgressHUD *hud = [self ensureHUDWithMask:sMaskType tap:allow];
|
||||
if (!hud) { return; }
|
||||
hud.mode = MBProgressHUDModeIndeterminate;
|
||||
hud.label.text = status ?: @"";
|
||||
}];
|
||||
}
|
||||
|
||||
+ (void)showProgress:(float)progress status:(NSString *)status {
|
||||
[self showProgress:progress status:status allowTapToDismiss:NO];
|
||||
}
|
||||
|
||||
+ (void)showProgress:(float)progress status:(NSString *)status allowTapToDismiss:(BOOL)allow {
|
||||
[self onMain:^{
|
||||
MBProgressHUD *hud = [self ensureHUDWithMask:sMaskType tap:allow];
|
||||
if (!hud) { return; }
|
||||
hud.mode = MBProgressHUDModeDeterminate;
|
||||
hud.progress = progress;
|
||||
hud.label.text = status ?: @"";
|
||||
}];
|
||||
}
|
||||
|
||||
+ (void)showInfo:(NSString *)status {
|
||||
[self _showText:status icon:nil];
|
||||
}
|
||||
|
||||
+ (void)showSuccess:(NSString *)status {
|
||||
// 简单起见,使用文字模式;如需图标,可替换为自定义勾勾图片
|
||||
[self _showText:status ?: @"成功" icon:nil];
|
||||
}
|
||||
|
||||
+ (void)showError:(NSString *)status { [self _showText:status ?: @"失败" icon:nil]; }
|
||||
|
||||
+ (void)dismiss { [self onMain:^{ [sHUD hideAnimated:YES]; }]; }
|
||||
|
||||
+ (void)dismissWithDelay:(NSTimeInterval)delay { [self onMain:^{ [sHUD hideAnimated:YES afterDelay:delay]; }]; }
|
||||
|
||||
#pragma mark - Config
|
||||
|
||||
+ (void)setDefaultMaskType:(KBHUDMaskType)maskType { sMaskType = maskType; }
|
||||
+ (void)setTapToDismissEnabled:(BOOL)enabled {
|
||||
// 仅针对当前可见 HUD 生效,不修改默认值,满足“B 允许点击,A 默认不允许”的诉求
|
||||
[self onMain:^{
|
||||
if (sHUD) { [self applyTapToDismiss:enabled hud:sHUD]; }
|
||||
}];
|
||||
}
|
||||
+ (void)setAutoDismissInterval:(NSTimeInterval)interval { sAutoDismiss = MAX(0.2, interval); }
|
||||
|
||||
+ (void)showAllowTapToDismiss:(BOOL)allow {
|
||||
[self showWithStatus:nil allowTapToDismiss:allow];
|
||||
}
|
||||
|
||||
+ (void)setContainerView:(UIView *)view {
|
||||
sContainerView = view;
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user