This commit is contained in:
2025-11-10 15:38:30 +08:00
parent 97316c7989
commit 1cdc17b710
18 changed files with 337 additions and 20 deletions

View 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