From 443e1a433220621f8237a4710a092468f394abf1 Mon Sep 17 00:00:00 2001 From: edwinQQQ Date: Mon, 9 Jun 2025 16:39:32 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=88=86=E4=BA=AB=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E7=9A=84=E5=A4=9A=E4=B8=AA=E6=96=B9=E6=B3=95=EF=BC=8C?= =?UTF-8?q?=E5=8C=85=E6=8B=AC=E6=8F=90=E5=8F=96=20URL=20=E5=92=8C=E5=89=A9?= =?UTF-8?q?=E4=BD=99=E5=AD=97=E7=AC=A6=E4=B8=B2=E3=80=81=E5=BC=BA=E5=88=B6?= =?UTF-8?q?=E5=85=B3=E9=97=AD=E5=88=86=E4=BA=AB=E5=BC=B9=E7=AA=97=EF=BC=8C?= =?UTF-8?q?=E4=BB=A5=E5=8F=8A=E5=88=86=E4=BA=AB=E5=86=85=E5=AE=B9=E7=9A=84?= =?UTF-8?q?=E6=9E=84=E5=BB=BA=E9=80=BB=E8=BE=91=E3=80=82=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=20ShareProvider=20=E7=B1=BB=E4=BB=A5=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=96=B0=E7=9A=84=E5=88=9D=E5=A7=8B=E5=8C=96=E6=96=B9=E6=B3=95?= =?UTF-8?q?=E5=92=8C=E4=B8=8D=E5=90=8C=E5=88=86=E4=BA=AB=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E7=9A=84=E5=86=85=E5=AE=B9=E6=8F=90=E4=BE=9B=E3=80=82=E4=BF=9D?= =?UTF-8?q?=E6=8C=81=E4=BB=A3=E7=A0=81=E7=BB=93=E6=9E=84=E4=B8=80=E8=87=B4?= =?UTF-8?q?=E6=80=A7=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- YuMi/Tools/Share/ShareHelder.h | 13 ++ YuMi/Tools/Share/ShareHelder.m | 217 ++++++++++++++++++++----------- YuMi/Tools/Share/ShareProvider.h | 14 +- YuMi/Tools/Share/ShareProvider.m | 216 +++++++++++++++++++++++++++++- 4 files changed, 377 insertions(+), 83 deletions(-) diff --git a/YuMi/Tools/Share/ShareHelder.h b/YuMi/Tools/Share/ShareHelder.h index 8932ca27..ea155bf4 100644 --- a/YuMi/Tools/Share/ShareHelder.h +++ b/YuMi/Tools/Share/ShareHelder.h @@ -19,6 +19,19 @@ NS_ASSUME_NONNULL_BEGIN url:(NSString *)urlString fromController:(UIViewController *)viewController; +/// 提取 URL 和剩余字符串 +/// @param inputString 输入字符串 +/// @return 包含 URL 和剩余字符串的字典 ++ (NSDictionary *)extractURLAndRemainingStringFromString:(NSString *)inputString; + +/// 提取 URL 字符串 +/// @param inputString 输入字符串 +/// @return 提取的 URL 字符串 ++ (NSString *)extractURLFromString:(NSString *)inputString; + +/// 强制关闭分享弹窗 ++ (void)forceCloseShareSheet; + @end NS_ASSUME_NONNULL_END diff --git a/YuMi/Tools/Share/ShareHelder.m b/YuMi/Tools/Share/ShareHelder.m index c4865517..30a97f1b 100644 --- a/YuMi/Tools/Share/ShareHelder.m +++ b/YuMi/Tools/Share/ShareHelder.m @@ -10,6 +10,11 @@ #import "ShareProvider.h" #import +// 添加静态变量追踪当前的分享控制器 +static UIActivityViewController *_currentActivityVC = nil; +static NSTimer *_timeoutTimer = nil; +static __weak UIViewController *_presentingVC = nil; + @implementation ShareHelder + (NSDictionary *)extractURLAndRemainingStringFromString:(NSString *)inputString { @@ -73,48 +78,85 @@ + (void)shareImage:(UIImage *)image url:(NSString *)urlString - fromController:(UIViewController *)viewController { + fromController:(UIViewController *)viewController { #if DEBUG urlString = [urlString stringByReplacingOccurrencesOfString:@"https" withString:@"http"]; #endif - // 1. 准备基础内容 - NSDictionary *dic = [self extractURLAndRemainingStringFromString:urlString]; - NSString *text = [dic objectForKey:@"remainingString"]; - NSURL *url = [NSURL URLWithString:[dic objectForKey:@"url"]]; - // 2. 创建富媒体元数据 - NSMutableArray *shareItems = @[text, url].mutableCopy; - if (@available(iOS 13.0, *)) { - LPLinkMetadata *metadata = [[LPLinkMetadata alloc] init]; - metadata.originalURL = url; - metadata.URL = url; - metadata.title = @"MoliStars"; - - // 设置自定义图标(使用系统图标) - UIImage *iconImage = image; - metadata.iconProvider = [[NSItemProvider alloc] initWithObject:iconImage]; - - // 设置自定义缩略图(从本地资源加载) - UIImage *thumbnailImage = image; - metadata.imageProvider = [[NSItemProvider alloc] initWithObject:thumbnailImage]; - [shareItems addObject:metadata]; + // 1. 解析URL和文本 + NSDictionary *dic = [self extractURLAndRemainingStringFromString:urlString]; + NSString *remainingText = [dic objectForKey:@"remainingString"]; + NSString *urlStr = [dic objectForKey:@"url"]; + NSURL *url = urlStr.length > 0 ? [NSURL URLWithString:urlStr] : nil; + + // 2. 准备分享内容 + NSString *title = @"🎵 发现精彩内容"; + NSString *subtitle = remainingText.length > 0 ? remainingText : @"快来看看这个有趣的内容吧!"; + NSString *appName = [YYUtility appName]; + + // 3. 获取应用图标 + UIImage *appIcon = [UIImage imageNamed:@"AppIcon"] ?: [UIImage imageNamed:@"app_icon"]; + if (!appIcon) { + // 尝试从bundle获取应用图标 + NSString *iconPath = [[NSBundle mainBundle] pathForResource:@"AppIcon60x60" ofType:@"png"]; + if (iconPath) { + appIcon = [UIImage imageWithContentsOfFile:iconPath]; + } } - // 3. 构建分享内容数组(顺序敏感) - NSArray *activityItems = shareItems.copy; + // 准备多种格式的分享内容 + NSMutableArray *shareItems = [NSMutableArray array]; - // 4. 创建分享控制器 + // 1. 添加纯文本(用于备忘录等文本应用) + NSString *plainText = [NSString stringWithFormat:@"🎵 发现精彩内容\n\n%@\n\n🔗 %@\n\n—— 来自 MoliStars ——", + subtitle, url.absoluteString ?: @""]; + [shareItems addObject:plainText]; + + // 2. 添加URL(用于浏览器、阅读列表等) + if (url) { + [shareItems addObject:url]; + } + + // 3. 添加图片(用于相册、图片编辑等) + if (image) { + [shareItems addObject:image]; + } + + // 4. 添加增强的ShareProvider(用于高级自定义) + ShareProvider *shareProvider = [[ShareProvider alloc] initWithTitle:title + subtitle:subtitle + appName:appName + url:url + image:image + appIcon:appIcon]; + [shareItems addObject:shareProvider]; + + // 创建ActivityViewController UIActivityViewController *activityVC = [[UIActivityViewController alloc] - initWithActivityItems:activityItems + initWithActivityItems:shareItems.copy applicationActivities:nil]; + + // 不排除任何系统分享类型,让用户看到所有选项 + activityVC.excludedActivityTypes = nil; - // 5. 排除不需要的分享类型 - activityVC.excludedActivityTypes = @[ - UIActivityTypeAssignToContact, // 移除「指定联系人」 -// UIActivityTypeAddToHomeScreen // 移除「添加到主屏幕」 - ]; + // 保存当前分享控制器的引用 + _currentActivityVC = activityVC; + _presentingVC = viewController; - // 6. 适配 iPad 弹出样式 + // 添加完成回调监控 + activityVC.completionWithItemsHandler = ^(UIActivityType activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) { + [self cleanupShareSession]; + + if (activityError) { + NSLog(@"ShareHelder: Activity error occurred: %@", activityError.localizedDescription); + // 错误发生时,确保界面能正常关闭 + dispatch_async(dispatch_get_main_queue(), ^{ + [self forceCloseShareSheet]; + }); + } + }; + + // 8. 适配 iPad 弹出样式 if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { UIPopoverPresentationController *popover = activityVC.popoverPresentationController; if (popover) { @@ -123,67 +165,90 @@ popover.permittedArrowDirections = UIPopoverArrowDirectionAny; } } + + // 10. 设置超时保护定时器 + _timeoutTimer = [NSTimer scheduledTimerWithTimeInterval:15.0 target:self selector:@selector(checkShareSheetTimeout) userInfo:nil repeats:NO]; - // 7. 显示分享界面 + // 11. 显示分享界面 [viewController presentViewController:activityVC animated:YES completion:nil]; } +#pragma mark - 新增方法 + ++ (void)forceCloseShareSheet { + if (_currentActivityVC) { + UIViewController *presentingVC = _currentActivityVC.presentingViewController; + if (presentingVC) { + [presentingVC dismissViewControllerAnimated:YES completion:^{ + [self cleanupShareSession]; + }]; + } else if (_presentingVC) { + [_presentingVC dismissViewControllerAnimated:YES completion:^{ + [self cleanupShareSession]; + }]; + } + } + + // 清理会话 + [self cleanupShareSession]; +} + ++ (void)checkShareSheetTimeout { + // 检查是否还有未关闭的分享弹窗 + if (_currentActivityVC && _currentActivityVC.presentingViewController) { + NSLog(@"ShareHelder: Share sheet timeout detected, attempting force close"); + [self forceCloseShareSheet]; + } +} + ++ (void)cleanupShareSession { + // 清理定时器 + if (_timeoutTimer) { + [_timeoutTimer invalidate]; + _timeoutTimer = nil; + } + + // 清理引用 + _currentActivityVC = nil; + _presentingVC = nil; +} + (void)__shareImage:(UIImage *)image url:(NSString *)urlString fromController:(UIViewController *)viewController { -// NSMutableArray *items = [NSMutableArray array]; - - // 添加图片 -// if (image) { -// NSData *imageData = UIImagePNGRepresentation(image); -// if (imageData) { -// NSItemProvider *imageProvider = [[NSItemProvider alloc] initWithItem:imageData typeIdentifier:@"public.png"]; -// [items addObject:imageProvider]; -// } -// } -// -// // 添加 URL + 文字 -// if (urlString.length > 0) { -// NSString *textToShare = [NSString stringWithFormat:@"分享链接:%@", urlString]; -// NSItemProvider *textProvider = [[NSItemProvider alloc] initWithItem:textToShare typeIdentifier:@"public.plain-text"]; -// [items addObject:textProvider]; -// } - + // 1. 准备分享内容 NSString *title = @"🎵 Apple Music 专辑推荐:Imagine Dragons"; + NSString *subtitle = @"来自MoliStars的精彩推荐"; + NSString *appName = @"MoliStars"; NSURL *albumURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@/%@", API_HOST_URL, urlString]]; UIImage *albumImage = image; + + // 2. 获取应用图标 + UIImage *appIcon = [UIImage imageNamed:@"AppIcon"] ?: [UIImage imageNamed:@"app_icon"]; + if (!appIcon) { + // 尝试从bundle获取应用图标 + NSString *iconPath = [[NSBundle mainBundle] pathForResource:@"AppIcon60x60" ofType:@"png"]; + if (iconPath) { + appIcon = [UIImage imageWithContentsOfFile:iconPath]; + } + } - // 创建自定义 ItemProvider - ShareProvider *musicShareProvider = [[ShareProvider alloc] initWithTitle:title url:albumURL image:albumImage]; + // 3. 创建自定义 ShareProvider(使用新的初始化方法) + ShareProvider *musicShareProvider = [[ShareProvider alloc] initWithTitle:title + subtitle:subtitle + appName:appName + url:albumURL + image:albumImage + appIcon:appIcon]; - // 创建分享项 + // 4. 创建分享项 NSArray *activityItems = @[musicShareProvider, albumImage]; - - -// // 添加图片 -// if (image) { -// [items addObject:image]; -// } -// -// // 添加 URL -// if (urlString.length > 0) { -// NSURL *url = [NSURL URLWithString:urlString]; -// if (url) { -// [items addObject:url]; -// } -// } -// -// if (items.count == 0) { -// NSLog(@"❌ 无效的分享内容"); -// return; -// } - // 创建分享面板 + // 5. 创建分享面板 UIActivityViewController *activityVC = [[UIActivityViewController alloc] initWithActivityItems:activityItems applicationActivities:nil]; - // 设置排除的分享选项 + // 6. 设置排除的分享选项 activityVC.excludedActivityTypes = @[ UIActivityTypeAssignToContact, // 指定给联系人 UIActivityTypeSaveToCameraRoll, // 保存到相册 @@ -193,13 +258,13 @@ UIActivityTypeMarkupAsPDF // 标记 PDF ]; - // iPad 需要指定 `popoverPresentationController` + // 7. iPad 需要指定 `popoverPresentationController` if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { activityVC.popoverPresentationController.sourceView = viewController.view; activityVC.popoverPresentationController.sourceRect = CGRectMake(viewController.view.bounds.size.width / 2, viewController.view.bounds.size.height / 2, 0, 0); } - // 展示分享面板 + // 8. 展示分享面板 [viewController presentViewController:activityVC animated:YES completion:nil]; } diff --git a/YuMi/Tools/Share/ShareProvider.h b/YuMi/Tools/Share/ShareProvider.h index cf3f626e..de8b76f5 100644 --- a/YuMi/Tools/Share/ShareProvider.h +++ b/YuMi/Tools/Share/ShareProvider.h @@ -6,14 +6,26 @@ // #import +#import NS_ASSUME_NONNULL_BEGIN @interface ShareProvider : UIActivityItemProvider + @property (nonatomic, strong) NSString *title; +@property (nonatomic, strong) NSString *subtitle; +@property (nonatomic, strong) NSString *appName; @property (nonatomic, strong) NSURL *url; @property (nonatomic, strong) UIImage *image; -- (instancetype)initWithTitle:(NSString *)title url:(NSURL *)url image:(UIImage *)image; +@property (nonatomic, strong) UIImage *appIcon; + +- (instancetype)initWithTitle:(NSString *)title + subtitle:(NSString *)subtitle + appName:(NSString *)appName + url:(NSURL *)url + image:(UIImage *)image + appIcon:(UIImage *)appIcon; + @end NS_ASSUME_NONNULL_END diff --git a/YuMi/Tools/Share/ShareProvider.m b/YuMi/Tools/Share/ShareProvider.m index 8e27ab0a..00d8080f 100644 --- a/YuMi/Tools/Share/ShareProvider.m +++ b/YuMi/Tools/Share/ShareProvider.m @@ -6,23 +6,227 @@ // #import "ShareProvider.h" +#import @implementation ShareProvider -- (instancetype)initWithTitle:(NSString *)title url:(NSURL *)url image:(UIImage *)image { - self = [super initWithPlaceholderItem:title]; +- (instancetype)initWithTitle:(NSString *)title + subtitle:(NSString *)subtitle + appName:(NSString *)appName + url:(NSURL *)url + image:(UIImage *)image + appIcon:(UIImage *)appIcon { + // 使用标题作为占位符 + self = [super initWithPlaceholderItem:title ?: @""]; if (self) { - _title = title; + _title = title ?: @""; + _subtitle = subtitle ?: @""; + _appName = appName ?: @"MoliStar"; _url = url; _image = image; + _appIcon = appIcon; } return self; } -// 根据不同的数据类型提供不同的内容 -- (id)item { - return [NSString stringWithFormat:@"%@\n%@", self.title, self.url.absoluteString]; +#pragma mark - UIActivityItemSource + +// 根据不同的分享类型提供不同的内容 +- (id)activityViewController:(UIActivityViewController *)activityViewController + itemForActivityType:(UIActivityType)activityType { + + if (!activityType) { + // 默认返回完整信息 + return [self fullShareText]; + } + + // 邮件分享 - 返回富文本内容 + if ([activityType isEqualToString:UIActivityTypeMail]) { + return [self emailShareText]; + } + + // 信息/短信分享 - 返回简洁文本 + if ([activityType isEqualToString:UIActivityTypeMessage]) { + return [self messageShareText]; + } + + // 备忘录分享 - 返回详细文本 + if ([activityType containsString:@"notes"] || + [activityType containsString:@"Notes"]) { + return [self notesShareText]; + } + + // 社交媒体分享 - 返回带话题标签的文本 + if ([activityType isEqualToString:UIActivityTypePostToTwitter] || + [activityType isEqualToString:UIActivityTypePostToFacebook]) { + return [self socialShareText]; + } + + // AirDrop - 根据有无图片决定返回内容 + if ([activityType isEqualToString:UIActivityTypeAirDrop]) { + if (self.image) { + return self.image; + } else { + return self.url ?: [self fullShareText]; + } + } + + // 复制链接 - 只返回URL + if ([activityType isEqualToString:UIActivityTypeCopyToPasteboard]) { + return self.url.absoluteString ?: [self fullShareText]; + } + + // 默认返回完整文本 + return [self fullShareText]; } +// 为邮件等应用提供主题 +- (NSString *)activityViewController:(UIActivityViewController *)activityViewController + subjectForActivityType:(UIActivityType)activityType { + if ([activityType isEqualToString:UIActivityTypeMail]) { + return [NSString stringWithFormat:@"来自%@的分享:%@", self.appName, self.title]; + } + return nil; +} + +// 指定数据类型(对图片分享很重要) +- (NSString *)activityViewController:(UIActivityViewController *)activityViewController + dataTypeIdentifierForActivityType:(UIActivityType)activityType { + + if (self.image && [activityType isEqualToString:UIActivityTypeAirDrop]) { + if (@available(iOS 14.0, *)) { + return UTTypePNG.identifier; + } else { + return @"public.png"; + } + } + + if (@available(iOS 14.0, *)) { + return UTTypeText.identifier; + } else { + return @"public.plain-text"; + } +} + +// 提供链接预览元数据 +- (LPLinkMetadata *)activityViewControllerLinkMetadata:(UIActivityViewController *)activityViewController { + LPLinkMetadata *metadata = [[LPLinkMetadata alloc] init]; + + // 设置标题 + metadata.title = self.title; + + // 使用 originalURL 技巧显示副标题 + if (self.subtitle.length > 0) { + metadata.originalURL = [NSURL fileURLWithPath:self.subtitle]; + } + + // 设置实际URL + if (self.url) { + metadata.URL = self.url; + } + + // 设置图标 - 优先使用应用图标,其次使用分享图片 + UIImage *iconToUse = self.appIcon ?: self.image; + if (iconToUse) { + metadata.iconProvider = [[NSItemProvider alloc] initWithObject:iconToUse]; + } + + // 如果有图片且不同于图标,设置为预览图 + if (self.image && self.image != iconToUse) { + metadata.imageProvider = [[NSItemProvider alloc] initWithObject:self.image]; + } + + return metadata; +} + +#pragma mark - 私有方法 - 不同分享文本格式 + +- (NSString *)fullShareText { + NSMutableString *text = [NSMutableString string]; + + if (self.title.length > 0) { + [text appendFormat:@"📱 %@\n\n", self.title]; + } + + if (self.subtitle.length > 0) { + [text appendFormat:@"%@\n\n", self.subtitle]; + } + + if (self.url) { + [text appendFormat:@"🔗 %@\n\n", self.url.absoluteString]; + } + + [text appendFormat:@"—— 来自 %@ ——", self.appName]; + + return text.copy; +} + +- (NSString *)emailShareText { + NSMutableString *text = [NSMutableString string]; + + [text appendString:@""]; + + if (self.title.length > 0) { + [text appendFormat:@"

