You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift中获取图片大小并优化:YPImagePicker图片压缩问题

Swift中YPImagePicker图片处理:准确获取大小+压缩至1MB以内的最优方案

问题根源

你之前的代码计算的是图片转成JPEG后的大小,而相册显示的是原始图片(比如HEIC格式)的文件大小,两种格式压缩逻辑不同,导致数值不符。要解决问题,得先获取原始文件的准确大小,再做针对性压缩。

一、获取图片原始大小的正确方式

根据图片来源(相册/相机),用不同的方法获取原始文件大小:

1. 相册选中的图片(PHAsset)

通过YPImagePicker返回的YPPhoto.asset属性,读取PHAsset的原始资源大小:

func getAssetOriginalSize(asset: PHAsset, completion: @escaping (Double?) -> Void) {
    let resources = PHAssetResource.assetResources(for: asset)
    guard let targetResource = resources.first else {
        completion(nil)
        return
    }
    // 读取原始文件大小(单位:MB)
    let fileSizeBytes = targetResource.value(forKey: "fileSize") as? Int64
    let fileSizeMB = fileSizeBytes.flatMap { Double($0) / 1024 / 1024 }
    completion(fileSizeMB)
}

2. 相机拍摄的图片

YPImagePicker拍摄的图片会生成临时本地文件,通过YPPhoto.imageURL读取文件大小:

func getLocalFileSize(url: URL) -> Double? {
    do {
        let fileAttributes = try FileManager.default.attributesOfItem(atPath: url.path)
        let fileSizeBytes = fileAttributes[.size] as? Int64
        return fileSizeBytes.flatMap { Double($0) / 1024 / 1024 }
    } catch {
        return nil
    }
}

二、图片压缩至1MB以内的最优逻辑

优先通过降低JPEG压缩质量来缩小体积,若压缩到最低质量仍超标,再缩小图片尺寸,平衡画质和大小:

func compressImageToMax1MB(_ image: UIImage) -> Data? {
    let maxSizeBytes = 1 * 1024 * 1024 // 1MB对应的字节数
    var compressionQuality: CGFloat = 1.0
    guard var compressedData = image.jpegData(compressionQuality: compressionQuality) else {
        return nil
    }
    
    // 渐进式降低压缩质量,直到符合大小要求或质量低于0.1
    while Double(compressedData.count) > maxSizeBytes && compressionQuality > 0.1 {
        compressionQuality -= 0.1
        if let newData = image.jpegData(compressionQuality: compressionQuality) {
            compressedData = newData
        } else {
            break
        }
    }
    
    // 若压缩质量到0.1仍超标,缩小图片尺寸
    if Double(compressedData.count) > maxSizeBytes {
        let scaleFactor = sqrt(maxSizeBytes / Double(compressedData.count))
        let newSize = CGSize(
            width: image.size.width * scaleFactor,
            height: image.size.height * scaleFactor
        )
        UIGraphicsBeginImageContextWithOptions(newSize, false, image.scale)
        image.draw(in: CGRect(origin: .zero, size: newSize))
        let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        compressedData = scaledImage?.jpegData(compressionQuality: 0.8) ?? compressedData
    }
    
    return compressedData
}

三、整合到YPImagePicker的完整流程

在图片选择回调中,结合大小判断和压缩逻辑,处理最多5张图片:

// 配置YPImagePicker(限制最多5张图片)
var config = YPImagePickerConfiguration()
config.screens = [.library, .photo]
config.library.maxNumberOfItems = 5

let picker = YPImagePicker(configuration: config)
picker.didFinishPicking { [weak self] items, _ in
    guard let self = self else { return }
    
    // 遍历选中的图片(最多5张)
    for item in items {
        guard case .photo(let photo) = item else { continue }
        
        // 区分相册/相机图片,获取原始大小
        if let asset = photo.asset {
            self.getAssetOriginalSize(asset: asset) { mbSize in
                guard let size = mbSize else { return }
                if size > 1.0 {
                    // 超过1MB,压缩后发送
                    if let compressedData = self.compressImageToMax1MB(photo.image) {
                        // 调用REST API发送compressedData到后端
                        print("压缩后大小:\(Double(compressedData.count)/1024/1024) MB")
                    }
                } else {
                    // 直接发送原始格式数据(若后端支持HEIC,可读取asset原始数据;否则转JPEG)
                    if let imageData = photo.image.jpegData(compressionQuality: 1.0) {
                        // 调用REST API发送imageData
                    }
                }
            }
        } else if let imageURL = photo.imageURL {
            if let mbSize = self.getLocalFileSize(url: imageURL) {
                if mbSize > 1.0 {
                    if let compressedData = self.compressImageToMax1MB(photo.image) {
                        // 发送压缩后的数据
                    }
                } else {
                    // 读取原始文件数据发送
                    do {
                        let originalData = try Data(contentsOf: imageURL)
                        // 调用REST API发送originalData
                    } catch {
                        print("读取原始图片失败:\(error.localizedDescription)")
                    }
                }
            }
        }
    }
    
    picker.dismiss(animated: true)
}
present(picker, animated: true)

注意事项

  • 若后端支持HEIC格式,建议直接发送原始HEIC数据(通过PHAsset读取原始资源或读取相机拍摄的本地文件),比JPEG更省体积且画质更好。
  • 压缩时的质量阈值(0.1)和缩放后的质量(0.8)可根据你的需求调整,平衡画质和大小。

内容的提问来源于stack exchange,提问作者Prabhu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 07:45:35