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,662 @@
//
// 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;
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;
// 使QQShareManagerQQ
[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];
// toastwindow
[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