Files
keyboard/keyBoard/Class/Categories/KBHUD.m
2025-10-31 15:08:30 +08:00

201 lines
7.1 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// 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