📱 %@

", self.title]; + } + + if (self.subtitle.length > 0) { + [text appendFormat:@"

%@

", self.subtitle]; + } + + if (self.url) { + [text appendFormat:@"

🔗 点击访问

", self.url.absoluteString]; + } + + [text appendFormat:@"

通过 %@ 分享", self.appName]; + [text appendString:@""]; + + return text.copy; +} + +- (NSString *)messageShareText { + if (self.url) { + return [NSString stringWithFormat:@"📱 %@\n%@", self.title, self.url.absoluteString]; + } + return [NSString stringWithFormat:@"📱 %@\n%@", self.title, self.subtitle]; +} + +- (NSString *)notesShareText { + NSMutableString *text = [NSMutableString string]; + + [text appendFormat:@"📝 %@\n", self.title]; + [text appendString:@"━━━━━━━━━━━━━━━━━━━━\n\n"]; + + if (self.subtitle.length > 0) { + [text appendFormat:@"📄 %@\n\n", self.subtitle]; + } + + if (self.url) { + [text appendFormat:@"🔗 链接:%@\n\n", self.url.absoluteString]; + } + + NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; + formatter.dateFormat = @"yyyy-MM-dd HH:mm"; + [text appendFormat:@"⏰ 保存时间:%@\n", [formatter stringFromDate:[NSDate date]]]; + [text appendFormat:@"📱 来源:%@", self.appName]; + + return text.copy; +} + +- (NSString *)socialShareText { + NSMutableString *text = [NSMutableString string]; + + if (self.title.length > 0) { + [text appendFormat:@"📱 %@ ", self.title]; + } + + if (self.url) { + [text appendFormat:@"%@ ", self.url.absoluteString]; + } + + [text appendFormat:@"#%@ #分享 #推荐", [self.appName stringByReplacingOccurrencesOfString:@" " withString:@""]]; + + return text.copy; +} @end