Files
youle_app_ios_v2/ylgamehall/Source/Share/PhotoLibrarySaver.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

56 lines
1.9 KiB
Swift

//
// PhotoLibrarySaver.swift
// ylgamehall
//
// 保存 UIImage 到系统相册(with addOnly 权限检查)。
//
// iOS 14+ 推荐用 PHAccessLevel.addOnly(仅写入相册不读取),权限弹窗
// 比 readWrite 更友好。Info.plist 需配 NSPhotoLibraryAddUsageDescription。
//
// Phase 4.D / 4.F 分享场景用:抖音 + QQ 图片分享前先保存到相册。
//
import UIKit
import Photos
public enum PhotoLibrarySaverError: Error, Sendable {
case permissionDenied
case saveFailed(any Error)
}
@MainActor
public enum PhotoLibrarySaver {
/// 保存图片到相册。内部处理权限(addOnly 级别)。
public static func save(_ image: UIImage) async throws {
let status = PHPhotoLibrary.authorizationStatus(for: .addOnly)
switch status {
case .authorized, .limited:
break
case .denied, .restricted:
throw PhotoLibrarySaverError.permissionDenied
case .notDetermined:
let newStatus = await PHPhotoLibrary.requestAuthorization(for: .addOnly)
guard newStatus == .authorized || newStatus == .limited else {
throw PhotoLibrarySaverError.permissionDenied
}
@unknown default:
throw PhotoLibrarySaverError.permissionDenied
}
try await withCheckedThrowingContinuation { (cont: CheckedContinuation<Void, Error>) in
PHPhotoLibrary.shared().performChanges {
PHAssetChangeRequest.creationRequestForAsset(from: image)
} completionHandler: { success, error in
if success {
cont.resume()
} else {
cont.resume(throwing: PhotoLibrarySaverError.saveFailed(
error ?? NSError(domain: "PhotoLibrarySaver", code: -1)
))
}
}
}
}
}