4.B 分享逻辑对齐 daoqi msext:调用即触发 sharesuccess

【契约影响】docs/H5-Native-Contract.md §3.1[2]/§3.2[11]

H5 端 sharesuccess 时机改为"调用 friendsShare handler 即立刻发",与
原 msext 行为等价(live path 完全不等 SDK / URL Scheme 回包)。用户
明示有意为之:URL Scheme + 剪贴板路径本就没有可靠回包,乐观策略避免
H5 业务卡住。

3 平台分支严格 1:1 对齐 daoqi msext:

WechatShare(对齐 WechatShareManager shareWithContent)
  - type=="2" → 截图 JPEG 0.6 + sendImageData + app icon thumb
  - else      → 链接分享 sendLinkURL + app icon thumb
  - 移除原"远端图"分支(原项目无此分支)
  - 新增 appIconImage helper(读 Info.plist CFBundleIcons,对齐
    [self getAppIconImage])

QQShare(对齐 QQShareManager shareWithContent + simpleShareToQQFriend)
  - type=="2" → 截图 → UIPasteboard.image → 4 URL Scheme fallback
  - else      → mqqapi://share/to_{fri,qzone}?req_type=1&url=...
                &title=...&description=...(webpageUrl 空时 req_type=0)
  - 不依赖 QQ SDK(CLAUDE.md ADR-006)

DouyinShare(对齐 DouyinShareManager shareWithContent)
  - type=="2" → 截图 → UIPasteboard.image → "立即打开抖音"引导框
  - else      → title+"\n\n"+desc → UIPasteboard.string → 引导框
                双侧空时兜底 "来自进贤聚友棋牌的精彩内容分享"
  - 放弃之前的"存相册 + 自动拉起"激进路径,回到原项目"用户主动点"路径

架构调整:
- SharePlatform.share 改 sync fire-and-forget(去 ShareResult 返回)
- ShareCenter.dispatch / SharePanel.show 去 completion 参数
- FriendsShareHandler:解析后先发 responseCallback("sharefriend")
  与 sharesuccess({success:"2", type:<sharefriend>}),再异步 dispatch
