diff --git a/CustomKeyboard/Network/KBNetworkManager.m b/CustomKeyboard/Network/KBNetworkManager.m index 824109b..73b0e05 100644 --- a/CustomKeyboard/Network/KBNetworkManager.m +++ b/CustomKeyboard/Network/KBNetworkManager.m @@ -26,7 +26,8 @@ NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network"; if (self = [super init]) { _enabled = NO; // 键盘扩展默认无网络能力,需外部显式开启 _timeout = 10.0; - _defaultHeaders = @{ @"Accept": @"application/json" }; + // 默认接受任意类型,避免下载图片/二进制被服务端基于 Accept 拒绝 + _defaultHeaders = @{ @"Accept": @"*/*" }; // 设置基础域名,路径可相对该地址拼接 _baseURL = [NSURL URLWithString:KB_BASE_URL]; } @@ -45,10 +46,15 @@ NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network"; // 使用 AFHTTPRequestSerializer 生成带参数与头的请求 AFHTTPRequestSerializer *serializer = [AFHTTPRequestSerializer serializer]; serializer.timeoutInterval = self.timeout; + NSError *serror = nil; NSMutableURLRequest *req = [serializer requestWithMethod:@"GET" URLString:urlString parameters:parameters - error:NULL]; + error:&serror]; + if (serror || !req) { + if (completion) completion(nil, nil, serror ?: [NSError errorWithDomain:KBNetworkErrorDomain code:KBNetworkErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey: @"无效的URL"}]); + return nil; + } [self applyHeaders:headers toMutableRequest:req contentType:nil]; return [self startAFTaskWithRequest:req completion:completion]; } @@ -109,7 +115,11 @@ NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network"; // 响应先用原始数据返回,再按 Content-Type 解析 JSON(与原实现一致) self.manager.responseSerializer = [AFHTTPResponseSerializer serializer]; NSURLSessionDataTask *task = [self.manager dataTaskWithRequest:req uploadProgress:nil downloadProgress:nil completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) { - if (error) { if (completion) completion(nil, response, error); return; } + // AFN 默认对非 2xx 的状态码返回 error;这里直接回调上层 + if (error) { + if (completion) completion(nil, response, error); + return; + } NSData *data = (NSData *)responseObject; if (![data isKindOfClass:[NSData class]]) { if (completion) completion(nil, response, [NSError errorWithDomain:KBNetworkErrorDomain code:KBNetworkErrorInvalidResponse userInfo:@{NSLocalizedDescriptionKey:@"无数据"}]); @@ -119,7 +129,19 @@ NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network"; if ([response isKindOfClass:[NSHTTPURLResponse class]]) { ct = ((NSHTTPURLResponse *)response).allHeaderFields[@"Content-Type"]; } - BOOL looksJSON = (ct && [ct.lowercaseString containsString:@"application/json"]); + // 更宽松的 JSON 判定:Content-Type 里包含 json;或首字符是 { / [ + BOOL looksJSON = (ct && [[ct lowercaseString] containsString:@"json"]); + if (!looksJSON) { + // 内容嗅探(不依赖服务端声明) + const unsigned char *bytes = data.bytes; + NSUInteger len = data.length; + for (NSUInteger i = 0; !looksJSON && i < len; i++) { + unsigned char c = bytes[i]; + if (c == ' ' || c == '\n' || c == '\r' || c == '\t') continue; + looksJSON = (c == '{' || c == '['); + break; + } + } if (looksJSON) { NSError *jsonErr = nil; id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonErr]; @@ -139,8 +161,7 @@ NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network"; if (!_manager) { NSURLSessionConfiguration *cfg = [NSURLSessionConfiguration ephemeralSessionConfiguration]; cfg.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData; - cfg.timeoutIntervalForRequest = self.timeout; - cfg.timeoutIntervalForResource = MAX(self.timeout, 30.0); + // 不在会话级别设置超时,避免与 per-request 的 serializer.timeoutInterval 产生不一致 if (@available(iOS 11.0, *)) { cfg.waitsForConnectivity = YES; } _manager = [[AFHTTPSessionManager alloc] initWithBaseURL:nil sessionConfiguration:cfg]; // 默认不使用 JSON 解析器,保持原生数据,再按需解析 diff --git a/keyBoard/Assets.xcassets/Shop/recharge_btn_bg.imageset/Contents.json b/keyBoard/Assets.xcassets/Shop/recharge_btn_bg.imageset/Contents.json new file mode 100644 index 0000000..10f8506 --- /dev/null +++ b/keyBoard/Assets.xcassets/Shop/recharge_btn_bg.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "recharge_btn_bg@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "recharge_btn_bg@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/keyBoard/Assets.xcassets/Shop/recharge_btn_bg.imageset/recharge_btn_bg@2x.png b/keyBoard/Assets.xcassets/Shop/recharge_btn_bg.imageset/recharge_btn_bg@2x.png new file mode 100644 index 0000000..b597c29 Binary files /dev/null and b/keyBoard/Assets.xcassets/Shop/recharge_btn_bg.imageset/recharge_btn_bg@2x.png differ diff --git a/keyBoard/Assets.xcassets/Shop/recharge_btn_bg.imageset/recharge_btn_bg@3x.png b/keyBoard/Assets.xcassets/Shop/recharge_btn_bg.imageset/recharge_btn_bg@3x.png new file mode 100644 index 0000000..f3fbaf4 Binary files /dev/null and b/keyBoard/Assets.xcassets/Shop/recharge_btn_bg.imageset/recharge_btn_bg@3x.png differ diff --git a/keyBoard/Class/Network/KBNetworkManager.m b/keyBoard/Class/Network/KBNetworkManager.m index 824109b..adea2dc 100644 --- a/keyBoard/Class/Network/KBNetworkManager.m +++ b/keyBoard/Class/Network/KBNetworkManager.m @@ -15,6 +15,16 @@ NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network"; - (void)fail:(KBNetworkError)code completion:(KBNetworkCompletion)completion; @end +#if DEBUG +// 在使用到 Debug 辅助方法之前做前置声明,避免出现 +// “No visible @interface declares the selector …” 的编译错误。 +@interface KBNetworkManager (Debug) +- (NSString *)kb_prettyJSONStringFromObject:(id)obj; +- (NSString *)kb_textFromData:(NSData *)data; +- (NSString *)kb_trimmedString:(NSString *)s maxLength:(NSUInteger)maxLen; +@end +#endif + @implementation KBNetworkManager + (instancetype)shared { @@ -26,7 +36,8 @@ NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network"; if (self = [super init]) { _enabled = NO; // 键盘扩展默认无网络能力,需外部显式开启 _timeout = 10.0; - _defaultHeaders = @{ @"Accept": @"application/json" }; + // 默认接受任意类型,避免下载图片/二进制被服务端基于 Accept 拒绝 + _defaultHeaders = @{ @"Accept": @"*/*" }; // 设置基础域名,路径可相对该地址拼接 _baseURL = [NSURL URLWithString:KB_BASE_URL]; } @@ -45,11 +56,24 @@ NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network"; // 使用 AFHTTPRequestSerializer 生成带参数与头的请求 AFHTTPRequestSerializer *serializer = [AFHTTPRequestSerializer serializer]; serializer.timeoutInterval = self.timeout; + NSError *serror = nil; NSMutableURLRequest *req = [serializer requestWithMethod:@"GET" URLString:urlString parameters:parameters - error:NULL]; + error:&serror]; + if (serror || !req) { + if (completion) completion(nil, nil, serror ?: [NSError errorWithDomain:KBNetworkErrorDomain code:KBNetworkErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey: @"无效的URL"}]); + return nil; + } [self applyHeaders:headers toMutableRequest:req contentType:nil]; +#if DEBUG + // 打印请求信息(GET) + NSString *paramStr = [self kb_prettyJSONStringFromObject:parameters] ?: @"(null)"; + KBLOG(@"HTTP GET\nURL: %@\nHeaders: %@\n参数: %@", + req.URL.absoluteString, + req.allHTTPHeaderFields ?: @{}, + paramStr); +#endif return [self startAFTaskWithRequest:req completion:completion]; } @@ -70,6 +94,14 @@ NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network"; error:&error]; if (error) { if (completion) completion(nil, nil, error); return nil; } [self applyHeaders:headers toMutableRequest:req contentType:nil]; +#if DEBUG + // 打印请求信息(POST JSON) + NSString *bodyStr = [self kb_prettyJSONStringFromObject:jsonBody] ?: @"(null)"; + KBLOG(@"HTTP POST\nURL: %@\nHeaders: %@\nJSON: %@", + req.URL.absoluteString, + req.allHTTPHeaderFields ?: @{}, + bodyStr); +#endif return [self startAFTaskWithRequest:req completion:completion]; } @@ -109,9 +141,24 @@ NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network"; // 响应先用原始数据返回,再按 Content-Type 解析 JSON(与原实现一致) self.manager.responseSerializer = [AFHTTPResponseSerializer serializer]; NSURLSessionDataTask *task = [self.manager dataTaskWithRequest:req uploadProgress:nil downloadProgress:nil completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) { - if (error) { if (completion) completion(nil, response, error); return; } + // AFN 默认对非 2xx 的状态码返回 error;这里先日志,再直接回调上层 + if (error) { +#if DEBUG + NSInteger status = [response isKindOfClass:NSHTTPURLResponse.class] ? ((NSHTTPURLResponse *)response).statusCode : 0; + KBLOG(@"请求失败\nURL: %@\n状态: %ld\n错误: %@\nUserInfo: %@", + req.URL.absoluteString, + (long)status, + error.localizedDescription, + error.userInfo ?: @{}); +#endif + if (completion) completion(nil, response, error); + return; + } NSData *data = (NSData *)responseObject; if (![data isKindOfClass:[NSData class]]) { +#if DEBUG + KBLOG(@"无效响应\nURL: %@\n说明: %@", req.URL.absoluteString, @"未获取到数据"); +#endif if (completion) completion(nil, response, [NSError errorWithDomain:KBNetworkErrorDomain code:KBNetworkErrorInvalidResponse userInfo:@{NSLocalizedDescriptionKey:@"无数据"}]); return; } @@ -119,13 +166,53 @@ NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network"; if ([response isKindOfClass:[NSHTTPURLResponse class]]) { ct = ((NSHTTPURLResponse *)response).allHeaderFields[@"Content-Type"]; } - BOOL looksJSON = (ct && [ct.lowercaseString containsString:@"application/json"]); + // 更宽松的 JSON 判定:Content-Type 里包含 json;或首字符是 { / [ + BOOL looksJSON = (ct && [[ct lowercaseString] containsString:@"json"]); + if (!looksJSON) { + // 内容嗅探(不依赖服务端声明) + const unsigned char *bytes = data.bytes; + NSUInteger len = data.length; + for (NSUInteger i = 0; !looksJSON && i < len; i++) { + unsigned char c = bytes[i]; + if (c == ' ' || c == '\n' || c == '\r' || c == '\t') continue; + looksJSON = (c == '{' || c == '['); + break; + } + } if (looksJSON) { NSError *jsonErr = nil; id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonErr]; - if (jsonErr) { if (completion) completion(nil, response, [NSError errorWithDomain:KBNetworkErrorDomain code:KBNetworkErrorDecodeFailed userInfo:@{NSLocalizedDescriptionKey:@"JSON解析失败"}]); return; } + if (jsonErr) { +#if DEBUG + KBLOG(@"响应解析失败(JSON)\nURL: %@\n错误: %@", + req.URL.absoluteString, + jsonErr.localizedDescription); +#endif + if (completion) completion(nil, response, [NSError errorWithDomain:KBNetworkErrorDomain code:KBNetworkErrorDecodeFailed userInfo:@{NSLocalizedDescriptionKey:@"JSON解析失败"}]); + return; + } +#if DEBUG + NSString *pretty = [self kb_prettyJSONStringFromObject:json] ?: @"(null)"; + pretty = [self kb_trimmedString:pretty maxLength:4096]; + NSInteger status = [response isKindOfClass:NSHTTPURLResponse.class] ? ((NSHTTPURLResponse *)response).statusCode : 0; + KBLOG(@"响应成功(JSON)\nURL: %@\n状态: %ld\nContent-Type: %@\n数据: %@", + req.URL.absoluteString, + (long)status, + ct ?: @"", + pretty); +#endif if (completion) completion(json, response, nil); } else { +#if DEBUG + NSString *text = [self kb_textFromData:data]; + text = [self kb_trimmedString:text maxLength:4096]; + NSInteger status = [response isKindOfClass:NSHTTPURLResponse.class] ? ((NSHTTPURLResponse *)response).statusCode : 0; + KBLOG(@"响应成功(Data)\nURL: %@\n状态: %ld\nContent-Type: %@\n数据: %@", + req.URL.absoluteString, + (long)status, + ct ?: @"", + text); +#endif if (completion) completion(data, response, nil); } }]; @@ -139,8 +226,7 @@ NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network"; if (!_manager) { NSURLSessionConfiguration *cfg = [NSURLSessionConfiguration ephemeralSessionConfiguration]; cfg.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData; - cfg.timeoutIntervalForRequest = self.timeout; - cfg.timeoutIntervalForResource = MAX(self.timeout, 30.0); + // 不在会话级别设置超时,避免与 per-request 的 serializer.timeoutInterval 产生不一致 if (@available(iOS 11.0, *)) { cfg.waitsForConnectivity = YES; } _manager = [[AFHTTPSessionManager alloc] initWithBaseURL:nil sessionConfiguration:cfg]; // 默认不使用 JSON 解析器,保持原生数据,再按需解析 @@ -167,3 +253,47 @@ NSErrorDomain const KBNetworkErrorDomain = @"com.company.keyboard.network"; } @end + +#pragma mark - Debug helpers + +#if DEBUG +@implementation KBNetworkManager (Debug) + +// 将对象转为漂亮的 JSON 字符串;否则返回 description +- (NSString *)kb_prettyJSONStringFromObject:(id)obj { + if (!obj || obj == (id)kCFNull) return nil; + if (![NSJSONSerialization isValidJSONObject:obj]) { + // 非标准 JSON 对象,直接 description + return [obj description]; + } + NSData *data = [NSJSONSerialization dataWithJSONObject:obj options:NSJSONWritingPrettyPrinted error:NULL]; + if (!data) return [obj description]; + return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] ?: [obj description]; +} + +// 尝试把二进制数据转为可读文本;失败则返回占位带长度 +- (NSString *)kb_textFromData:(NSData *)data { + if (!data) return @"(null)"; + if (data.length == 0) return @""; + // 优先 UTF-8 + NSString *text = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + if (text.length > 0) return text; + // 再尝试常见编码 + NSArray *encs = @[@(NSASCIIStringEncoding), @(NSISOLatin1StringEncoding), @(NSUnicodeStringEncoding)]; + for (NSNumber *n in encs) { + text = [[NSString alloc] initWithData:data encoding:n.unsignedIntegerValue]; + if (text.length > 0) return text; + } + return [NSString stringWithFormat:@"", (unsigned long)data.length]; +} + +// 过长时裁剪,避免刷屏 +- (NSString *)kb_trimmedString:(NSString *)s maxLength:(NSUInteger)maxLen { + if (!s) return @""; + if (s.length <= maxLen) return s; + NSString *head = [s substringToIndex:maxLen]; + return [head stringByAppendingString:@"\n......"]; +} + +@end +#endif diff --git a/keyBoard/Class/Shop/V/KBShopHeadView.m b/keyBoard/Class/Shop/V/KBShopHeadView.m index 91d4351..d24a9fa 100644 --- a/keyBoard/Class/Shop/V/KBShopHeadView.m +++ b/keyBoard/Class/Shop/V/KBShopHeadView.m @@ -6,7 +6,9 @@ // #import "KBShopHeadView.h" +@interface KBShopHeadView() +@end @implementation KBShopHeadView - (instancetype)initWithFrame:(CGRect)frame{ diff --git a/keyBoard/Class/Shop/VC/KBShopItemVC.m b/keyBoard/Class/Shop/VC/KBShopItemVC.m index b5be936..51dbc3b 100644 --- a/keyBoard/Class/Shop/VC/KBShopItemVC.m +++ b/keyBoard/Class/Shop/VC/KBShopItemVC.m @@ -18,7 +18,7 @@ - (void)viewDidLoad { [super viewDidLoad]; - self.view.backgroundColor = [UIColor whiteColor]; + self.view.backgroundColor = [UIColor clearColor]; // 懒加载 collectionView,并添加到视图 [self.view addSubview:self.collectionView]; @@ -133,7 +133,7 @@ UICollectionViewFlowLayout *layout = [UICollectionViewFlowLayout new]; layout.scrollDirection = UICollectionViewScrollDirectionVertical; _collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:layout]; - _collectionView.backgroundColor = [UIColor whiteColor]; + _collectionView.backgroundColor = [UIColor clearColor]; _collectionView.dataSource = self; _collectionView.delegate = self; // 注册皮肤卡片 cell diff --git a/keyBoard/Class/Shop/VC/KBShopVC.m b/keyBoard/Class/Shop/VC/KBShopVC.m index d0566a9..8ad5684 100644 --- a/keyBoard/Class/Shop/VC/KBShopVC.m +++ b/keyBoard/Class/Shop/VC/KBShopVC.m @@ -13,6 +13,9 @@ #import #import #import "KBShopItemVC.h" +#import "KBSearchVC.h" +#import "MySkinVC.h" + #import "KBWebViewViewController.h" @@ -31,6 +34,10 @@ static const CGFloat JXheightForHeaderInSection = 50; @property (nonatomic, strong) JXCategoryTitleView *categoryView; @property (nonatomic, strong) NSArray *titles; @property (nonatomic, strong) UIImageView *bgImageView; // 全屏背景图 +@property (nonatomic, strong) UIButton *searchBtn; +@property (nonatomic, strong) UIButton *skinBtn; +// 记录当前分类条是否为白底,避免重复设置 +@property (nonatomic, assign) BOOL categoryIsWhite; @end @@ -45,6 +52,18 @@ static const CGFloat JXheightForHeaderInSection = 50; make.edges.equalTo(self.view); }]; [self setupUI]; + [self.view addSubview:self.skinBtn]; + [self.view addSubview:self.searchBtn]; + [self.skinBtn mas_makeConstraints:^(MASConstraintMaker *make) { + make.right.equalTo(self.view).offset(-16); + make.top.mas_equalTo(KB_NAV_TOTAL_HEIGHT - 25); + make.width.height.mas_equalTo(25); + }]; + [self.searchBtn mas_makeConstraints:^(MASConstraintMaker *make) { + make.right.equalTo(self.skinBtn.mas_left).offset(-16); + make.top.equalTo(self.skinBtn); + make.width.height.mas_equalTo(25); + }]; } @@ -62,7 +81,7 @@ static const CGFloat JXheightForHeaderInSection = 50; _userHeaderView = [[KBShopHeadView alloc] init]; _categoryView = (JXCategoryTitleView *)[[KBCategoryTitleView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, JXheightForHeaderInSection)]; self.categoryView.titles = self.titles; - self.categoryView.backgroundColor = [UIColor whiteColor]; + self.categoryView.backgroundColor = [UIColor clearColor]; self.categoryView.delegate = self; self.categoryView.titleSelectedColor = [UIColor colorWithHex:0x1B1F1A]; self.categoryView.titleColor = [UIColor colorWithHex:0x9F9F9F]; @@ -102,7 +121,7 @@ static const CGFloat JXheightForHeaderInSection = 50; [self.view addSubview:self.pagerView]; // self.pagerView.listContainerView.scrollView.scrollEnabled = false; - +// self.pagerView.listContainerView.listCellBackgroundColor = [UIColor clearColor]; self.categoryView.listContainer = (id)self.pagerView.listContainerView; //导航栏隐藏的情况,处理扣边返回,下面的代码要加上 @@ -200,10 +219,50 @@ static const CGFloat JXheightForHeaderInSection = 50; - (void)pagerView:(JXPagerView *)pagerView mainTableViewDidScroll:(UIScrollView *)scrollView { - CGFloat thresholdDistance = JXTableHeaderViewHeight; - CGFloat percent = scrollView.contentOffset.y/thresholdDistance; + // 正确的吸顶阈值:头图高度 - 固定区偏移(即导航高度) + CGFloat headerH = (CGFloat)[self tableHeaderViewHeightInPagerView:self.pagerView]; + CGFloat thresholdDistance = MAX(0.0, headerH - self.pagerView.pinSectionHeaderVerticalOffset); + CGFloat percent = (thresholdDistance > 0 ? scrollView.contentOffset.y/thresholdDistance : 1); percent = MAX(0, MIN(1, percent)); self.naviBGView.alpha = percent; + // 分类条背景:未吸顶时透明,吸顶后白色 + // 触发吸顶的大致阈值 ≈ 头图高度 - 顶部偏移(此处用头图高度近似即可) + BOOL shouldWhite = (thresholdDistance > 0.0 && scrollView.contentOffset.y >= (thresholdDistance - 0.5)); + if (shouldWhite != self.categoryIsWhite) { + self.categoryIsWhite = shouldWhite; + UIColor *bg = shouldWhite ? [UIColor whiteColor] : [UIColor clearColor]; + self.categoryView.backgroundColor = bg; + // 内部 collectionView 也同步,避免出现条纹底色 + self.categoryView.collectionView.backgroundColor = bg; + } } +#pragma mark - action +- (void)searchBtnAction{ + KBSearchVC *vc = [[KBSearchVC alloc] init]; +// [self.navigationController pushViewController:vc animated:true]; +} + +- (void)skinBtnAction{ + MySkinVC *vc = [[MySkinVC alloc] init]; +// [self.navigationController pushViewController:vc animated:true]; +} + +#pragma mark - lazy +- (UIButton *)searchBtn{ + if (!_searchBtn) { + _searchBtn = [UIButton buttonWithType:UIButtonTypeCustom]; + [_searchBtn setImage:[UIImage imageNamed:@"shop_search_icon"] forState:UIControlStateNormal]; + [_searchBtn addTarget:self action:@selector(searchBtnAction) forControlEvents:UIControlEventTouchUpInside]; + } + return _searchBtn; +} +- (UIButton *)skinBtn{ + if (!_skinBtn) { + _skinBtn = [UIButton buttonWithType:UIButtonTypeCustom]; + [_skinBtn setImage:[UIImage imageNamed:@"shop_skin_icon"] forState:UIControlStateNormal]; + [_skinBtn addTarget:self action:@selector(skinBtnAction) forControlEvents:UIControlEventTouchUpInside]; + } + return _skinBtn; +} @end diff --git a/keyBoard/KeyBoardPrefixHeader.pch b/keyBoard/KeyBoardPrefixHeader.pch index bad7123..7f61f72 100644 --- a/keyBoard/KeyBoardPrefixHeader.pch +++ b/keyBoard/KeyBoardPrefixHeader.pch @@ -41,6 +41,16 @@ //-----------------------------------------------宏定义全局----------------------------------------------------------/ +// 调试专用日志(DEBUG 打印,RELEASE 不打印)。尽量显眼,包含函数与行号。 +#if DEBUG +#define KBLOG(fmt, ...) do { \ + NSString *kb_msg__ = [NSString stringWithFormat:(fmt), ##__VA_ARGS__]; \ + NSLog(@"\n==============================[KB DEBUG]==============================\n[Function] %s\n[Line] %d\n%@\n=====================================================================\n", __PRETTY_FUNCTION__, __LINE__, kb_msg__); \ +} while(0) +#else +#define KBLOG(...) +#endif + // 通用链接(Universal Links)统一配置 // 仅需修改这里的域名/前缀,工程内所有使用 UL 的地方都会同步。 #define KB_UL_BASE @"https://app.tknb.net/ul" // 与 Associated Domains 一致