// // SharePanel.m // msext // // Created on 2025/06/15. // Copyright © 2025年. All rights reserved. // #import "SharePanel.h" #import "WechatShareManager.h" // 微信分享管理器 #import "QQShareManager.h" // QQ分享管理器 #import "DouyinShareManager.h" // 抖音分享管理器 // 面板常量 #define kPanelHeight 180.0f #define kButtonSize 60.0f #define kButtonMargin 30.0f #define kButtonTopMargin 40.0f #define kAnimationDuration 0.25f @interface SharePanel() @property (nonatomic, strong) UIView *contentView; @property (nonatomic, strong) UIButton *wechatButton; @property (nonatomic, strong) UIButton *qqButton; @property (nonatomic, strong) UIButton *douyinButton; @property (nonatomic, strong) UILabel *wechatLabel; @property (nonatomic, strong) UILabel *qqLabel; @property (nonatomic, strong) UILabel *douyinLabel; @property (nonatomic, strong) ShareContent *shareContent; @property (nonatomic, copy) SharePanelCompletionBlock completionBlock; // 私有方法声明 - (UIViewController *)getTopViewController; @end static SharePanel *sharedPanel = nil; @implementation ShareContent @end @implementation SharePanel #pragma mark - Lifecycle + (instancetype)sharedPanel { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedPanel = [[self alloc] initWithFrame:CGRectZero]; }); return sharedPanel; } - (instancetype)initWithFrame:(CGRect)frame { CGRect screenBounds = [UIScreen mainScreen].bounds; self = [super initWithFrame:screenBounds]; if (self) { // 设置背景为半透明黑色 self.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5]; // 添加点击手势 UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleBackgroundTap:)]; [self addGestureRecognizer:tapGesture]; // 创建内容视图 _contentView = [[UIView alloc] initWithFrame:CGRectMake(0, screenBounds.size.height, screenBounds.size.width, kPanelHeight)]; _contentView.backgroundColor = [UIColor whiteColor]; // 圆角设置 UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:_contentView.bounds byRoundingCorners:UIRectCornerTopLeft|UIRectCornerTopRight cornerRadii:CGSizeMake(10.0, 10.0)]; CAShapeLayer *maskLayer = [CAShapeLayer layer]; maskLayer.frame = _contentView.bounds; maskLayer.path = maskPath.CGPath; _contentView.layer.mask = maskLayer; [self addSubview:_contentView]; // 创建分享按钮 [self setupShareButtons]; } return self; } #pragma mark - UI Setup - (void)setupShareButtons { CGFloat contentWidth = self.contentView.frame.size.width; CGFloat startX = (contentWidth - (3 * kButtonSize + 2 * kButtonMargin)) / 2; // 微信分享按钮 _wechatButton = [self createButtonWithImage:@"shareWechat" atIndex:0 startX:startX]; // 绑定点击事件为先关闭面板,然后执行微信分享 [_wechatButton addTarget:self action:@selector(handleButtonClick:) forControlEvents:UIControlEventTouchUpInside]; _wechatButton.tag = ShareTypeWeChat; [self.contentView addSubview:_wechatButton]; // 微信分享文字 _wechatLabel = [self createLabelWithText:@"微信" forButton:_wechatButton]; [self.contentView addSubview:_wechatLabel]; // QQ分享按钮 _qqButton = [self createButtonWithImage:@"shareQQ" atIndex:1 startX:startX]; // 绑定点击事件为先关闭面板,然后执行QQ分享 [_qqButton addTarget:self action:@selector(handleButtonClick:) forControlEvents:UIControlEventTouchUpInside]; _qqButton.tag = ShareTypeQQ; [self.contentView addSubview:_qqButton]; // QQ分享文字 _qqLabel = [self createLabelWithText:@"QQ" forButton:_qqButton]; [self.contentView addSubview:_qqLabel]; // 抖音分享按钮 _douyinButton = [self createButtonWithImage:@"shareDouyin" atIndex:2 startX:startX]; // 绑定点击事件为先关闭面板,然后执行抖音分享 [_douyinButton addTarget:self action:@selector(handleButtonClick:) forControlEvents:UIControlEventTouchUpInside]; _douyinButton.tag = ShareTypeDouyin; [self.contentView addSubview:_douyinButton]; // 抖音分享文字 _douyinLabel = [self createLabelWithText:@"抖音" forButton:_douyinButton]; [self.contentView addSubview:_douyinLabel]; // 添加取消按钮 UIButton *cancelButton = [UIButton buttonWithType:UIButtonTypeSystem]; cancelButton.frame = CGRectMake(0, kPanelHeight - 40, contentWidth, 40); [cancelButton setTitle:@"取消" forState:UIControlStateNormal]; [cancelButton setTitleColor:[UIColor darkGrayColor] forState:UIControlStateNormal]; cancelButton.titleLabel.font = [UIFont systemFontOfSize:16]; [cancelButton addTarget:self action:@selector(cancelAction) forControlEvents:UIControlEventTouchUpInside]; [self.contentView addSubview:cancelButton]; // 添加分隔线 UIView *lineView = [[UIView alloc] initWithFrame:CGRectMake(0, kPanelHeight - 41, contentWidth, 1)]; lineView.backgroundColor = [UIColor colorWithWhite:0.9 alpha:1.0]; [self.contentView addSubview:lineView]; } - (UIButton *)createButtonWithImage:(NSString *)imageName atIndex:(NSInteger)index startX:(CGFloat)startX { UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; CGFloat x = startX + index * (kButtonSize + kButtonMargin); button.frame = CGRectMake(x, kButtonTopMargin, kButtonSize, kButtonSize); [button setImage:[UIImage imageNamed:imageName] forState:UIControlStateNormal]; // 圆角设置 button.layer.cornerRadius = kButtonSize / 2; button.clipsToBounds = YES; // 可选:添加边框效果 // button.layer.borderWidth = 1.0; // button.layer.borderColor = [UIColor colorWithWhite:0.9 alpha:1.0].CGColor; // 可选:添加阴影效果(注意:阴影和clipsToBounds冲突,需要使用容器视图) // button.layer.shadowColor = [UIColor blackColor].CGColor; // button.layer.shadowOffset = CGSizeMake(0, 2); // button.layer.shadowOpacity = 0.1; // button.layer.shadowRadius = 4; return button; } - (UILabel *)createLabelWithText:(NSString *)text forButton:(UIButton *)button { UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, kButtonSize, 20)]; label.center = CGPointMake(button.center.x, button.frame.origin.y + button.frame.size.height + 15); label.text = text; label.textColor = [UIColor darkGrayColor]; label.font = [UIFont systemFontOfSize:12]; label.textAlignment = NSTextAlignmentCenter; return label; } #pragma mark - Public Methods + (void)showWithContent:(ShareContent *)content completion:(SharePanelCompletionBlock)completion { SharePanel *panel = [SharePanel sharedPanel]; panel.shareContent = content; panel.completionBlock = completion; UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow; [keyWindow addSubview:panel]; [UIView animateWithDuration:kAnimationDuration animations:^{ CGRect frame = panel.contentView.frame; frame.origin.y = [UIScreen mainScreen].bounds.size.height - kPanelHeight; panel.contentView.frame = frame; panel.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5]; }]; } + (void)showWithDictionary:(NSDictionary *)data completion:(SharePanelCompletionBlock)completion { // 创建ShareContent对象并从字典中提取数据 ShareContent *content = [[ShareContent alloc] init]; // 提取文本数据objectForKey // content.title = data[@"title"];//标题 // content.desc = data[@"description"]; // 更改为新的属性名 desc // content.webpageUrl = data[@"webpageUrl"];//网页链接 // content.type = data[@"type"];//类型 1-网页/文字 2-截图 // content.sharefriend = data[@"sharefriend"];// 是否分享给好友 content.title = [data objectForKey:@"title"];//标题 content.desc = [data objectForKey:@"description"]; // 更改为新的属性名 desc content.webpageUrl = [data objectForKey:@"webpageUrl"];//网页链接 content.type = [data objectForKey:@"type"];//类型 1-网页/文字 2-截图 content.sharefriend = [data objectForKey: @"sharefriend"];// 是否分享给好友 // 使用现有方法显示分享面板 [self showWithContent:content completion:completion]; } + (void)dismiss { SharePanel *panel = [SharePanel sharedPanel]; //[panel removeFromSuperview]; [UIView animateWithDuration:kAnimationDuration animations:^{ CGRect frame = panel.contentView.frame; frame.origin.y = [UIScreen mainScreen].bounds.size.height; panel.contentView.frame = frame; panel.backgroundColor = [UIColor colorWithWhite:0 alpha:0]; } completion:^(BOOL finished) { [panel removeFromSuperview]; }]; } + (void)dismissWithCompletion:(void (^)(void))completion { SharePanel *panel = [SharePanel sharedPanel]; [UIView animateWithDuration:kAnimationDuration animations:^{ CGRect frame = panel.contentView.frame; frame.origin.y = [UIScreen mainScreen].bounds.size.height; panel.contentView.frame = frame; panel.backgroundColor = [UIColor colorWithWhite:0 alpha:0]; } completion:^(BOOL finished) { [panel removeFromSuperview]; if (completion) { completion(); } }]; } #pragma mark - Actions - (void)handleBackgroundTap:(UITapGestureRecognizer *)gesture { CGPoint point = [gesture locationInView:self]; if (!CGRectContainsPoint(self.contentView.frame, point)) { [SharePanel dismiss]; } } - (void)cancelAction { [SharePanel dismiss]; } - (void)wechatShareAction { [self handleWechatShareAction]; } - (void)qqShareAction { [self handleQQShareAction]; } - (void)douyinShareAction { [self handleDouyinShareAction]; } #pragma mark - Button Action Handlers - (void)handleButtonClick:(UIButton *)sender { ShareType shareType = (ShareType)sender.tag; // 先关闭面板,在面板完全关闭后再执行对应的分享操作 __weak typeof(self) weakSelf = self; [SharePanel dismissWithCompletion:^{ switch (shareType) { case ShareTypeWeChat: [weakSelf handleWechatShareAction]; break; case ShareTypeQQ: [weakSelf handleQQShareAction]; break; case ShareTypeDouyin: [weakSelf handleDouyinShareAction]; break; default: break; } }]; } - (void)handleWechatShareAction { NSLog(@"🔍 [SharePanel] 处理微信分享"); ShareContent *content = self.shareContent; SharePanelCompletionBlock completionBlock = self.completionBlock; // 使用WechatShareManager处理微信分享 [WechatShareManager shareWithContent:content completion:^(BOOL success) { NSLog(@"微信分享结果: %@", success ? @"✅ 成功" : @"❌ 失败"); if (completionBlock) { completionBlock(ShareTypeWeChat, success); } }]; } - (void)handleQQShareAction { NSLog(@"🔍 [SharePanel] 处理QQ分享"); ShareContent *content = self.shareContent; SharePanelCompletionBlock completionBlock = self.completionBlock; // 使用QQShareManager处理QQ分享 [QQShareManager shareWithSystemShareContent:content completion:^(BOOL success) { NSLog(@"QQ分享结果: %@", success ? @"✅ 成功" : @"❌ 失败"); if (completionBlock) { completionBlock(ShareTypeQQ, success); } }]; } - (void)handleDouyinShareAction { NSLog(@"🔍 [SharePanel] 处理抖音分享"); ShareContent *content = self.shareContent; SharePanelCompletionBlock completionBlock = self.completionBlock; // 使用DouyinShareManager处理抖音分享 [DouyinShareManager shareWithContent:content completion:^(BOOL success) { NSLog(@"抖音分享结果: %@", success ? @"✅ 成功" : @"❌ 失败"); if (completionBlock) { completionBlock(ShareTypeDouyin, success); } }]; } #pragma mark - Helper Methods - (void)showErrorAlertWithMessage:(NSString *)message { UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"分享失败" message:message preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:nil]; [alert addAction:okAction]; UIViewController *rootVC = [UIApplication sharedApplication].keyWindow.rootViewController; UIViewController *topVC = [self topViewController:rootVC]; [topVC presentViewController:alert animated:YES completion:nil]; } - (UIViewController *)topViewController:(UIViewController *)rootViewController { if (rootViewController.presentedViewController == nil) { return rootViewController; } if ([rootViewController.presentedViewController isKindOfClass:[UINavigationController class]]) { UINavigationController *navigationController = (UINavigationController *)rootViewController.presentedViewController; UIViewController *lastViewController = [[navigationController viewControllers] lastObject]; return [self topViewController:lastViewController]; } if ([rootViewController.presentedViewController isKindOfClass:[UITabBarController class]]) { UITabBarController *tabBarController = (UITabBarController *)rootViewController.presentedViewController; UIViewController *selectedViewController = tabBarController.selectedViewController; return [self topViewController:selectedViewController]; } return [self topViewController:rootViewController.presentedViewController]; } - (UIImage *)getAppIconImage { // 获取应用的主Bundle NSBundle *mainBundle = [NSBundle mainBundle]; // 获取应用图标信息 NSDictionary *infoDictionary = [mainBundle infoDictionary]; NSArray *iconFiles = infoDictionary[@"CFBundleIcons"][@"CFBundlePrimaryIcon"][@"CFBundleIconFiles"]; // 如果找到图标文件 if (iconFiles && [iconFiles count] > 0) { // 使用最后一个图标(通常是最高分辨率的) NSString *iconLastName = [iconFiles lastObject]; UIImage *image = [UIImage imageNamed:iconLastName]; return image; } // 如果没有找到使用应用名称 NSString *appIconName = infoDictionary[@"CFBundleIconFile"]; if (appIconName) { return [UIImage imageNamed:appIconName]; } // 如果上面的方法都失败了,尝试直接使用应用图标名称 UIImage *appIcon = [UIImage imageNamed:@"AppIcon"]; if (appIcon) { return appIcon; } // 如果实在找不到,返回一个1x1像素的透明图片(避免崩溃) UIGraphicsBeginImageContext(CGSizeMake(1, 1)); UIImage *blankImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return blankImage; } // 显示复制成功提示 - (void)showCopySuccessAlert { // 创建一个新的UIWindow,设置为最高层级 UIWindow *toastWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; toastWindow.windowLevel = UIWindowLevelStatusBar + 100; // 尽可能高的层级 toastWindow.backgroundColor = [UIColor clearColor]; // 创建根视图控制器 __block UIViewController *rootVC = [[UIViewController alloc] init]; rootVC.view.backgroundColor = [UIColor clearColor]; toastWindow.rootViewController = rootVC; // 创建一个悬浮的提示框,显示在屏幕顶部 UIView *toastView = [[UIView alloc] initWithFrame:CGRectMake(0, -60, toastWindow.bounds.size.width - 40, 60)]; toastView.center = CGPointMake(CGRectGetMidX(toastWindow.bounds), toastView.center.y); toastView.backgroundColor = [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.8]; toastView.layer.cornerRadius = 10; toastView.alpha = 0; // 添加图标 UIImageView *iconView = [[UIImageView alloc] initWithFrame:CGRectMake(15, 15, 30, 30)]; UIImage *checkmarkImage = [self createCheckmarkImage]; iconView.image = checkmarkImage; iconView.contentMode = UIViewContentModeScaleAspectFit; [toastView addSubview:iconView]; // 添加提示文本 UILabel *messageLabel = [[UILabel alloc] initWithFrame:CGRectMake(55, 0, toastView.bounds.size.width - 70, toastView.bounds.size.height)]; messageLabel.text = @"内容已复制到剪贴板,请在抖音中粘贴"; messageLabel.textColor = [UIColor whiteColor]; messageLabel.textAlignment = NSTextAlignmentLeft; messageLabel.numberOfLines = 2; messageLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightMedium]; [toastView addSubview:messageLabel]; // 将toast视图添加到window [rootVC.view addSubview:toastView]; // 显示窗口 [toastWindow makeKeyAndVisible]; // 执行显示动画:从顶部滑出 [UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{ toastView.frame = CGRectOffset(toastView.frame, 0, 80 + [self safeAreaInsets].top); toastView.alpha = 1.0; } completion:nil]; // 2秒后开始消失动画 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // 消失动画 [UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{ toastView.frame = CGRectOffset(toastView.frame, 0, -80 - [self safeAreaInsets].top); toastView.alpha = 0.0; } completion:^(BOOL finished) { // 释放窗口 toastWindow.hidden = YES; // 设置为nil以释放内存 rootVC = nil; }]; }); } // 创建一个对勾图标 - (UIImage *)createCheckmarkImage { UIGraphicsBeginImageContextWithOptions(CGSizeMake(30, 30), NO, [UIScreen mainScreen].scale); CGContextRef context = UIGraphicsGetCurrentContext(); // 绘制圆形背景 CGContextSetFillColorWithColor(context, [UIColor colorWithRed:0.2 green:0.8 blue:0.4 alpha:1.0].CGColor); CGContextFillEllipseInRect(context, CGRectMake(0, 0, 30, 30)); // 绘制对勾 CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor); CGContextSetLineWidth(context, 2.5); CGContextSetLineCap(context, kCGLineCapRound); CGContextSetLineJoin(context, kCGLineJoinRound); // 绘制对勾路径 CGContextMoveToPoint(context, 8, 15); CGContextAddLineToPoint(context, 13, 20); CGContextAddLineToPoint(context, 22, 10); CGContextStrokePath(context); UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return image; } // 获取安全区域插入值 - (UIEdgeInsets)safeAreaInsets { if (@available(iOS 11.0, *)) { return [UIApplication sharedApplication].keyWindow.safeAreaInsets; } return UIEdgeInsetsZero; } // URL编码方法 - (NSString *)encodeUrlString:(NSString *)string { if (!string) return @""; return [string stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; } #pragma mark - QQ分享独立方法 + (void)shareToQQFriend:(ShareContent *)content completion:(SharePanelCompletionBlock)completion { if (![QQShareManager isQQInstalled]) { [self showAlertWithMessage:@"您的设备未安装QQ客户端,无法进行QQ分享"]; if (completion) { completion(ShareTypeQQ, NO); } return; } // 准备分享内容 NSString *title = content.title ?: @"游戏分享"; NSString *description = content.desc ?: @"精彩游戏分享"; NSString *shareUrl = content.webpageUrl; // 准备缩略图 UIImage *thumbImage = [UIImage imageNamed:@"sharelogo"] ?: [UIImage imageNamed:@"Icon180"]; // 根据内容选择分享类型 QQShareType shareType = shareUrl ? QQShareTypeNews : QQShareTypeText; // 使用QQShareManager的分享方法 if (shareType == QQShareTypeText) { // 纯文本分享 [QQShareManager shareToQQFriend:QQShareTypeText title:title description:description thumbImage:nil url:nil image:nil completion:^(BOOL success) { if (completion) { completion(ShareTypeQQ, success); } }]; } else { // 链接分享 [QQShareManager shareToQQFriend:QQShareTypeNews title:title description:description thumbImage:thumbImage url:shareUrl image:nil completion:^(BOOL success) { if (completion) { completion(ShareTypeQQ, success); } }]; } } + (void)shareToQZone:(ShareContent *)content completion:(SharePanelCompletionBlock)completion { if (![QQShareManager isQQInstalled]) { [self showAlertWithMessage:@"您的设备未安装QQ客户端,无法进行QQ空间分享"]; if (completion) { completion(ShareTypeQQ, NO); } return; } // 准备分享内容 NSString *title = content.title ?: @"游戏分享"; NSString *description = content.desc ?: @"精彩游戏分享"; NSString *shareUrl = content.webpageUrl; // 准备缩略图 UIImage *thumbImage = [UIImage imageNamed:@"sharelogo"] ?: [UIImage imageNamed:@"Icon180"]; // 根据内容选择分享类型 QQShareType shareType = shareUrl ? QQShareTypeNews : QQShareTypeText; [QQShareManager shareToQZone:shareType title:title description:description thumbImage:thumbImage url:shareUrl images:thumbImage ? @[thumbImage] : nil completion:^(BOOL success) { if (completion) { completion(ShareTypeQQ, success); } }]; } + (void)showAlertWithMessage:(NSString *)message { dispatch_async(dispatch_get_main_queue(), ^{ UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message preferredStyle:UIAlertControllerStyleAlert]; UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:nil]; [alertController addAction:okAction]; 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; } // 获取最顶层的视图控制器(实例方法) - (UIViewController *)getTopViewController { UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController; while (topController.presentedViewController) { topController = topController.presentedViewController; } if ([topController isKindOfClass:[UINavigationController class]]) { UINavigationController *navigationController = (UINavigationController *)topController; return navigationController.visibleViewController; } if ([topController isKindOfClass:[UITabBarController class]]) { UITabBarController *tabBarController = (UITabBarController *)topController; UIViewController *selectedViewController = tabBarController.selectedViewController; if ([selectedViewController isKindOfClass:[UINavigationController class]]) { UINavigationController *navigationController = (UINavigationController *)selectedViewController; return navigationController.visibleViewController; } return selectedViewController; } return topController; } @end