实现我的 Tab、对齐 Android 流程并接入 OSS 上传
This commit is contained in:
139
suixinkan/Core/Upload/UploadImageProcessors.swift
Normal file
139
suixinkan/Core/Upload/UploadImageProcessors.swift
Normal file
@ -0,0 +1,139 @@
|
||||
//
|
||||
// UploadImageProcessors.swift
|
||||
// suixinkan
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
/// 头像图片处理器,负责压缩和规范化用户选择的头像图片。
|
||||
enum AvatarImageProcessor {
|
||||
static let maxPixelLength: CGFloat = 1024
|
||||
static let jpegCompressionQuality: CGFloat = 0.82
|
||||
|
||||
/// 处理后的上传图片实体,包含数据、文件名和内容类型。
|
||||
struct ProcessedImage: Equatable {
|
||||
let data: Data
|
||||
let fileName: String
|
||||
let contentType: UTType
|
||||
}
|
||||
|
||||
/// 将原始图片数据转换为适合头像上传的 JPEG 数据。
|
||||
static func process(data: Data, timestamp: TimeInterval = Date().timeIntervalSince1970) throws -> ProcessedImage {
|
||||
try UploadImageRenderer.process(
|
||||
data: data,
|
||||
fileNamePrefix: "avatar",
|
||||
maxPixelLength: maxPixelLength,
|
||||
jpegCompressionQuality: jpegCompressionQuality,
|
||||
timestamp: timestamp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// 实名证件图片处理器,负责压缩和规范化身份证正反面图片。
|
||||
enum RealNameImageProcessor {
|
||||
static let maxPixelLength: CGFloat = 1800
|
||||
static let jpegCompressionQuality: CGFloat = 0.88
|
||||
|
||||
/// 将原始图片数据转换为适合实名认证上传的 JPEG 数据。
|
||||
static func process(data: Data, side: RealNameImageSide, timestamp: TimeInterval = Date().timeIntervalSince1970) throws -> AvatarImageProcessor.ProcessedImage {
|
||||
try UploadImageRenderer.process(
|
||||
data: data,
|
||||
fileNamePrefix: "real_name_\(side.rawValue)",
|
||||
maxPixelLength: maxPixelLength,
|
||||
jpegCompressionQuality: jpegCompressionQuality,
|
||||
timestamp: timestamp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// 银行卡图片处理器,负责压缩银行卡正反面图片。
|
||||
enum BankCardImageProcessor {
|
||||
static let maxPixelLength: CGFloat = 1800
|
||||
static let jpegCompressionQuality: CGFloat = 0.88
|
||||
|
||||
static func process(data: Data, side: BankCardImageSide, timestamp: TimeInterval = Date().timeIntervalSince1970) throws -> AvatarImageProcessor.ProcessedImage {
|
||||
try UploadImageRenderer.process(
|
||||
data: data,
|
||||
fileNamePrefix: "bank_card_\(side.rawValue)",
|
||||
maxPixelLength: maxPixelLength,
|
||||
jpegCompressionQuality: jpegCompressionQuality,
|
||||
timestamp: timestamp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// 实名认证证件图片面向实体,区分人像面和国徽面。
|
||||
enum RealNameImageSide: String {
|
||||
case front
|
||||
case back
|
||||
}
|
||||
|
||||
/// 银行卡图片面向实体。
|
||||
enum BankCardImageSide: String {
|
||||
case front
|
||||
case back
|
||||
}
|
||||
|
||||
/// 上传图片处理错误实体,表示图片无效或编码失败。
|
||||
enum UploadImageProcessingError: LocalizedError, Equatable {
|
||||
case invalidImage
|
||||
case encodingFailed
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
case .invalidImage:
|
||||
"请选择有效的图片"
|
||||
case .encodingFailed:
|
||||
"图片处理失败,请重新选择"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 上传图片渲染器,提供通用图片缩放、铺底和 JPEG 编码能力。
|
||||
private enum UploadImageRenderer {
|
||||
static func process(
|
||||
data: Data,
|
||||
fileNamePrefix: String,
|
||||
maxPixelLength: CGFloat,
|
||||
jpegCompressionQuality: CGFloat,
|
||||
timestamp: TimeInterval
|
||||
) throws -> AvatarImageProcessor.ProcessedImage {
|
||||
guard let image = UIImage(data: data), image.size.width > 0, image.size.height > 0 else {
|
||||
throw UploadImageProcessingError.invalidImage
|
||||
}
|
||||
|
||||
let normalized = image.normalizedForUpload(maxPixelLength: maxPixelLength)
|
||||
guard let jpegData = normalized.jpegData(compressionQuality: jpegCompressionQuality), !jpegData.isEmpty else {
|
||||
throw UploadImageProcessingError.encodingFailed
|
||||
}
|
||||
|
||||
return AvatarImageProcessor.ProcessedImage(
|
||||
data: jpegData,
|
||||
fileName: "\(fileNamePrefix)_\(Int(timestamp)).jpg",
|
||||
contentType: .jpeg
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private extension UIImage {
|
||||
func normalizedForUpload(maxPixelLength: CGFloat) -> UIImage {
|
||||
let longestSide = max(size.width, size.height)
|
||||
let scale = min(1, maxPixelLength / longestSide)
|
||||
let targetSize = CGSize(
|
||||
width: max(1, (size.width * scale).rounded()),
|
||||
height: max(1, (size.height * scale).rounded())
|
||||
)
|
||||
|
||||
let format = UIGraphicsImageRendererFormat.default()
|
||||
format.scale = 1
|
||||
format.opaque = true
|
||||
let renderer = UIGraphicsImageRenderer(size: targetSize, format: format)
|
||||
return renderer.image { context in
|
||||
UIColor.white.setFill()
|
||||
context.fill(CGRect(origin: .zero, size: targetSize))
|
||||
draw(in: CGRect(origin: .zero, size: targetSize))
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user