Phase 4.F:微信截图分享 type=2 / 远端图分享 type=其它

按 msext gameController.m:637-678 字面行为,WechatShare 按 content.type
分发:
- type=1 → shareLink(已有)
- type=2 → captureScreenshot → JPEG 0.6 + thumb 0.4x → shareImage
- type=其它 → downloadRemote(URL) → JPEG 0.9 + thumb 0.4x → shareImage

新增:
- ImageProvider.scaledThumb(_:scale:):等价 msext FuncPublic
  imageByScalingAndCroppingForSize 0.4x 缩放;微信 SDK setThumbImage:
  内部再压到 32KB
- WeChatManager.ShareImage 结构 + shareImage(_:scene:) 异步包装
  (WXImageObject + WXMediaMessage.setThumbImage,FIFO 串行复用 sharePending)

契约:与 type=1 一致,调成功 → sharesuccess 反向 callback(success/cancelled
都发);errCode=-2 → cancelled;其它 errCode → failed。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
joywayer
2026-06-23 01:33:17 +08:00
co-authored by Claude Opus 4.7
parent d9d9554d9f
commit 8e4c36d1c8
3 changed files with 96 additions and 11 deletions
@@ -69,6 +69,19 @@ public final class WeChatManager: NSObject {
} }
} }
/// 图片分享载荷(type=2 截图 / type=3 远端图)。
/// imageData 是大图原始字节(JPEG 等),thumb 是缩略图源 — SDK
/// `setThumbImage:` 内部把 thumb 压到 32KB。
public struct ShareImage {
public let imageData: Data
public let thumb: UIImage
public init(imageData: Data, thumb: UIImage) {
self.imageData = imageData
self.thumb = thumb
}
}
/// 链接分享(FIFO 串行)。微信回包不携带配对 ID,连续调时按入队顺序消费。 /// 链接分享(FIFO 串行)。微信回包不携带配对 ID,连续调时按入队顺序消费。
public func shareLink(_ link: ShareLink, scene: ShareScene) async throws { public func shareLink(_ link: ShareLink, scene: ShareScene) async throws {
#if canImport(WechatOpenSDK) #if canImport(WechatOpenSDK)
@@ -97,6 +110,35 @@ public final class WeChatManager: NSObject {
#endif #endif
} }
/// 图片分享(FIFO 串行,复用 sharePending)。msext gameController.m:637-678
/// type=2 截图 / type=其它 远端图 共用同一条 WXImageObject + setThumbImage 路径。
public func shareImage(_ img: ShareImage, scene: ShareScene) async throws {
#if canImport(WechatOpenSDK)
try await withCheckedThrowingContinuation { (cont: CheckedContinuation<Void, Error>) in
sharePending.append(cont)
let media = WXImageObject()
media.imageData = img.imageData
let message = WXMediaMessage()
// 图片分享不需要 title / description(msext 字面:传 nil;这里传空串等价)
message.mediaObject = media
message.setThumbImage(img.thumb)
let req = SendMessageToWXReq()
req.bText = false
req.message = message
switch scene {
case .session: req.scene = Int32(WXSceneSession.rawValue)
case .timeline: req.scene = Int32(WXSceneTimeline.rawValue)
}
WXApi.send(req)
}
#else
throw WeChatError.sdkNotLinked
#endif
}
// MARK: - Internal: resolve callbacks // MARK: - Internal: resolve callbacks
fileprivate func resolveAuth(state: String, code: String?, errCode: Int) { fileprivate func resolveAuth(state: String, code: String?, errCode: Int) {
@@ -39,6 +39,22 @@ public enum ImageProvider {
} }
} }
/// 等比缩放图片用作微信分享的缩略图源。默认 0.4x 与 msext
/// `imageByScalingAndCroppingForSize:CGSizeMake(w*0.4, h*0.4)` 等价
/// (gameController.m:656 / 677 字面常量)。微信 SDK `setThumbImage:`
/// 内部会再压缩至 32KB,外层只需把图像尺寸压低,避免传 4K 截图过 SDK 时
/// 内部 JPEG 编码出超大缩略图。
public static func scaledThumb(_ image: UIImage, scale: CGFloat = 0.4) -> UIImage {
let target = CGSize(
width: max(1, image.size.width * scale),
height: max(1, image.size.height * scale)
)
let renderer = UIGraphicsImageRenderer(size: target)
return renderer.image { _ in
image.draw(in: CGRect(origin: .zero, size: target))
}
}
/// 下载远端 URL → UIImage。仅简单 URLSession 同步路径,无重试 / 缓存。 /// 下载远端 URL → UIImage。仅简单 URLSession 同步路径,无重试 / 缓存。
public static func downloadRemote(_ urlString: String) async throws -> UIImage { public static func downloadRemote(_ urlString: String) async throws -> UIImage {
guard let url = URL(string: urlString) else { guard let url = URL(string: urlString) else {
+30 -3
View File
@@ -9,6 +9,7 @@
// //
import Foundation import Foundation
import UIKit
#if canImport(WechatOpenSDK) #if canImport(WechatOpenSDK)
import WechatOpenSDK import WechatOpenSDK
@@ -37,18 +38,44 @@ public final class WechatShare: SharePlatform {
return .notInstalled(platform: name) return .notInstalled(platform: name)
} }
let wxScene: WeChatManager.ShareScene = (scene == .timeline) ? .timeline : .session let wxScene: WeChatManager.ShareScene = (scene == .timeline) ? .timeline : .session
// 当前仅链接分享(type == "1")走真实 SDK;截图 / 远端图分享待 Phase 4.F 接入
// WXImageObject + WXMediaMessage.thumbData
do { do {
switch content.type {
case "1":
// 链接分享(msext gameController.m:613-635)
try await WeChatManager.shared.shareLink( try await WeChatManager.shared.shareLink(
.init( .init(
url: content.webpageUrl, url: content.webpageUrl,
title: content.title, title: content.title,
desc: content.desc, desc: content.desc,
thumbnailURL: nil // type=2/3 缩略图待 Phase 4.F thumbnailURL: nil
), ),
scene: wxScene scene: wxScene
) )
case "2":
// 截图分享(msext gameController.m:637-658)
let shot = try ImageProvider.captureScreenshot()
guard let imageData = shot.jpegData(compressionQuality: 0.6) else {
return .failed(reason: "screenshot JPEG encode failed")
}
let thumb = ImageProvider.scaledThumb(shot)
try await WeChatManager.shared.shareImage(
.init(imageData: imageData, thumb: thumb),
scene: wxScene
)
default:
// 远端图分享(msext gameController.m:659-680:type 是其它值就当 URL 远端图)
let remote = try await ImageProvider.downloadRemote(content.webpageUrl)
// msext 直接用原始 data 作为大图;这里 UIImage 已是 decode 后的,
// 用 jpegData 重新编码维持等价(避免持有未压缩的原始字节膨胀)。
guard let imageData = remote.jpegData(compressionQuality: 0.9) else {
return .failed(reason: "remote image JPEG encode failed")
}
let thumb = ImageProvider.scaledThumb(remote)
try await WeChatManager.shared.shareImage(
.init(imageData: imageData, thumb: thumb),
scene: wxScene
)
}
return .success return .success
} catch let WeChatError.shareFailed(errCode) { } catch let WeChatError.shareFailed(errCode) {
if errCode == -2 { return .cancelled } if errCode == -2 { return .cancelled }