This commit is contained in:
joywayer
2026-06-24 07:34:03 +08:00
parent d471393806
commit 9bd5a81e6c
7 changed files with 259 additions and 219 deletions
@@ -5,16 +5,17 @@
// H5 Native handlerfriendsSharetypeUrlToptitleDescript // H5 Native handlerfriendsSharetypeUrlToptitleDescript
// docs/H5-Native-Contract.md §3.1 2 + §3.2 11sharesuccess // docs/H5-Native-Contract.md §3.1 2 + §3.2 11sharesuccess
// //
// msext gameController.m:575-597 // msext gameController.m:575-597
// 1. ShareContent // 1. ShareContent
// 2. ShareCenter.dispatch // 2. responseCallback("sharefriend")
// - sharefriend == "1" SharePanel / QQ / // 3. bridge.call("sharesuccess", { success:"2", type:<sharefriend > })
// - sharefriend == "2" WechatShare // ****H5 """" SDK
// 3. bridge.call("sharesuccess", { success: "2", type: <sharefriend > }) // URL Scheme /
// 4. fire-and-forget ShareCenter.dispatch UI
// //
// SDK // SDK
// - Phase 4.E SDK WechatShare stub // - WeChatManager 1.8.2 SDK
// - QQ + SDK URL SchemePhase 4.C/4.D stub // - QQ + SDK URL Scheme + CLAUDE.md ADR-006
// //
import Foundation import Foundation
@@ -28,6 +29,7 @@ public enum FriendsShareHandler {
// cb: "sharefriend"msext 沿 // cb: "sharefriend"msext 沿
// callbacksharesuccess({ success: "2", type: sharefriend }) // callbacksharesuccess({ success: "2", type: sharefriend })
bridge.register("friendsSharetypeUrlToptitleDescript") { [weak bridge] data, callback in bridge.register("friendsSharetypeUrlToptitleDescript") { [weak bridge] data, callback in
// 1. responseCallback msext "sharefriend"
callback?(.string("sharefriend")) callback?(.string("sharefriend"))
guard let obj = data?.asObject else { return } guard let obj = data?.asObject else { return }
@@ -39,25 +41,20 @@ public enum FriendsShareHandler {
title: obj["title"]?.asString ?? "", title: obj["title"]?.asString ?? "",
desc: obj["description"]?.asString ?? "" desc: obj["description"]?.asString ?? ""
) )
let sharefriend = content.sharefriend
await MainActor.run { await MainActor.run {
ShareCenter.dispatch(content) { result in // 2. sharesuccess
// success / cancelled / notInstalled / failed msext bridge?.call(
// sharesuccessURL Scheme "sharesuccess",
switch result {
case .success, .cancelled:
bridge?.call("sharesuccess",
data: .object([ data: .object([
"success": .string("2"), "success": .string("2"),
"type": .string(sharefriend) "type": .string(content.sharefriend)
]), ]),
callback: nil) callback: nil
case .notInstalled, .failed: )
// sharesuccessH5 timeout fallback
break // 3. fire-and-forget UI
} ShareCenter.dispatch(content)
}
} }
} }
} }
+96 -55
View File
@@ -2,19 +2,15 @@
// DouyinShare.swift // DouyinShare.swift
// ylgamehall // ylgamehall
// //
// SharePlatform Phase 4.D // SharePlatform 1:1 daoqi msext DouyinShareManager
// `shareWithContent:completion:` type
// - type == "2" UIPasteboard ""
// - else title + "\n\n" + desc UIPasteboard
// //
// msext DouyinShareManager.m:201-269 shareImageToDouyin // ** SDK**CLAUDE.md ADR-006 /
// 1. type=2 ImageProvider.captureScreenshot // + SDK
// ImageProvider.downloadRemotetype=1
// 退msext H5
// 2. PhotoLibrarySaver.save addOnly
// 3. UIPasteboard
// 4. snssdk1128://camera / publish / share/image / scheme
// canOpenURL
// 5. successURL Scheme
// //
// ** SDK** // fire-and-forgetsharesuccess FriendsShareHandler
// //
import UIKit import UIKit
@@ -27,68 +23,113 @@ public final class DouyinShare: SharePlatform {
private static let log = Logger(subsystem: "ylgamehall", category: "DouyinShare") private static let log = Logger(subsystem: "ylgamehall", category: "DouyinShare")
private static let douyinScheme = "snssdk1128://"
public let name = "Douyin" public let name = "Douyin"
public var isInstalled: Bool { public var isInstalled: Bool {
guard let url = URL(string: "snssdk1128://") else { return false } guard let url = URL(string: Self.douyinScheme) else { return false }
return UIApplication.shared.canOpenURL(url) return UIApplication.shared.canOpenURL(url)
} }
public init() {} public init() {}
public func share(_ content: ShareContent, scene: ShareScene) async -> ShareResult { public func share(_ content: ShareContent, scene: ShareScene) {
guard isInstalled else { return .notInstalled(platform: name) } guard isInstalled else { return }
// 1. if content.type == "2" {
let image: UIImage shareScreenshot()
} else {
shareText(content)
}
}
// MARK: -
/// msext DouyinShareManager.m:139-162 isScreenshotShare
/// pasteboard.image ""
private func shareScreenshot() {
do { do {
switch content.type { let shot = try ImageProvider.captureScreenshot()
case "2": UIPasteboard.general.image = shot
image = try ImageProvider.captureScreenshot() Self.log.debug("DouyinShare 截图已复制到剪贴板")
case "1":
// 退msext
Self.log.debug("DouyinShare: type=1 链接,退化为截屏分享")
image = try ImageProvider.captureScreenshot()
default:
// type == URL
image = try await ImageProvider.downloadRemote(content.webpageUrl)
}
} catch { } catch {
Self.log.error("DouyinShare: 获取图片失败 \(error.localizedDescription, privacy: .public)") Self.log.error("DouyinShare 截图失败 \(error.localizedDescription, privacy: .public)")
return .failed(reason: "获取图片失败: \(error.localizedDescription)") return
} }
// 2. let message = """
do { ✅ 图片已复制到剪贴板
try await PhotoLibrarySaver.save(image)
} catch PhotoLibrarySaverError.permissionDenied { 🎯 分享到好友或群聊步骤:
Self.log.warning("DouyinShare: 相册写入权限被拒") 1️⃣ 打开抖音,点击右下角「消息」
return .failed(reason: "相册写入权限被拒") 2️⃣ 选择好友或群聊进入聊天
} catch { 3️⃣ 在输入框长按粘贴图片
Self.log.error("DouyinShare: 保存相册失败 \(error.localizedDescription, privacy: .public)") 4️⃣ 点击发送即可分享
return .failed(reason: "保存相册失败: \(error.localizedDescription)") """
showGuidanceAlert(title: "抖音分享准备完成", message: message)
} }
// 3. msext // MARK: -
UIPasteboard.general.image = image
// 4. msext URL Scheme /// msext DouyinShareManager.m:163-196 else
let schemes = [ /// title + "\n\n" + desc pasteboard.string
"snssdk1128://camera", // /// msext ""
"snssdk1128://publish", // private func shareText(_ content: ShareContent) {
"snssdk1128://share/image", // var shareText = ""
"snssdk1128://" // scheme if !content.title.isEmpty {
] shareText = content.title
}
if !content.desc.isEmpty {
shareText = shareText.isEmpty ? content.desc : "\(shareText)\n\n\(content.desc)"
}
if shareText.isEmpty {
shareText = "来自进贤聚友棋牌的精彩内容分享"
}
UIPasteboard.general.string = shareText
Self.log.debug("DouyinShare 文字已复制到剪贴板")
for schemeString in schemes { let message = """
guard let url = URL(string: schemeString), ✅ 内容已复制到剪贴板
UIApplication.shared.canOpenURL(url)
else { continue } 🎯 分享到好友或群聊步骤:
Self.log.debug("DouyinShare open: \(schemeString, privacy: .public)") 1️⃣ 打开抖音,点击右下角「消息」
let opened = await UIApplication.shared.open(url) 2️⃣ 选择好友或群聊进入聊天
if opened { return .success } 3️⃣ 在输入框长按粘贴内容
4️⃣ 点击发送即可分享
"""
showGuidanceAlert(title: "抖音分享准备完成", message: message)
} }
return .failed(reason: "所有抖音 URL Scheme 都调起失败") // MARK: -
/// " / " Alert msext
/// `showGuidanceAlertWithTitle:message:`
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.douyinScheme),
UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url)
}
})
alert.addAction(UIAlertAction(title: "稍后分享", style: .cancel))
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
}
return top
} }
} }
+53 -37
View File
@@ -2,21 +2,18 @@
// QQShare.swift // QQShare.swift
// ylgamehall // ylgamehall
// //
// QQ SharePlatform Phase 4.C // QQ SharePlatform daoqi msext QQShareManager
// `shareWithContent:completion:` type
// - type == "2" UIPasteboard + mqqapi://
// - else mqqapi://share/to_fri?req_type=1&url=...
// //
// msext QQShareManager.m:716-771 simpleShareToQQFriend URL Scheme // ** QQ SDK**CLAUDE.md ADR-006 SDK
// fallback** QQ SDK** // URL Scheme + pasteboard UX
//
// URL
// mqqapi://share/to_fri?version=1&cflag=0&req_type=1
// &url=<encoded webpageUrl>&title=<encoded title>&description=<encoded desc>
// //
// encodeString msext line 1262-1268 unreserved RFC 3986 // encodeString msext line 1262-1268 unreserved RFC 3986
// alphanumeric + "-._~" percent encode // alphanumeric + "-._~" percent encode
// //
// type == "1"type == "2" / // fire-and-forgetsharesuccess FriendsShareHandler
// Phase 4.F UIGraphicsImageRenderer + Photos / +
// mqqapi://share/to_fri?file_type=img
// //
import UIKit import UIKit
@@ -38,28 +35,57 @@ public final class QQShare: SharePlatform {
public init() {} public init() {}
public func share(_ content: ShareContent, scene: ShareScene) async -> ShareResult { public func share(_ content: ShareContent, scene: ShareScene) {
guard isInstalled else { return .notInstalled(platform: name) } guard isInstalled else { return }
// URL Scheme Phase 4.F if content.type == "2" {
guard content.type == "1" else { shareScreenshot()
Self.log.warning("QQShare: type=\(content.type, privacy: .public) (非链接) 暂未实现,返回 success 避免 H5 业务卡住") } else {
return .success shareLink(content, scene: scene)
}
} }
// URLmqqapi://share/to_fri? mqqapi://share/to_qzone? // MARK: -
// scene == .timeline QQ to_qzone.friend to_friQQ
let host = scene == .timeline ? "share/to_qzone" : "share/to_fri" /// msext QQShareManager.m:1876-1906 isScreenshotShare
var parts: [String] = [ /// QQ SDK shareToQQFriend QQShareTypeImage UIImage SDK
"version=1", /// SDK 退 pasteboard QQ
"cflag=0", private func shareScreenshot() {
"req_type=1" do {
let shot = try ImageProvider.captureScreenshot()
UIPasteboard.general.image = shot
} catch {
Self.log.error("QQShare 截图失败 \(error.localizedDescription, privacy: .public)")
}
let candidates = [
"mqqapi://share/to_fri?file_type=news&version=1&cflag=0",
"mqq://im/chat?chat_type=select",
"mqqapi://",
"mqq://"
] ]
for s in candidates {
guard let url = URL(string: s),
UIApplication.shared.canOpenURL(url) else { continue }
UIApplication.shared.open(url)
return
}
}
// MARK: -
/// msext QQShareManager.m simpleShareToQQFriend
/// mqqapi://share/to_fri?version=1&cflag=0&req_type=1
/// &url=<encoded webpageUrl>&title=<encoded title>&description=<encoded desc>
/// scene == .timeline QQ (to_qzone).friend QQ (to_fri)
private func shareLink(_ content: ShareContent, scene: ShareScene) {
let host = scene == .timeline ? "share/to_qzone" : "share/to_fri"
var parts: [String] = ["version=1", "cflag=0"]
if !content.webpageUrl.isEmpty { if !content.webpageUrl.isEmpty {
parts.append("req_type=1")
parts.append("url=\(Self.encode(content.webpageUrl))") parts.append("url=\(Self.encode(content.webpageUrl))")
} else { } else {
// URL req_type=0 parts.append("req_type=0")
parts = ["version=1", "cflag=0", "req_type=0"]
} }
if !content.title.isEmpty { if !content.title.isEmpty {
parts.append("title=\(Self.encode(content.title))") parts.append("title=\(Self.encode(content.title))")
@@ -69,21 +95,11 @@ public final class QQShare: SharePlatform {
} }
let urlString = "mqqapi://\(host)?\(parts.joined(separator: "&"))" let urlString = "mqqapi://\(host)?\(parts.joined(separator: "&"))"
Self.log.debug("QQShare open: \(urlString, privacy: .public)")
guard let qqURL = URL(string: urlString), guard let qqURL = URL(string: urlString),
UIApplication.shared.canOpenURL(qqURL) UIApplication.shared.canOpenURL(qqURL) else { return }
else { UIApplication.shared.open(qqURL)
return .failed(reason: "invalid QQ URL")
} }
// URL Scheme msext success
let opened = await UIApplication.shared.open(qqURL)
return opened ? .success : .failed(reason: "openURL failed")
}
/// msext QQShareManager.m:1262-1268 RFC 3986 unreserved
/// alphanumeric + "-._~" percent encode
nonisolated static func encode(_ string: String) -> String { nonisolated static func encode(_ string: String) -> String {
var allowed = CharacterSet.alphanumerics var allowed = CharacterSet.alphanumerics
allowed.insert(charactersIn: "-._~") allowed.insert(charactersIn: "-._~")
+6 -13
View File
@@ -6,7 +6,8 @@
// - sharefriend == "1"/ SharePanel /QQ/ // - sharefriend == "1"/ SharePanel /QQ/
// - sharefriend == "2" WechatShare timeline // - sharefriend == "2" WechatShare timeline
// //
// Design §8.2 ShareKit + SharePlatform // fire-and-forgetH5 sharesuccess FriendsShareHandler
// UI / SDK
// //
import Foundation import Foundation
@@ -14,27 +15,19 @@ import Foundation
@MainActor @MainActor
public enum ShareCenter { public enum ShareCenter {
/// FriendsShareHandler handler public static func dispatch(_ content: ShareContent) {
/// completion UI await cb msext
public static func dispatch(_ content: ShareContent, completion: @escaping (ShareResult) -> Void) {
switch content.sharefriend { switch content.sharefriend {
case "1": case "1":
// / SharePanel // / SharePanel
SharePanel.show(content: content, completion: completion) SharePanel.show(content: content)
case "2": case "2":
// WechatSharetimeline // WechatSharetimeline
Task { @MainActor in WechatShare.shared.share(content, scene: .timeline)
let result = await WechatShare.shared.share(content, scene: .timeline)
completion(result)
}
default: default:
// sharefriendmsext 沿 // sharefriendmsext 沿
Task { @MainActor in WechatShare.shared.share(content, scene: .timeline)
let result = await WechatShare.shared.share(content, scene: .timeline)
completion(result)
}
} }
} }
} }
+11 -17
View File
@@ -27,13 +27,11 @@ public final class SharePanel: UIView {
private static let animationDuration: TimeInterval = 0.25 private static let animationDuration: TimeInterval = 0.25
private let content: ShareContent private let content: ShareContent
private let completion: (ShareResult) -> Void
private let contentView = UIView() private let contentView = UIView()
public init(content: ShareContent, completion: @escaping (ShareResult) -> Void) { public init(content: ShareContent) {
self.content = content self.content = content
self.completion = completion
super.init(frame: UIScreen.main.bounds) super.init(frame: UIScreen.main.bounds)
backgroundColor = UIColor(white: 0, alpha: 0) backgroundColor = UIColor(white: 0, alpha: 0)
setupUI() setupUI()
@@ -46,13 +44,11 @@ public final class SharePanel: UIView {
// MARK: - // MARK: -
/// /// fire-and-forgetsharesuccess FriendsShareHandler
public static func show(content: ShareContent, completion: @escaping (ShareResult) -> Void) { /// H5
guard let window = topWindow() else { public static func show(content: ShareContent) {
completion(.failed(reason: "no key window")) guard let window = topWindow() else { return }
return let panel = SharePanel(content: content)
}
let panel = SharePanel(content: content, completion: completion)
window.addSubview(panel) window.addSubview(panel)
panel.animateIn() panel.animateIn()
} }
@@ -146,7 +142,7 @@ public final class SharePanel: UIView {
@objc private func handleBackgroundTap(_ gesture: UITapGestureRecognizer) { @objc private func handleBackgroundTap(_ gesture: UITapGestureRecognizer) {
let point = gesture.location(in: self) let point = gesture.location(in: self)
if !contentView.frame.contains(point) { if !contentView.frame.contains(point) {
dismiss { [self] in completion(.cancelled) } dismiss()
} }
} }
@@ -155,12 +151,10 @@ public final class SharePanel: UIView {
let platform = platforms[sender.tag] let platform = platforms[sender.tag]
let scene: ShareScene = content.sharefriend == "2" ? .timeline : .friend let scene: ShareScene = content.sharefriend == "2" ? .timeline : .friend
let snapshotContent = content let snapshotContent = content
dismiss { dismiss { [weak platform] in
Task { @MainActor in guard let platform else { return }
let result = await platform.share(snapshotContent, scene: scene) Self.log.debug("share via \(platform.name, privacy: .public)")
Self.log.debug("share via \(platform.name, privacy: .public) result=\(String(describing: result), privacy: .public)") platform.share(snapshotContent, scene: scene)
self.completion(result)
}
} }
} }
} }
+13 -15
View File
@@ -2,10 +2,15 @@
// SharePlatform.swift // SharePlatform.swift
// ylgamehall // ylgamehall
// //
// + ShareContent + ShareResult // + ShareContent
// //
// / QQ / SharePlatform ShareCenter // / QQ / SharePlatform ShareCenter
// sharefriend / conformance // sharefriend /
//
// **fire-and-forget **share H5
// sharesuccess FriendsShareHandler msext
// gameController.m "" share()
// UI / URL Scheme
// //
// docs/H5-Native-Implementation-Design.md §8.2 ShareKit // docs/H5-Native-Implementation-Design.md §8.2 ShareKit
// //
@@ -22,9 +27,9 @@ nonisolated public struct ShareContent: Sendable {
/// msext 1 / 2 / 3 SharePanel /// msext 1 / 2 / 3 SharePanel
/// §3.1 2 /// §3.1 2
public let sharetype: String public let sharetype: String
/// 1 / 2 / = URL /// 1 / 2 / = WechatShareManager
public let type: String public let type: String
/// URL URL /// URL
public let webpageUrl: String public let webpageUrl: String
public let title: String public let title: String
public let desc: String public let desc: String
@@ -46,13 +51,6 @@ nonisolated public struct ShareContent: Sendable {
} }
} }
public enum ShareResult: Sendable {
case success
case cancelled
case notInstalled(platform: String)
case failed(reason: String)
}
/// / QQ / /// / QQ /
public enum ShareScene: Sendable { public enum ShareScene: Sendable {
case friend // / QQ case friend // / QQ
@@ -61,10 +59,10 @@ public enum ShareScene: Sendable {
@MainActor @MainActor
public protocol SharePlatform: AnyObject { public protocol SharePlatform: AnyObject {
/// log / /// log
var name: String { get } var name: String { get }
/// app SharePanel /// app SharePanel
var isInstalled: Bool { get } var isInstalled: Bool { get }
/// content.type / / /// UIfire-and-forgetsharesuccess handler
func share(_ content: ShareContent, scene: ShareScene) async -> ShareResult func share(_ content: ShareContent, scene: ShareScene)
} }
+42 -41
View File
@@ -2,17 +2,20 @@
// WechatShare.swift // WechatShare.swift
// ylgamehall // ylgamehall
// //
// SharePlatform // SharePlatform 1:1 daoqi msext WechatShareManager
// `shareWithContent:completion:`
// - type == "2" getImageWithFullScreenshot + JPEG 0.6 + sendImageData
// - else sendLinkURL + webpageUrl + title + desc
// //
// canImport(WechatOpenSDK) SDK isInstalled = true / share = .success // app icon `[self getAppIconImage]` sharelogo
// SharePanel Xcode Embed & Sign //
// fire-and-forgetH5 sharesuccess FriendsShareHandler
// dispatch_after 1s completion(YES)
// //
import Foundation import Foundation
import UIKit import UIKit
// SDK 1.8.2 Bridging Header import
@MainActor @MainActor
public final class WechatShare: SharePlatform { public final class WechatShare: SharePlatform {
@@ -26,15 +29,24 @@ public final class WechatShare: SharePlatform {
public init() {} public init() {}
public func share(_ content: ShareContent, scene: ShareScene) async -> ShareResult { public func share(_ content: ShareContent, scene: ShareScene) {
guard WXApi.isWXAppInstalled() else { guard WXApi.isWXAppInstalled() else { return }
return .notInstalled(platform: name)
}
let wxScene: WeChatManager.ShareScene = (scene == .timeline) ? .timeline : .session let wxScene: WeChatManager.ShareScene = (scene == .timeline) ? .timeline : .session
let thumb = Self.appIconImage()
Task { @MainActor in
do { do {
switch content.type { if content.type == "2" {
case "1": // msext WechatShareManager isScreenshotShare
// msext gameController.m:613-635 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),
scene: wxScene
)
} else {
// msext else type=="1"
try await WeChatManager.shared.shareLink( try await WeChatManager.shared.shareLink(
.init( .init(
url: content.webpageUrl, url: content.webpageUrl,
@@ -44,37 +56,26 @@ public final class WechatShare: SharePlatform {
), ),
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-680type 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
} catch let WeChatError.shareFailed(errCode) {
if errCode == -2 { return .cancelled }
return .failed(reason: "WeChat errCode \(errCode)")
} catch { } catch {
return .failed(reason: "\(error)") // SDK H5 sharesuccess
} }
} }
} }
/// 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
}
if let icon = UIImage(named: "AppIcon") { return icon }
let renderer = UIGraphicsImageRenderer(size: CGSize(width: 1, height: 1))
return renderer.image { _ in }
}
}