Files
youle_app_ios_v2/ylgamehall/Source/Share/DouyinShare.swift
T
joywayerandClaude Opus 4.7 39c1b5164d Phase 4.D:DouyinShare URL Scheme + Photos 真实落地(不依赖 SDK)
移植 msext DouyinShareManager.m:201-269 shareImageToDouyin 完整流程到 Swift。

新增基础设施(Phase 4.F 截图 / 远端图也共用):

Source/Share/ImageProvider.swift:
  - captureScreenshot() throws -> UIImage:UIGraphicsImageRenderer 截
    keyWindow(含状态栏 + letterbox 黑边 + WebView),与 msext
    FuncPublic.getImageWithFullScreenshot 等价
  - downloadRemote(_:) async throws -> UIImage:URLSession.data(from:)
    下载远端 URL → UIImage 解码
  - ImageProviderError 错误分类(noKeyWindow / downloadFailed / decodeFailed /
    badURL)

Source/Share/PhotoLibrarySaver.swift:
  - save(_:) async throws:使用 PHAccessLevel.addOnly(iOS 14+ 推荐,仅写
    相册不读,弹窗更友好)+ PHAssetChangeRequest.creationRequestForAsset
  - 自动处理权限三态(authorized/notDetermined 请求 + denied/restricted 抛错)

DouyinShare.share 完整实现:
  - 拿图片源:
    * type == "2" 截图 / type == "1" 链接(退化为当前屏截屏,msext 行为)
      → ImageProvider.captureScreenshot
    * type == 其它(远端图 URL)→ ImageProvider.downloadRemote(webpageUrl)
  - PhotoLibrarySaver.save 保存到相册(含 .permissionDenied 错误回 .failed)
  - UIPasteboard.general.image = image 兜底(抖音可能从剪贴板读图)
  - 按 msext 顺序尝试 4 个 URL Scheme:
    snssdk1128://camera / publish / share/image / 基础 scheme
    canOpenURL 命中即 await UIApplication.open + 调起即 success
  - 全部失败返回 .failed("所有抖音 URL Scheme 都调起失败")

Info.plist 加 NSPhotoLibraryAddUsageDescription("需要将分享内容保存到
相册,以便您在抖音中选取分享"),addOnly 权限弹窗用此文案。

BuildProject 通过

Plan §5 Phase 4.4 勾选

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-22 20:07:45 +08:00

95 lines
3.7 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// DouyinShare.swift
// ylgamehall
//
// 抖音分享 SharePlatform 实现(Phase 4.D 完整)。
//
// 移植 msext DouyinShareManager.m:201-269 shareImageToDouyin 流程:
// 1. 拿图片源(type=2 → ImageProvider.captureScreenshot;其它 →
// ImageProvider.downloadRemote);type=1 链接 → 抖音不原生支持纯链接,
// 退化为截屏分享(msext 行为:所有 H5 分享到抖音都走图片)
// 2. PhotoLibrarySaver.save 保存到相册(addOnly 权限)
// 3. UIPasteboard 兜底放图片数据,供抖音读取
// 4. 按顺序尝试 snssdk1128://camera / publish / share/image / 基础 scheme
// canOpenURL 命中即调起
// 5. 调起即视为 successURL Scheme 单向无返回)
//
// **不依赖抖音 SDK**。
//
import UIKit
import os.log
@MainActor
public final class DouyinShare: SharePlatform {
public static let shared = DouyinShare()
private static let log = Logger(subsystem: "ylgamehall", category: "DouyinShare")
public let name = "Douyin"
public var isInstalled: Bool {
guard let url = URL(string: "snssdk1128://") else { return false }
return UIApplication.shared.canOpenURL(url)
}
public init() {}
public func share(_ content: ShareContent, scene: ShareScene) async -> ShareResult {
guard isInstalled else { return .notInstalled(platform: name) }
// 1. 拿图片源
let image: UIImage
do {
switch content.type {
case "2":
image = try ImageProvider.captureScreenshot()
case "1":
// 抖音不支持纯链接分享,退化为当前屏截屏(msext 行为)
Self.log.debug("DouyinShare: type=1 链接,退化为截屏分享")
image = try ImageProvider.captureScreenshot()
default:
// type == 其它 → 远端图片 URL
image = try await ImageProvider.downloadRemote(content.webpageUrl)
}
} catch {
Self.log.error("DouyinShare: 获取图片失败 \(error.localizedDescription, privacy: .public)")
return .failed(reason: "获取图片失败: \(error.localizedDescription)")
}
// 2. 保存到相册(含权限检查)
do {
try await PhotoLibrarySaver.save(image)
} catch PhotoLibrarySaverError.permissionDenied {
Self.log.warning("DouyinShare: 相册写入权限被拒")
return .failed(reason: "相册写入权限被拒")
} catch {
Self.log.error("DouyinShare: 保存相册失败 \(error.localizedDescription, privacy: .public)")
return .failed(reason: "保存相册失败: \(error.localizedDescription)")
}
// 3. 同时放剪贴板兜底(msext 行为:抖音从剪贴板读图)
UIPasteboard.general.image = image
// 4. 按 msext 顺序尝试多个 URL Scheme
let schemes = [
"snssdk1128://camera", // 抖音拍摄界面
"snssdk1128://publish", // 发布界面
"snssdk1128://share/image", // 图片分享(可能已废弃但保留)
"snssdk1128://" // 兜底基础 scheme
]
for schemeString in schemes {
guard let url = URL(string: schemeString),
UIApplication.shared.canOpenURL(url)
else { continue }
Self.log.debug("DouyinShare open: \(schemeString, privacy: .public)")
let opened = await UIApplication.shared.open(url)
if opened { return .success }
}
return .failed(reason: "所有抖音 URL Scheme 都调起失败")
}
}