feat: 重构分享功能 - 将各平台分享逻辑整理到对应Manager中

- 新增 WechatShareManager:封装微信分享逻辑,支持ShareContent对象
- 增强 DouyinShareManager:新增ShareContent支持和分享引导功能
- 优化 QQShareManager:支持截图/纯文本分享类型自动识别
- 重构 SharePanel:简化为UI调度层,移除具体分享实现
- 实现职责分离:各Manager专注自己的平台分享逻辑
- 提升可维护性:修改某平台不影响其他平台
- 增强可扩展性:新增分享平台更容易实现
This commit is contained in:
joywayer
2025-06-17 19:55:44 +08:00
parent 1666d6cf24
commit 2296c65974
13 changed files with 3730 additions and 2 deletions

View File

@@ -0,0 +1,595 @@
//
// DouyinShareManager.m
// msext
//
// Created on 2025/06/15.
// Copyright © 2025. All rights reserved.
//
#import "DouyinShareManager.h"
#import "FuncPublic.h" //
#import <Photos/Photos.h>
#import <MobileCoreServices/MobileCoreServices.h>
// 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;
}
// KVCShareContent
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