微信分享去sdk

This commit is contained in:
joywayer
2026-06-30 17:40:54 +08:00
parent 04e702a99b
commit c1e51d2183
2 changed files with 198 additions and 146 deletions
+149 -114
View File
@@ -2,161 +2,196 @@
// WechatShare.swift
// ylgamehall
//
// 微信分享 SharePlatform 实现 + 大厅专用 shareLobby() 路径。
// 微信分享 SharePlatform 实现:剪贴板 + 引导框 + `weixin://` URL Scheme,
// 与 QQShare / DouyinShare 同款交互模式,**不依赖微信 OpenSDK 的分享 API**。
//
// 两条路径对应原 msext 两个 ViewController 中同名 handler 的差异行为:
// 设计取舍:
// - 微信 OpenSDK 的 sendReq 走"专有剪贴板 plist + AppID 后台白名单",与
// QQ / 抖音的"明文 URL Scheme"开放策略不同;为统一三方分享交互体验、
// 减少 SDK 协议变更带来的不稳定,分享路径改走剪贴板 + 手动粘贴。
// - 微信 OpenSDK 二进制依然保留(WeChatSDK.register / WeChatAuth.authorize
// 用于登录),仅去掉分享 SDK 调用与 WeChatManager 中的分享桥接。
// - scene 参数(friend / timeline)保留以满足 SharePlatform 协议契约,但
// 剪贴板路径下用户进微信后自行决定粘贴到对话框还是朋友圈,文案统一不分。
//
// • share() — 对齐 daoqi msext `WechatShareManager.shareWithContent:`
// (子游戏 gameController.m 活路径)2 type 分支:
// - type == "2" → 截图分享(thumb = app icon)
// - else → 链接分享(thumb = app icon)
// 两条对外入口对应两个 ViewController 同名 handler 的差异(仅剪贴板内容分支不同):
//
// • shareLobby() — 对齐 daoqi msext `NewRootVC.m:471-534` 3 type 分支:
// - type == "1" → 链接分享(thumb = app icon;原 msext
// 用 sharelogo.png,按用户决定 fallback 到 app icon)
// - type == "2" → 截图分享(thumb = 截图 40% 缩放)
// - 其他 → 远端图分享(NSData 直发 + thumb = 远端图 40% 缩放)
// • share() — 子游戏路径(gameController.m),2 type 分支:
// - type == "2" → 截图:UIPasteboard.image
// - else → 链接:UIPasteboard.string
//
// fire-and-forget:H5 端的 sharesuccess 已在 FriendsShareHandler 立即触发,
// 此处不再回传结果(与原 dispatch_after 1s completion(YES) 实质等价)。
// • shareLobby() — 大厅路径(NewRootVC.m),3 type 分支:
// - type == "1" → 链接
// - type == "2" → 截图
// - 其他 → 远端图(下载 → UIPasteboard.image)
//
// fire-and-forget:H5 端 sharesuccess 已在 FriendsShareHandler 立即触发,
// 此处不再回传结果。
//
import Foundation
import UIKit
import os.log
@MainActor
public final class WechatShare: SharePlatform {
public static let shared = WechatShare()
private static let log = Logger(subsystem: "ylgamehall", category: "WechatShare")
private static let wechatScheme = "weixin://"
public let name = "WeChat"
public var isInstalled: Bool {
WXApi.isWXAppInstalled()
guard let url = URL(string: Self.wechatScheme) else { return false }
return UIApplication.shared.canOpenURL(url)
}
public init() {}
// MARK: - 子游戏路径(gameController.m)
public func share(_ content: ShareContent, scene: ShareScene) {
guard WXApi.isWXAppInstalled() else { return }
guard isInstalled else { return }
let wxScene: WeChatManager.ShareScene = (scene == .timeline) ? .timeline : .session
let thumb = Self.appIconImage()
Task { @MainActor in
do {
if content.type == "2" {
// 截图分享(msext WechatShareManager isScreenshotShare 分支)
let shot = try ImageProvider.captureScreenshot()
guard let imageData = shot.jpegData(compressionQuality: 0.6) else { return }
try await WeChatManager.shared.shareImage(
.init(
imageData: imageData,
thumb: thumb,
messageExt: content.desc,
mediaTagName: "\(Self.appTagPrefix)游戏截图分享"
),
scene: wxScene
)
} else {
// 链接分享(msext else 分支,含 type=="1" 与任何其它值)
try await WeChatManager.shared.shareLink(
.init(
url: content.webpageUrl,
title: content.title,
desc: content.desc,
thumb: thumb,
mediaTagName: "\(Self.appTagPrefix)游戏分享"
),
scene: wxScene
)
}
} catch {
// SDK 回包失败不向 H5 反馈(sharesuccess 已乐观发出,对齐原项目)
}
if content.type == "2" {
shareScreenshot()
} else {
shareLink(content)
}
}
// MARK: - 大厅路径(NewRootVC.m)
/// 大厅页 H5 调 friendsShare 的微信分享路径,对齐 msext NewRootVC.m
/// :471-534 完整 3 分支结构。直发微信无 SharePanel。
public func shareLobby(_ content: ShareContent, scene: ShareScene) {
guard WXApi.isWXAppInstalled() else { return }
guard isInstalled else { return }
let wxScene: WeChatManager.ShareScene = (scene == .timeline) ? .timeline : .session
let appIcon = Self.appIconImage()
switch content.type {
case "1":
shareLink(content)
case "2":
shareScreenshot()
default:
shareRemoteImage(content)
}
}
// MARK: - 截图分享
/// 截图 → UIPasteboard.image → 弹引导框(不自动拉起微信,等用户点"立即打开")。
private func shareScreenshot() {
do {
let shot = try ImageProvider.captureScreenshot()
UIPasteboard.general.image = shot
Self.log.debug("WechatShare 截图已复制到剪贴板")
} catch {
Self.log.error("WechatShare 截图失败 \(error.localizedDescription, privacy: .public)")
return
}
let message = """
✅ 图片已复制到剪贴板
🎯 分享到好友、群聊或朋友圈步骤:
1️⃣ 打开微信,选择好友、群聊或朋友圈
2️⃣ 在输入框长按粘贴图片
3️⃣ 点击发送即可分享
"""
showGuidanceAlert(title: "微信分享准备完成", message: message)
}
// MARK: - 链接分享
/// title + desc + url 拼字符串 → UIPasteboard.string → 弹引导框。
/// 三字段均空时用兜底文案"来自进贤聚友棋牌的精彩内容分享"(与 DouyinShare 同款策略)。
private func shareLink(_ content: ShareContent) {
var parts: [String] = []
if !content.title.isEmpty { parts.append(content.title) }
if !content.desc.isEmpty { parts.append(content.desc) }
if !content.webpageUrl.isEmpty { parts.append(content.webpageUrl) }
let shareText = parts.isEmpty
? "来自进贤聚友棋牌的精彩内容分享"
: parts.joined(separator: "\n")
UIPasteboard.general.string = shareText
Self.log.debug("WechatShare 链接已复制到剪贴板")
let message = """
✅ 内容已复制到剪贴板
🎯 分享到好友、群聊或朋友圈步骤:
1️⃣ 打开微信,选择好友、群聊或朋友圈
2️⃣ 在输入框长按粘贴内容
3️⃣ 点击发送即可分享
"""
showGuidanceAlert(title: "微信分享准备完成", message: message)
}
// MARK: - 远端图分享(大厅 shareLobby 的"其他"分支)
/// 下载远端图 → UIPasteboard.image → 弹引导框。对齐 msext NewRootVC.m:526-534
/// "公众号二维码分享"分支:原 SDK 路径直发 NSData,剪贴板路径写入解码后的
/// UIImage 让用户在微信里长按粘贴。
private func shareRemoteImage(_ content: ShareContent) {
guard let url = URL(string: content.webpageUrl) else {
Self.log.error("WechatShare 远端图 URL 解析失败")
return
}
Task { @MainActor in
do {
switch content.type {
case "1":
// 链接分享:原 msext 用 sharelogo.png;用户决定 fallback 到 app icon
try await WeChatManager.shared.shareLink(
.init(
url: content.webpageUrl,
title: content.title,
desc: content.desc,
thumb: appIcon,
mediaTagName: "\(Self.appTagPrefix)游戏下载链接"
),
scene: wxScene
)
case "2":
// 截图分享:thumb = 截图 40% 缩放(对齐 msext imageByScalingAndCroppingForSize:0.4x)
let shot = try ImageProvider.captureScreenshot()
guard let imageData = shot.jpegData(compressionQuality: 0.6) else { return }
try await WeChatManager.shared.shareImage(
.init(
imageData: imageData,
thumb: ImageProvider.scaledThumb(shot, scale: 0.4),
messageExt: content.desc,
mediaTagName: "\(Self.appTagPrefix)游戏截图分享"
),
scene: wxScene
)
default:
// 远端图分享(type == "" / "3" / 其他):下载 NSData 直发 +
// thumb = 远端图 40% 缩放。messageExt/mediaTagName 都是固定
// 字面(msext NewRootVC.m:530 字面,非 H5 desc)
guard let url = URL(string: content.webpageUrl) else { return }
let (data, _) = try await URLSession.shared.data(from: url)
guard let image = UIImage(data: data) else { return }
try await WeChatManager.shared.shareImage(
.init(
imageData: data,
thumb: ImageProvider.scaledThumb(image, scale: 0.4),
messageExt: "\(Self.appTagPrefix)公众号二维码分享",
mediaTagName: "\(Self.appTagPrefix)公众号二维码分享"
),
scene: wxScene
)
let (data, _) = try await URLSession.shared.data(from: url)
guard let image = UIImage(data: data) else {
Self.log.error("WechatShare 远端图解码失败")
return
}
UIPasteboard.general.image = image
Self.log.debug("WechatShare 远端图已复制到剪贴板")
} catch {
// 与子游戏路径同样吞掉错误(sharesuccess 已乐观发出)
Self.log.error("WechatShare 远端图下载失败 \(error.localizedDescription, privacy: .public)")
return
}
let message = """
✅ 图片已复制到剪贴板
🎯 分享到好友、群聊或朋友圈步骤:
1️⃣ 打开微信,选择好友、群聊或朋友圈
2️⃣ 在输入框长按粘贴图片
3️⃣ 点击发送即可分享
"""
showGuidanceAlert(title: "微信分享准备完成", message: message)
}
}
// MARK: - 共享常量 / 工具
// MARK: - 引导弹窗
/// msext WechatShareManager 字面常量 "进贤聚友棋牌"(与 daoqi
/// SGDefineInfo.h gamehallname 同源),用于 WXMediaMessage.mediaTagName 拼接。
private static let appTagPrefix = "进贤聚友棋牌"
/// 弹"立即打开微信 / 稍后分享"二选一 Alert,与 QQShare / DouyinShare 同款 UX。
private func showGuidanceAlert(title: String, message: String) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "立即打开微信", style: .default) { _ in
if let url = URL(string: Self.wechatScheme),
UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
}
})
alert.addAction(UIAlertAction(title: "稍后分享", style: .cancel))
/// app icon 缩略图。读 Info.plist `CFBundleIcons` → `CFBundlePrimaryIcon`
/// → `CFBundleIconFiles` 的最后一个(最高分辨率),与原 msext
/// WechatShareManager `getAppIconImage` 等价。失败兜底返回 1x1 透明图。
private static func appIconImage() -> UIImage {
if let icons = Bundle.main.object(forInfoDictionaryKey: "CFBundleIcons") as? [String: Any],
let primary = icons["CFBundlePrimaryIcon"] as? [String: Any],
let files = primary["CFBundleIconFiles"] as? [String],
let last = files.last,
let icon = UIImage(named: last) {
return icon
guard let topVC = Self.topViewController() else { return }
topVC.present(alert, animated: true)
}
private static func topViewController() -> UIViewController? {
let scene = UIApplication.shared.connectedScenes
.compactMap { $0 as? UIWindowScene }
.first(where: { $0.activationState == .foregroundActive })
?? UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene }.first
guard var top = scene?.windows.first(where: \.isKeyWindow)?.rootViewController
?? scene?.windows.first?.rootViewController else { return nil }
while let presented = top.presentedViewController { top = presented }
if let nav = top as? UINavigationController, let visible = nav.visibleViewController {
return visible
}
if let icon = UIImage(named: "AppIcon") { return icon }
let renderer = UIGraphicsImageRenderer(size: CGSize(width: 1, height: 1))
return renderer.image { _ in }
return top
}
}