// // DouyinShareManager.m // msext // // Created on 2025/06/15. // Copyright © 2025年. All rights reserved. // #import "DouyinShareManager.h" #import "FuncPublic.h" // 用于截图功能 #import #import // 抖音URL Schemes #define kDouyinScheme @"snssdk1128://" #define kDouyinShareScheme @"snssdk1128://share/video" #define kDouyinImageShareScheme @"snssdk1128://share/image" #define kDouyinHashtagScheme @"snssdk1128://challenge/detail/" #define kDouyinUserProfileScheme @"snssdk1128://user/profile/" // 抖音应用回调的处理 static void(^DouyinShareCompletion)(BOOL) = nil; @implementation DouyinShareManager #pragma mark - Public Methods + (BOOL)isDouyinInstalled { return [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:kDouyinScheme]]; } + (void)shareToDouyin:(DouyinShareType)type hashtagName:(NSString *)hashtagName image:(UIImage *)image videoUrl:(NSURL *)videoUrl userId:(NSString *)userId completion:(void (^)(BOOL))completion { // 检查抖音是否已安装 if (![self isDouyinInstalled]) { if (completion) { completion(NO); } [self showAppNotInstalledAlert:@"抖音"]; return; } // 保存回调 DouyinShareCompletion = completion; switch (type) { case DouyinShareTypeImage: [self shareImageToDouyin:image completion:completion]; break; case DouyinShareTypeVideo: [self shareVideoToDouyin:videoUrl completion:completion]; break; case DouyinShareTypeHashtag: [self shareHashtagToDouyin:hashtagName completion:completion]; break; case DouyinShareTypeOpenProfile: [self openDouyinUserProfile:userId completion:completion]; break; } } + (BOOL)handleOpenURL:(NSURL *)url { // 判断是否是抖音返回的URL NSString *urlString = [url absoluteString]; if ([urlString hasPrefix:@"msext://douyin_share_result"]) { // 根据返回的URL解析结果 NSString *result = [self getUrlParam:urlString paramName:@"result"]; BOOL success = result && [result isEqualToString:@"success"]; // 执行回调 if (DouyinShareCompletion) { DouyinShareCompletion(success); DouyinShareCompletion = nil; } return YES; } return NO; } + (void)shareWithContent:(id)shareContent completion:(void (^ _Nullable)(BOOL success))completion { NSLog(@"🔍 [DouyinShareManager] 开始抖音分享"); // 检查抖音是否已安装 if (![self isDouyinInstalled]) { NSLog(@"❌ [DouyinShareManager] 抖音未安装"); [self showAppNotInstalledAlert:@"抖音"]; if (completion) { completion(NO); } return; } // 检查ShareContent对象 if (!shareContent) { NSLog(@"❌ [DouyinShareManager] ShareContent对象为空"); if (completion) { completion(NO); } return; } // 通过KVC从ShareContent对象中提取参数 NSString *title = nil; NSString *description = nil; NSString *type = nil; @try { if ([shareContent respondsToSelector:@selector(valueForKey:)]) { title = [shareContent valueForKey:@"title"]; description = [shareContent valueForKey:@"desc"]; type = [shareContent valueForKey:@"type"]; } NSLog(@"🔍 [DouyinShareManager] 从ShareContent提取参数:"); NSLog(@"🔍 - 标题: %@", title ?: @"(无)"); NSLog(@"🔍 - 描述: %@", description ?: @"(无)"); NSLog(@"🔍 - 类型: %@", type ?: @"(无)"); } @catch (NSException *exception) { NSLog(@"❌ [DouyinShareManager] 解析ShareContent时发生异常: %@", exception.reason); if (completion) { completion(NO); } return; } // 根据分享类型选择分享方式 BOOL isScreenshotShare = [type intValue] == 2; if (isScreenshotShare) { // 截图分享模式 NSLog(@"🔍 [DouyinShareManager] 执行截图分享"); UIImage *screenImage = [FuncPublic getImageWithFullScreenshot]; if (screenImage) { // 复制图片到剪贴板 UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; pasteboard.image = screenImage; NSLog(@"✅ [DouyinShareManager] 截图已复制到剪贴板"); // 显示分享引导弹窗 [self showImageShareGuidance]; if (completion) { completion(YES); } } else { NSLog(@"❌ [DouyinShareManager] 截图获取失败"); if (completion) { completion(NO); } } } else { // 文本分享模式 NSLog(@"🔍 [DouyinShareManager] 执行文本分享"); // 组合分享内容 NSString *shareText = @""; if (title && title.length > 0) { shareText = title; } if (description && description.length > 0) { if (shareText.length > 0) { shareText = [NSString stringWithFormat:@"%@\n\n%@", shareText, description]; } else { shareText = description; } } if (shareText.length == 0) { shareText = @"来自进贤聚友棋牌的精彩内容分享"; } // 复制到剪贴板 UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; [pasteboard setString:shareText]; NSLog(@"✅ [DouyinShareManager] 文本已复制到剪贴板: %@", shareText); // 显示分享引导弹窗 [self showTextShareGuidance]; if (completion) { completion(YES); } } } #pragma mark - Private Methods + (void)shareImageToDouyin:(UIImage *)image completion:(void (^)(BOOL))completion { if (!image) { if (completion) { completion(NO); } return; } // 方案1:先保存图片到相册,然后使用UIPasteboard传递图片数据,最后打开抖音 [self requestPhotoLibraryAuthorizationWithCompletion:^(BOOL granted) { if (!granted) { if (completion) { completion(NO); } [self showPhotoLibraryPermissionAlert]; return; } // 将图片保存到相册 [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{ [PHAssetChangeRequest creationRequestForAssetFromImage:image]; } completionHandler:^(BOOL success, NSError * _Nullable error) { dispatch_async(dispatch_get_main_queue(), ^{ if (success) { // 同时将图片数据放到剪贴板,供抖音读取 UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; [pasteboard setImage:image]; // 尝试多种URL方案打开抖音分享 NSArray *urlSchemes = @[ @"snssdk1128://camera", // 打开抖音拍摄界面 @"snssdk1128://publish", // 打开发布界面 @"snssdk1128://share/image", // 图片分享(可能已废弃) @"snssdk1128://" // 基础scheme,打开抖音主界面 ]; BOOL opened = NO; for (NSString *urlString in urlSchemes) { NSURL *douyinURL = [NSURL URLWithString:urlString]; if ([[UIApplication sharedApplication] canOpenURL:douyinURL]) { if (@available(iOS 10.0, *)) { [[UIApplication sharedApplication] openURL:douyinURL options:@{} completionHandler:^(BOOL openSuccess) { if (completion) { completion(openSuccess); } }]; } else { BOOL openSuccess = [[UIApplication sharedApplication] openURL:douyinURL]; if (completion) { completion(openSuccess); } } opened = YES; break; } } if (!opened && completion) { completion(NO); } } else { if (completion) { completion(NO); } NSLog(@"保存图片到相册失败:%@", error); } }); }]; }]; } + (void)shareVideoToDouyin:(NSURL *)videoUrl completion:(void (^)(BOOL))completion { if (!videoUrl) { if (completion) { completion(NO); } return; } // 检查文件是否存在 if (![[NSFileManager defaultManager] fileExistsAtPath:videoUrl.path]) { if (completion) { completion(NO); } return; } // 将视频保存到相册 [self requestPhotoLibraryAuthorizationWithCompletion:^(BOOL granted) { if (!granted) { if (completion) { completion(NO); } [self showPhotoLibraryPermissionAlert]; return; } [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{ [PHAssetChangeRequest creationRequestForAssetFromVideoAtFileURL:videoUrl]; } completionHandler:^(BOOL success, NSError * _Nullable error) { dispatch_async(dispatch_get_main_queue(), ^{ if (success) { // 获取刚保存的视频的资源标识符 PHFetchOptions *fetchOptions = [[PHFetchOptions alloc] init]; fetchOptions.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"creationDate" ascending:NO]]; PHFetchResult *fetchResult = [PHAsset fetchAssetsWithMediaType:PHAssetMediaTypeVideo options:fetchOptions]; if (fetchResult.firstObject) { PHAsset *asset = fetchResult.firstObject; NSString *localIdentifier = asset.localIdentifier; // 构建抖音分享URL NSString *urlString = [NSString stringWithFormat:@"%@?video=%@&from=app_%@", kDouyinShareScheme, [self encodeString:localIdentifier], [self encodeString:@"msext"]]; NSURL *douyinURL = [NSURL URLWithString:urlString]; if ([[UIApplication sharedApplication] canOpenURL:douyinURL]) { if (@available(iOS 10.0, *)) { [[UIApplication sharedApplication] openURL:douyinURL options:@{} completionHandler:^(BOOL success) { // 这里不调用completion,等待抖音返回再调用 }]; } else { [[UIApplication sharedApplication] openURL:douyinURL]; } } else { if (completion) { completion(NO); } } } else { if (completion) { completion(NO); } } } else { if (completion) { completion(NO); } NSLog(@"保存视频到相册失败:%@", error); } }); }]; }]; } + (void)shareHashtagToDouyin:(NSString *)hashtagName completion:(void (^)(BOOL))completion { if (!hashtagName || hashtagName.length == 0) { if (completion) { completion(NO); } return; } // 构建打开话题挑战的URL NSString *urlString = [NSString stringWithFormat:@"%@%@", kDouyinHashtagScheme, [self encodeString:hashtagName]]; NSURL *douyinURL = [NSURL URLWithString:urlString]; if ([[UIApplication sharedApplication] canOpenURL:douyinURL]) { if (@available(iOS 10.0, *)) { [[UIApplication sharedApplication] openURL:douyinURL options:@{} completionHandler:^(BOOL success) { if (completion) { completion(success); } }]; } else { BOOL success = [[UIApplication sharedApplication] openURL:douyinURL]; if (completion) { completion(success); } } } else { if (completion) { completion(NO); } } } + (void)openDouyinUserProfile:(NSString *)userId completion:(void (^)(BOOL))completion { if (!userId || userId.length == 0) { if (completion) { completion(NO); } return; } // 构建打开用户主页的URL NSString *urlString = [NSString stringWithFormat:@"%@%@", kDouyinUserProfileScheme, [self encodeString:userId]]; NSURL *douyinURL = [NSURL URLWithString:urlString]; if ([[UIApplication sharedApplication] canOpenURL:douyinURL]) { if (@available(iOS 10.0, *)) { [[UIApplication sharedApplication] openURL:douyinURL options:@{} completionHandler:^(BOOL success) { if (completion) { completion(success); } }]; } else { BOOL success = [[UIApplication sharedApplication] openURL:douyinURL]; if (completion) { completion(success); } } } else { if (completion) { completion(NO); } } } + (void)requestPhotoLibraryAuthorizationWithCompletion:(void (^)(BOOL granted))completion { PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus]; switch (status) { case PHAuthorizationStatusAuthorized: { if (completion) { completion(YES); } break; } case PHAuthorizationStatusNotDetermined: { [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus newStatus) { dispatch_async(dispatch_get_main_queue(), ^{ if (completion) { completion(newStatus == PHAuthorizationStatusAuthorized); } }); }]; break; } case PHAuthorizationStatusDenied: case PHAuthorizationStatusRestricted: { if (completion) { completion(NO); } break; } default: { if (completion) { completion(NO); } break; } } } + (NSString *)encodeString:(NSString *)string { if (!string) return @""; return [string stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; } + (NSString *)getUrlParam:(NSString *)url paramName:(NSString *)name { NSError *error; NSString *regTags = [[NSString alloc] initWithFormat:@"(^|&|\\?)%@=([^&]*)(&|$)", name]; NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:regTags options:NSRegularExpressionCaseInsensitive error:&error]; NSTextCheckingResult *match = [regex firstMatchInString:url options:0 range:NSMakeRange(0, [url length])]; if (match) { NSString *paramValue = [url substringWithRange:[match rangeAtIndex:2]]; return paramValue; } return nil; } + (void)showAppNotInstalledAlert:(NSString *)appName { dispatch_async(dispatch_get_main_queue(), ^{ UIAlertController *alertController = [UIAlertController alertControllerWithTitle:[NSString stringWithFormat:@"%@未安装", appName] message:[NSString stringWithFormat:@"您的设备未安装%@客户端,无法进行分享", appName] preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:nil]; [alertController addAction:okAction]; UIViewController *topVC = [self topViewController]; [topVC presentViewController:alertController animated:YES completion:nil]; }); } + (void)showPhotoLibraryPermissionAlert { dispatch_async(dispatch_get_main_queue(), ^{ UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"无法访问相册" message:@"请在设置中开启相册权限,以便分享到抖音" preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:nil]; UIAlertAction *settingsAction = [UIAlertAction actionWithTitle:@"去设置" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { [[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]]; }]; [alertController addAction:cancelAction]; [alertController addAction:settingsAction]; UIViewController *topVC = [self topViewController]; [topVC presentViewController:alertController animated:YES completion:nil]; }); } + (UIViewController *)topViewController { UIViewController *rootViewController = [UIApplication sharedApplication].keyWindow.rootViewController; while (rootViewController.presentedViewController) { rootViewController = rootViewController.presentedViewController; } if ([rootViewController isKindOfClass:[UINavigationController class]]) { UINavigationController *navigationController = (UINavigationController *)rootViewController; return navigationController.visibleViewController; } if ([rootViewController isKindOfClass:[UITabBarController class]]) { UITabBarController *tabBarController = (UITabBarController *)rootViewController; UIViewController *selectedViewController = tabBarController.selectedViewController; if ([selectedViewController isKindOfClass:[UINavigationController class]]) { UINavigationController *navigationController = (UINavigationController *)selectedViewController; return navigationController.visibleViewController; } return selectedViewController; } return rootViewController; } + (void)showImageShareGuidance { NSString *title = @"抖音分享准备完成"; NSString *message = @"✅ 图片已复制到剪贴板\n\n" @"🎯 分享到好友或群聊步骤:\n" @"1️⃣ 打开抖音,点击右下角「消息」\n" @"2️⃣ 选择好友或群聊进入聊天\n" @"3️⃣ 在输入框长按粘贴图片\n" @"4️⃣ 点击发送即可分享"; [self showGuidanceAlertWithTitle:title message:message]; } + (void)showTextShareGuidance { NSString *title = @"抖音分享准备完成"; NSString *message = @"✅ 内容已复制到剪贴板\n\n" @"🎯 分享到好友或群聊步骤:\n" @"1️⃣ 打开抖音,点击右下角「消息」\n" @"2️⃣ 选择好友或群聊进入聊天\n" @"3️⃣ 在输入框长按粘贴内容\n" @"4️⃣ 点击发送即可分享"; [self showGuidanceAlertWithTitle:title message:message]; } + (void)showGuidanceAlertWithTitle:(NSString *)title message:(NSString *)message { dispatch_async(dispatch_get_main_queue(), ^{ UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction *openAction = [UIAlertAction actionWithTitle:@"立即打开抖音" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) { NSURL *douyinURL = [NSURL URLWithString:kDouyinScheme]; if ([[UIApplication sharedApplication] canOpenURL:douyinURL]) { [[UIApplication sharedApplication] openURL:douyinURL options:@{} completionHandler:nil]; } }]; UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"稍后分享" style:UIAlertActionStyleCancel handler:nil]; [alert addAction:openAction]; [alert addAction:cancelAction]; // 获取顶层视图控制器 UIViewController *topVC = [self getTopViewController]; if (topVC) { [topVC presentViewController:alert animated:YES completion:nil]; } }); } + (UIViewController *)getTopViewController { UIViewController *topViewController = [UIApplication sharedApplication].keyWindow.rootViewController; while (topViewController.presentedViewController) { topViewController = topViewController.presentedViewController; } return topViewController; } @end