Files
keyboard/keyBoard/Class/Common/V/KBAlert.m

244 lines
10 KiB
Mathematica
Raw Normal View History

2025-11-10 15:38:30 +08:00
//
// 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;
}
2025-11-17 20:07:39 +08:00
NSString *ok = button ?: (NSLocalizedString(@"OK", nil).length ? NSLocalizedString(@"OK", nil) : KBLocalized(@"好"));
2025-11-10 15:38:30 +08:00
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 {
2025-11-10 15:55:36 +08:00
[self confirmTitle:title message:message ok:okTitle cancel:cancelTitle okColor:nil cancelColor:nil completion:completion];
}
// KVC action API
static inline void KBSetActionTitleColor(UIAlertAction *action, UIColor *color) {
if (!color || !action) return;
@try { [action setValue:color forKey:@"titleTextColor"]; } @catch (__unused NSException *e) {}
}
+ (void)confirmTitle:(NSString *)title
message:(NSString *)message
ok:(NSString *)okTitle
cancel:(NSString *)cancelTitle
okColor:(UIColor *)okColor
cancelColor:(UIColor *)cancelColor
completion:(KBAlertBoolHandler)completion {
2025-11-10 15:38:30 +08:00
[self enqueuePresent:^{
UIViewController *vc = [self presentingVC];
if (!vc) { [self _markFinishedAndContinue]; return; }
2025-11-17 20:07:39 +08:00
NSString *ok = okTitle ?: KBLocalized(@"Confirm");
NSString *cancel = cancelTitle ?: KBLocalized(@"Cancel");
2025-11-10 15:38:30 +08:00
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];
}];
2025-11-10 15:55:36 +08:00
//
KBSetActionTitleColor(okAct, okColor);
KBSetActionTitleColor(cancelAct, cancelColor);
2025-11-10 15:38:30 +08:00
[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; }
2025-11-17 20:07:39 +08:00
NSString *ok = okTitle ?: KBLocalized(@"Confirm");
NSString *cancel = cancelTitle ?: KBLocalized(@"Cancel");
2025-11-10 15:38:30 +08:00
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