Compare commits
3 Commits
fac5e7657c
...
9f4110b24a
| Author | SHA1 | Date | |
|---|---|---|---|
| 9f4110b24a | |||
| 1cdc17b710 | |||
| 97316c7989 |
@@ -234,7 +234,7 @@ static CGFloat KEYBOARDHEIGHT = 256 + 20;
|
||||
// 使用与主 App 一致的自定义 Scheme
|
||||
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@@//login?src=keyboard", KB_APP_SCHEME]];
|
||||
if (!url) return;
|
||||
__weak typeof(self) weakSelf = self;
|
||||
KBWeakSelf
|
||||
[self.extensionContext openURL:url completionHandler:^(__unused BOOL success) {
|
||||
// 即使失败也不重复尝试;避免打扰。
|
||||
__unused typeof(weakSelf) selfStrong = weakSelf;
|
||||
|
||||
@@ -259,7 +259,7 @@ static NSString * const kKBFunctionTagCellId = @"KBFunctionTagCellId";
|
||||
// 未开启“完全访问”时不做自动读取,避免宿主/系统拒绝并刷错误日志
|
||||
if (![[KBFullAccessManager shared] hasFullAccess]) return;
|
||||
if (self.pasteboardTimer) return;
|
||||
__weak typeof(self) weakSelf = self;
|
||||
KBWeakSelf
|
||||
self.pasteboardTimer = [NSTimer scheduledTimerWithTimeInterval:0.5 repeats:YES block:^(NSTimer * _Nonnull timer) {
|
||||
__strong typeof(weakSelf) self = weakSelf; if (!self) return;
|
||||
UIPasteboard *pb = [UIPasteboard generalPasteboard];
|
||||
|
||||
@@ -338,7 +338,6 @@
|
||||
if (before.length <= 0) { self.backspaceHoldActive = NO; return; }
|
||||
[proxy deleteBackward]; // 每次仅删 1 个
|
||||
|
||||
// 轻量递归调度下一次;不使用 NSTimer,避免复杂状态管理
|
||||
__weak typeof(self) weakSelf = self;
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.06 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
__strong typeof(weakSelf) selfStrong = weakSelf;
|
||||
|
||||
@@ -75,6 +75,8 @@
|
||||
048909F72EC0AAAA00FABA60 /* KBCategoryTitleView.m in Sources */ = {isa = PBXBuildFile; fileRef = 048909F32EC0AAAA00FABA60 /* KBCategoryTitleView.m */; };
|
||||
04890A042EC0BBBB00FABA60 /* KBCategoryTitleImageCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 04890A012EC0BBBB00FABA60 /* KBCategoryTitleImageCell.m */; };
|
||||
04890A052EC0BBBB00FABA60 /* KBCategoryTitleImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04890A032EC0BBBB00FABA60 /* KBCategoryTitleImageView.m */; };
|
||||
049FB20B2EC1C13800FAB05D /* KBSkinBottomActionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 049FB20A2EC1C13800FAB05D /* KBSkinBottomActionView.m */; };
|
||||
049FB20E2EC1CD2800FAB05D /* KBAlert.m in Sources */ = {isa = PBXBuildFile; fileRef = 049FB20D2EC1CD2800FAB05D /* KBAlert.m */; };
|
||||
04A9FE0F2EB481100020DB6D /* KBHUD.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC97082EB31B14007BD342 /* KBHUD.m */; };
|
||||
04A9FE132EB4D0D20020DB6D /* KBFullAccessManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 04A9FE112EB4D0D20020DB6D /* KBFullAccessManager.m */; };
|
||||
04A9FE162EB873C80020DB6D /* UIViewController+Extension.m in Sources */ = {isa = PBXBuildFile; fileRef = 04A9FE152EB873C80020DB6D /* UIViewController+Extension.m */; };
|
||||
@@ -263,6 +265,10 @@
|
||||
04890A012EC0BBBB00FABA60 /* KBCategoryTitleImageCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBCategoryTitleImageCell.m; sourceTree = "<group>"; };
|
||||
04890A022EC0BBBB00FABA60 /* KBCategoryTitleImageView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBCategoryTitleImageView.h; sourceTree = "<group>"; };
|
||||
04890A032EC0BBBB00FABA60 /* KBCategoryTitleImageView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBCategoryTitleImageView.m; sourceTree = "<group>"; };
|
||||
049FB2092EC1C13800FAB05D /* KBSkinBottomActionView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBSkinBottomActionView.h; sourceTree = "<group>"; };
|
||||
049FB20A2EC1C13800FAB05D /* KBSkinBottomActionView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBSkinBottomActionView.m; sourceTree = "<group>"; };
|
||||
049FB20C2EC1CD2800FAB05D /* KBAlert.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBAlert.h; sourceTree = "<group>"; };
|
||||
049FB20D2EC1CD2800FAB05D /* KBAlert.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBAlert.m; sourceTree = "<group>"; };
|
||||
04A9A67D2EB9E1690023B8F4 /* KBResponderUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBResponderUtils.h; sourceTree = "<group>"; };
|
||||
04A9FE102EB4D0D20020DB6D /* KBFullAccessManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBFullAccessManager.h; sourceTree = "<group>"; };
|
||||
04A9FE112EB4D0D20020DB6D /* KBFullAccessManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBFullAccessManager.m; sourceTree = "<group>"; };
|
||||
@@ -541,6 +547,10 @@
|
||||
048908B82EBDC11200FABA60 /* V */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
04FC97072EB31B14007BD342 /* KBHUD.h */,
|
||||
04FC97082EB31B14007BD342 /* KBHUD.m */,
|
||||
049FB20C2EC1CD2800FAB05D /* KBAlert.h */,
|
||||
049FB20D2EC1CD2800FAB05D /* KBAlert.m */,
|
||||
);
|
||||
path = V;
|
||||
sourceTree = "<group>";
|
||||
@@ -823,6 +833,8 @@
|
||||
048908EB2EBF849300FABA60 /* KBSkinTagsContainerCell.m */,
|
||||
048908ED2EBF861800FABA60 /* KBSkinSectionTitleCell.h */,
|
||||
048908EE2EBF861800FABA60 /* KBSkinSectionTitleCell.m */,
|
||||
049FB2092EC1C13800FAB05D /* KBSkinBottomActionView.h */,
|
||||
049FB20A2EC1C13800FAB05D /* KBSkinBottomActionView.m */,
|
||||
);
|
||||
path = V;
|
||||
sourceTree = "<group>";
|
||||
@@ -963,8 +975,6 @@
|
||||
04FC970B2EB334F8007BD342 /* UIImageView+KBWebImage.m */,
|
||||
04FC970C2EB334F8007BD342 /* KBWebImageManager.h */,
|
||||
04FC970D2EB334F8007BD342 /* KBWebImageManager.m */,
|
||||
04FC97072EB31B14007BD342 /* KBHUD.h */,
|
||||
04FC97082EB31B14007BD342 /* KBHUD.m */,
|
||||
04A9FE142EB873C80020DB6D /* UIViewController+Extension.h */,
|
||||
04A9FE152EB873C80020DB6D /* UIViewController+Extension.m */,
|
||||
047C655A2EBCD08E0035E841 /* UIView+KBShadow.h */,
|
||||
@@ -1333,6 +1343,7 @@
|
||||
0477BE002EBC6A330055D639 /* HomeRankVC.m in Sources */,
|
||||
047C650D2EBC8A840035E841 /* KBPanModalView.m in Sources */,
|
||||
043FBCD22EAF97630036AFE1 /* KBPermissionViewController.m in Sources */,
|
||||
049FB20E2EC1CD2800FAB05D /* KBAlert.m in Sources */,
|
||||
04A9FE162EB873C80020DB6D /* UIViewController+Extension.m in Sources */,
|
||||
04C6EABE2EAF86530089C901 /* AppDelegate.m in Sources */,
|
||||
04FC95F12EB339A7007BD342 /* LoginViewController.m in Sources */,
|
||||
@@ -1352,6 +1363,7 @@
|
||||
047C65102EBCA8DD0035E841 /* HomeRankContentVC.m in Sources */,
|
||||
047C655C2EBCD0F80035E841 /* UIView+KBShadow.m in Sources */,
|
||||
048908C32EBE32B800FABA60 /* KBSearchVC.m in Sources */,
|
||||
049FB20B2EC1C13800FAB05D /* KBSkinBottomActionView.m in Sources */,
|
||||
047C655E2EBCD5B20035E841 /* UIImage+KBColor.m in Sources */,
|
||||
04FC95DD2EB202A3007BD342 /* KBGuideVC.m in Sources */,
|
||||
04FC95E52EB220B5007BD342 /* UIColor+Extension.m in Sources */,
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
// keyBoard
|
||||
//
|
||||
// 统一封装基于 LYEmptyView 的空态视图挂载方法,适用于 UITableView/UICollectionView。
|
||||
// 注意:仅在对应页面已通过 CocoaPods 集成 LYEmptyView 时生效。
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@@ -34,13 +34,25 @@ static inline __kindof UIWindow *KBActiveWindow(void) {
|
||||
|
||||
+ (UIViewController *)kb_topMostViewController {
|
||||
UIWindow *window = KBActiveWindow();
|
||||
UIViewController *root = window.rootViewController;
|
||||
if (!root) return nil;
|
||||
UIViewController *top = root;
|
||||
while (top.presentedViewController) {
|
||||
top = top.presentedViewController;
|
||||
UIViewController *vc = window.rootViewController;
|
||||
if (!vc) return nil;
|
||||
// 展开容器控制器
|
||||
while (1) {
|
||||
if ([vc isKindOfClass:UINavigationController.class]) {
|
||||
vc = ((UINavigationController *)vc).visibleViewController ?: vc;
|
||||
continue;
|
||||
}
|
||||
if ([vc isKindOfClass:UITabBarController.class]) {
|
||||
vc = ((UITabBarController *)vc).selectedViewController ?: vc;
|
||||
continue;
|
||||
}
|
||||
if (vc.presentedViewController) {
|
||||
vc = vc.presentedViewController;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return top;
|
||||
return vc;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
83
keyBoard/Class/Common/V/KBAlert.h
Normal file
83
keyBoard/Class/Common/V/KBAlert.h
Normal file
@@ -0,0 +1,83 @@
|
||||
//
|
||||
// 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;
|
||||
|
||||
/// 自定义“确定/取消”按钮颜色(为 nil 时使用系统默认)
|
||||
+ (void)confirmTitle:(nullable NSString *)title
|
||||
message:(nullable NSString *)message
|
||||
ok:(nullable NSString *)okTitle
|
||||
cancel:(nullable NSString *)cancelTitle
|
||||
okColor:(nullable UIColor *)okColor
|
||||
cancelColor:(nullable UIColor *)cancelColor
|
||||
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
|
||||
243
keyBoard/Class/Common/V/KBAlert.m
Normal file
243
keyBoard/Class/Common/V/KBAlert.m
Normal file
@@ -0,0 +1,243 @@
|
||||
//
|
||||
// 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 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 {
|
||||
[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];
|
||||
}];
|
||||
// 自定义颜色
|
||||
KBSetActionTitleColor(okAct, okColor);
|
||||
KBSetActionTitleColor(cancelAct, cancelColor);
|
||||
|
||||
[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
|
||||
@@ -81,9 +81,9 @@
|
||||
|
||||
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
[tableView deselectRowAtIndexPath:indexPath animated:YES];
|
||||
// KBSearchVC *vc = [[KBSearchVC alloc] init];
|
||||
KBSearchVC *vc = [[KBSearchVC alloc] init];
|
||||
// [self.navigationController pushViewController:vc animated:true];
|
||||
// UINavigationController *nav = KB_CURRENT_NAV;
|
||||
UINavigationController *nav = KB_CURRENT_NAV;
|
||||
// [nav pushViewController:vc animated:true];
|
||||
NSLog(@"===");
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
desc:d[@"desc"]
|
||||
people:d[@"people"]
|
||||
added:added];
|
||||
__weak typeof(self) weakSelf = self;
|
||||
KBWeakSelf
|
||||
cell.onTapAction = ^{
|
||||
// 切换添加/已添加状态并刷新该项
|
||||
NSMutableArray *m = [weakSelf.dataSource mutableCopy];
|
||||
|
||||
@@ -132,7 +132,7 @@
|
||||
|
||||
- (void)onContinue {
|
||||
LoginViewController *login = [LoginViewController new];
|
||||
__weak typeof(self) weakSelf = self;
|
||||
KBWeakSelf
|
||||
login.onLoginSuccess = ^(NSDictionary * _Nonnull userInfo) {
|
||||
__strong typeof(weakSelf) self = weakSelf; if (!self) return;
|
||||
[self dismissViewControllerAnimated:YES completion:^{
|
||||
|
||||
@@ -114,7 +114,7 @@
|
||||
}
|
||||
|
||||
- (void)handleAppleIDButtonPress API_AVAILABLE(ios(13.0)) {
|
||||
__weak typeof(self) weakSelf = self;
|
||||
KBWeakSelf
|
||||
[[AppleSignInManager shared] signInFromViewController:self completion:^(ASAuthorizationAppleIDCredential * _Nullable credential, NSError * _Nullable error) {
|
||||
__strong typeof(weakSelf) selfStrong = weakSelf;
|
||||
if (error) {
|
||||
|
||||
37
keyBoard/Class/Me/V/KBSkinBottomActionView.h
Normal file
37
keyBoard/Class/Me/V/KBSkinBottomActionView.h
Normal file
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// KBSkinBottomActionView.h
|
||||
// keyBoard
|
||||
//
|
||||
// 底部操作条(圆角胶囊样式),支持点击。用于皮肤详情页下载/购买。
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
/// 底部操作条(45 高,左右圆角)。
|
||||
/// 结构: [ Title [coinIcon] [price] ] 居中对齐
|
||||
@interface KBSkinBottomActionView : UIControl
|
||||
|
||||
/// 标题,例如:@"Download"
|
||||
@property (nonatomic, copy) NSString *titleText;
|
||||
|
||||
/// 价格/金币数,例如:@"20"
|
||||
@property (nonatomic, copy) NSString *priceText;
|
||||
|
||||
/// 图标(可选),例如金币图
|
||||
@property (nonatomic, strong, nullable) UIImage *iconImage;
|
||||
|
||||
/// 点击回调(也可直接 addTarget 使用)
|
||||
@property (nonatomic, copy, nullable) void (^tapHandler)(void);
|
||||
|
||||
/// 配置便捷方法
|
||||
- (void)configWithTitle:(nullable NSString *)title price:(nullable NSString *)price icon:(nullable UIImage *)icon;
|
||||
|
||||
/// 建议固定高度
|
||||
+ (CGFloat)preferredHeight;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
153
keyBoard/Class/Me/V/KBSkinBottomActionView.m
Normal file
153
keyBoard/Class/Me/V/KBSkinBottomActionView.m
Normal file
@@ -0,0 +1,153 @@
|
||||
//
|
||||
// KBSkinBottomActionView.m
|
||||
// keyBoard
|
||||
//
|
||||
//
|
||||
|
||||
#import "KBSkinBottomActionView.h"
|
||||
|
||||
@interface KBSkinBottomActionView ()
|
||||
@property (nonatomic, strong) UIView *contentView; // 内部容器,使三项整体居中
|
||||
@property (nonatomic, strong) UILabel *titleLabel; // 左侧标题
|
||||
@property (nonatomic, strong) UIImageView *coinImageView; // 中间图标(可选)
|
||||
@property (nonatomic, strong) UILabel *priceLabel; // 右侧价格
|
||||
@end
|
||||
|
||||
@implementation KBSkinBottomActionView
|
||||
|
||||
+ (CGFloat)preferredHeight { return 45.0; }
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
self.backgroundColor = [UIColor colorWithHex:0x02BEAC];
|
||||
self.layer.masksToBounds = YES; // 圆角生效
|
||||
|
||||
// 高亮态轻微透明,突出点击感
|
||||
[self addTarget:self action:@selector(onTouchDown) forControlEvents:UIControlEventTouchDown];
|
||||
[self addTarget:self action:@selector(onTouchUp) forControlEvents:UIControlEventTouchUpInside | UIControlEventTouchUpOutside | UIControlEventTouchCancel];
|
||||
|
||||
// 内部容器,承载三个元素,容器整体水平/垂直居中
|
||||
[self addSubview:self.contentView];
|
||||
[self.contentView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.centerX.equalTo(self);
|
||||
make.centerY.equalTo(self);
|
||||
// 防止文本过长时超出左右边界
|
||||
make.left.greaterThanOrEqualTo(self).offset(16);
|
||||
make.right.lessThanOrEqualTo(self).offset(-16);
|
||||
}];
|
||||
|
||||
// 三个元素放进容器,左右顺序:Title - Icon - Price
|
||||
[self.contentView addSubview:self.titleLabel];
|
||||
[self.contentView addSubview:self.coinImageView];
|
||||
[self.contentView addSubview:self.priceLabel];
|
||||
|
||||
[self.titleLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.contentView);
|
||||
make.centerY.equalTo(self.contentView);
|
||||
}];
|
||||
[self.coinImageView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.titleLabel.mas_right).offset(8);
|
||||
make.centerY.equalTo(self.contentView);
|
||||
make.width.height.mas_equalTo(18);
|
||||
}];
|
||||
[self.priceLabel mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.coinImageView.mas_right).offset(6);
|
||||
make.right.equalTo(self.contentView);
|
||||
make.centerY.equalTo(self.contentView);
|
||||
}];
|
||||
|
||||
// 默认文案
|
||||
self.titleText = @"Download";
|
||||
self.priceText = @"20";
|
||||
UIImage *img = [UIImage systemImageNamed:@"circle.fill"];
|
||||
self.iconImage = img; // 若项目没有金币图标,用系统占位(黄色)
|
||||
self.coinImageView.tintColor = [UIColor colorWithRed:1.0 green:0.85 blue:0.2 alpha:1.0];
|
||||
// 点击回调(可选)
|
||||
[self addTarget:self action:@selector(handleTap) forControlEvents:UIControlEventTouchUpInside];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
// 圆角随高度自适应
|
||||
self.layer.cornerRadius = CGRectGetHeight(self.bounds) * 0.5;
|
||||
}
|
||||
|
||||
#pragma mark - Public
|
||||
|
||||
- (void)configWithTitle:(nullable NSString *)title price:(nullable NSString *)price icon:(nullable UIImage *)icon {
|
||||
if (title.length) self.titleText = title;
|
||||
if (price.length) self.priceText = price;
|
||||
if (icon) self.iconImage = icon;
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
|
||||
- (void)onTouchDown { self.alpha = 0.85; }
|
||||
- (void)onTouchUp { self.alpha = 1.0; }
|
||||
|
||||
- (void)handleTap {
|
||||
if (self.tapHandler) { self.tapHandler(); }
|
||||
}
|
||||
|
||||
#pragma mark - Setters
|
||||
|
||||
- (void)setTitleText:(NSString *)titleText {
|
||||
_titleText = [titleText copy];
|
||||
self.titleLabel.text = _titleText;
|
||||
}
|
||||
|
||||
- (void)setPriceText:(NSString *)priceText {
|
||||
_priceText = [priceText copy];
|
||||
self.priceLabel.text = _priceText;
|
||||
}
|
||||
|
||||
- (void)setIconImage:(UIImage *)iconImage {
|
||||
_iconImage = iconImage;
|
||||
self.coinImageView.image = _iconImage;
|
||||
}
|
||||
|
||||
#pragma mark - Lazy
|
||||
|
||||
- (UILabel *)titleLabel {
|
||||
if (!_titleLabel) {
|
||||
_titleLabel = [[UILabel alloc] init];
|
||||
_titleLabel.font = [UIFont systemFontOfSize:17 weight:UIFontWeightSemibold];
|
||||
_titleLabel.textColor = [UIColor whiteColor];
|
||||
_titleLabel.textAlignment = NSTextAlignmentCenter;
|
||||
}
|
||||
return _titleLabel;
|
||||
}
|
||||
|
||||
- (UIView *)contentView {
|
||||
if (!_contentView) {
|
||||
_contentView = [[UIView alloc] init];
|
||||
_contentView.backgroundColor = [UIColor clearColor];
|
||||
// 让容器尽量包裹内容
|
||||
[_contentView setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
|
||||
[_contentView setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
|
||||
}
|
||||
return _contentView;
|
||||
}
|
||||
|
||||
- (UIImageView *)coinImageView {
|
||||
if (!_coinImageView) {
|
||||
_coinImageView = [[UIImageView alloc] init];
|
||||
_coinImageView.contentMode = UIViewContentModeScaleAspectFit;
|
||||
_coinImageView.clipsToBounds = YES;
|
||||
}
|
||||
return _coinImageView;
|
||||
}
|
||||
|
||||
- (UILabel *)priceLabel {
|
||||
if (!_priceLabel) {
|
||||
_priceLabel = [[UILabel alloc] init];
|
||||
_priceLabel.font = [UIFont systemFontOfSize:17 weight:UIFontWeightSemibold];
|
||||
_priceLabel.textColor = [UIColor whiteColor];
|
||||
_priceLabel.textAlignment = NSTextAlignmentCenter;
|
||||
}
|
||||
return _priceLabel;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -8,8 +8,9 @@
|
||||
#import "UICollectionViewLeftAlignedLayout.h"
|
||||
#import "KBSkinDetailHeaderCell.h"
|
||||
#import "KBSkinTagsContainerCell.h"
|
||||
#import "KBSkinCardCell.h" // 已有的 皮肤卡片 cell(用作 2 列网格)
|
||||
#import "KBSkinCardCell.h"
|
||||
#import "KBSkinSectionTitleCell.h"
|
||||
#import "KBSkinBottomActionView.h"
|
||||
|
||||
static NSString * const kHeaderCellId = @"kHeaderCellId";
|
||||
static NSString * const kTagsContainerCellId = @"kTagsContainerCellId";
|
||||
@@ -26,6 +27,7 @@ typedef NS_ENUM(NSInteger, KBSkinDetailSection) {
|
||||
|
||||
@interface KBSkinDetailVC () <UICollectionViewDataSource, UICollectionViewDelegateFlowLayout>
|
||||
@property (nonatomic, strong) UICollectionView *collectionView; // 主列表
|
||||
@property (nonatomic, strong) KBSkinBottomActionView *bottomBar; // 底部操作条
|
||||
@property (nonatomic, copy) NSArray<NSString *> *tags; // 标签数据
|
||||
@property (nonatomic, copy) NSArray<NSDictionary *> *gridData; // 底部网格数据
|
||||
@end
|
||||
@@ -44,9 +46,26 @@ typedef NS_ENUM(NSInteger, KBSkinDetailSection) {
|
||||
@{ @"title": @"Dopamine" }, @{ @"title": @"Dopamine" },
|
||||
];
|
||||
|
||||
// 1. 列表
|
||||
[self.view addSubview:self.collectionView];
|
||||
|
||||
// 2. 底部操作条(左右 15,高 45,可点击)
|
||||
[self.view addSubview:self.bottomBar];
|
||||
[self.bottomBar mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.left.equalTo(self.view).offset(15);
|
||||
make.right.equalTo(self.view).offset(-15);
|
||||
make.height.mas_equalTo([KBSkinBottomActionView preferredHeight]);
|
||||
if (@available(iOS 11.0, *)) {
|
||||
make.bottom.equalTo(self.view.mas_safeAreaLayoutGuideBottom);
|
||||
} else {
|
||||
make.bottom.equalTo(self.view);
|
||||
}
|
||||
}];
|
||||
|
||||
// 列表底部距离操作条顶部 10
|
||||
[self.collectionView mas_makeConstraints:^(MASConstraintMaker *make) {
|
||||
make.edges.equalTo(self.view);
|
||||
make.top.left.right.equalTo(self.view);
|
||||
make.bottom.equalTo(self.bottomBar.mas_top).offset(-10);
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -156,4 +175,27 @@ typedef NS_ENUM(NSInteger, KBSkinDetailSection) {
|
||||
return _collectionView;
|
||||
}
|
||||
|
||||
#pragma mark - Lazy (Bottom Bar)
|
||||
|
||||
- (KBSkinBottomActionView *)bottomBar {
|
||||
if (!_bottomBar) {
|
||||
_bottomBar = [[KBSkinBottomActionView alloc] init];
|
||||
// 中文注释:配置文案与图标,可根据业务传入金币图/价格
|
||||
[_bottomBar configWithTitle:@"Download" price:@"20" icon:nil];
|
||||
KBWeakSelf
|
||||
_bottomBar.tapHandler = ^{
|
||||
// 示例:点击下载/购买
|
||||
// [KBHUD showText:@"点击了下载"];
|
||||
// TODO: 在此处触发下载/购买逻辑
|
||||
[weakSelf handleDownloadAction];
|
||||
};
|
||||
}
|
||||
return _bottomBar;
|
||||
}
|
||||
|
||||
#pragma mark - Actions
|
||||
- (void)handleDownloadAction {
|
||||
// 预留:下载/购买动作
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -55,7 +55,7 @@ static NSString * const kMySkinCellId = @"kMySkinCellId";
|
||||
}];
|
||||
|
||||
// 空态视图(LYEmptyView)统一样式 + 重试按钮
|
||||
__weak typeof(self) weakSelf = self;
|
||||
KBWeakSelf
|
||||
[self.collectionView kb_makeDefaultEmptyViewWithImage:nil
|
||||
title:@"暂无皮肤"
|
||||
detail:@"下拉刷新试试"
|
||||
|
||||
@@ -29,10 +29,10 @@
|
||||
}
|
||||
|
||||
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
|
||||
MySkinVC *vc = [[MySkinVC alloc] init];
|
||||
// KBSkinDetailVC *vc = [[KBSkinDetailVC alloc] init];
|
||||
// MySkinVC *vc = [[MySkinVC alloc] init];
|
||||
KBSkinDetailVC *vc = [[KBSkinDetailVC alloc] init];
|
||||
|
||||
[self.navigationController pushViewController:vc animated:true];
|
||||
// [self.navigationController pushViewController:vc animated:true];
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -20,10 +20,14 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
/// 配置标题与是否显示垃圾桶
|
||||
- (void)configWithTitle:(NSString *)title showTrash:(BOOL)showTrash;
|
||||
|
||||
/// 清除按钮点击回调
|
||||
/// 清除按钮点击回调(用户在弹窗中点击“确定”后触发)
|
||||
@property (nonatomic, copy) void(^onTapTrash)(void);
|
||||
|
||||
/// “确定”按钮颜色(默认黑色)
|
||||
@property (nonatomic, strong, nullable) UIColor *confirmColor;
|
||||
/// “取消”按钮颜色(默认黑色)
|
||||
@property (nonatomic, strong, nullable) UIColor *cancelColor;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
//
|
||||
|
||||
#import "KBSearchSectionHeader.h"
|
||||
#import "KBAlert.h"
|
||||
|
||||
@interface KBSearchSectionHeader ()
|
||||
@property (nonatomic, strong, readwrite) UILabel *titleLabel;
|
||||
@@ -15,6 +16,8 @@
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
[self setupUI];
|
||||
_confirmColor = [UIColor redColor];
|
||||
_cancelColor = [UIColor blackColor];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -39,7 +42,19 @@
|
||||
}
|
||||
|
||||
- (void)tapTrash {
|
||||
if (self.onTapTrash) { self.onTapTrash(); }
|
||||
// 弹出确认框:是否删除所有历史记录
|
||||
__weak typeof(self) weakSelf = self;
|
||||
[KBAlert confirmTitle:@"清空历史"
|
||||
message:@"是否删除所有历史记录?"
|
||||
ok:@"确定"
|
||||
cancel:@"取消"
|
||||
okColor:weakSelf.confirmColor
|
||||
cancelColor:weakSelf.cancelColor
|
||||
completion:^(BOOL ok) {
|
||||
if (ok && weakSelf.onTapTrash) {
|
||||
weakSelf.onTapTrash();
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)configWithTitle:(NSString *)title showTrash:(BOOL)showTrash {
|
||||
@@ -69,4 +84,3 @@
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -152,7 +152,7 @@ static NSString * const kResultCellId = @"KBSkinCardCell";
|
||||
if (!_searchBarView) {
|
||||
_searchBarView = [[KBSearchBarView alloc] init];
|
||||
_searchBarView.placeholder = @"Themes";
|
||||
__weak typeof(self) weakSelf = self;
|
||||
KBWeakSelf
|
||||
_searchBarView.onSearch = ^(NSString * _Nonnull keyword) {
|
||||
[weakSelf performSearch:keyword];
|
||||
};
|
||||
|
||||
@@ -285,7 +285,7 @@ typedef NS_ENUM(NSInteger, KBSearchSection) {
|
||||
if (indexPath.section == KBSearchSectionHistory) {
|
||||
// 当没有历史时,外部通过 sizeForHeader 返回 0,这里仍设置但不会显示
|
||||
[header configWithTitle:@"Historical Search" showTrash:self.historyWords.count > 0];
|
||||
__weak typeof(self) weakSelf = self;
|
||||
KBWeakSelf
|
||||
header.onTapTrash = ^{ [weakSelf clearHistory]; };
|
||||
} else {
|
||||
[header configWithTitle:@"Recommended Skin" showTrash:NO];
|
||||
@@ -363,7 +363,7 @@ typedef NS_ENUM(NSInteger, KBSearchSection) {
|
||||
if (!_searchBarView) {
|
||||
_searchBarView = [[KBSearchBarView alloc] init];
|
||||
_searchBarView.placeholder = @"Themes";
|
||||
__weak typeof(self) weakSelf = self;
|
||||
KBWeakSelf
|
||||
_searchBarView.onSearch = ^(NSString * _Nonnull keyword) {
|
||||
// 置顶到历史 + 打开结果页
|
||||
[weakSelf performSearch:keyword];
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
}];
|
||||
|
||||
// 刷新组件(演示:2 秒后结束)
|
||||
__weak typeof(self) weakSelf = self;
|
||||
KBWeakSelf
|
||||
if (self.isNeedHeader) {
|
||||
self.collectionView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
/// 系统
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "KBHUD.h"
|
||||
#import "KBAlert.h"
|
||||
#import "KBNetworkManager.h"
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user