Files
keyboard/keyBoard/Class/Common/V/KBHUD.m
2025-12-15 16:41:53 +08:00

223 lines
7.8 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); }
}
/// 解析当前 HUD 需要附着的宿主视图
+ (UIView *)kb_resolvedHostView {
UIView *hostView = sContainerView;
#ifndef KB_APP_EXTENSION
// App 内退回到 KeyWindow
if (!hostView) {
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
return hostView;
}
+ (MBProgressHUD *)ensureHUDWithMask:(KBHUDMaskType)mask tap:(BOOL)tap {
UIView *hostView = [self kb_resolvedHostView];
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 *loadingHUD = sHUD;
if (loadingHUD) {
[loadingHUD hideAnimated:YES];
}
sHUD = nil;
UIView *hostView = [self kb_resolvedHostView];
if (!hostView) { return; }
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:hostView animated:YES];
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;
[self applyMaskType:sMaskType hud:hud];
[self applyTapToDismiss:sDefaultTapToDismiss hud:hud];
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 ?: KBLocalized(@"Success") icon:nil];
}
+ (void)showError:(NSString *)status { [self _showText:status ?: KBLocalized(@"Failed") 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