Commit 3f3bdcb5 authored by CZ1004's avatar CZ1004

【优化】调整压缩动画以及细节

parent 87820404
{
"images" : [
{
"filename" : "Group_1171275116.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Group_1171275116@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Group_1171275116@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
......@@ -25,7 +25,7 @@ class CompressCompletedViewController : BaseViewController{
}
}
var comVideoDataSource : [URL?] = []{
var comVideoDataSource : [URL] = []{
didSet{
if let url = comVideoDataSource.first{
DispatchQueue.main.async {
......@@ -225,6 +225,8 @@ class CompressCompletedViewController : BaseViewController{
}
// MARK: 事件方法
/// 保留两个
@objc func keepButtonAction(){
if currentMediaType == .compressPhoto {
if let imageData = self.comDataSource.first {
......@@ -234,38 +236,34 @@ class CompressCompletedViewController : BaseViewController{
}) { success, error in
if(success){
print("保存照片成功")
}else {
if let error = error {
print("保存相片时出错: \(error.localizedDescription)")
}
}
self.jumpToRootVC()
self.showDeleteSuccess(fileCount: 1, fileSize: Int64(self.completedSize))
}
}else {
self.jumpToRootVC()
self.showDeleteSuccess(fileCount: 1, fileSize: Int64(self.completedSize))
}
}else{
// 保存视频到相册
if let url = self.comVideoDataSource.first {
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: url!)
}) { (success, saveError) in
if success {
print("保存视频成功")
}else{
if let error = saveError {
print("保存视频时出错: \(error.localizedDescription)")
}
PHPhotoLibrary.shared().performChanges({
if let url = self.comVideoDataSource.first{
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: url as URL)
}
}) { (success, saveError) in
if success {
print("保存视频成功")
}else{
if let error = saveError {
print("保存视频时出错: \(error.localizedDescription)")
}
self.jumpToRootVC()
}
}else {
self.jumpToRootVC()
self.showDeleteSuccess(fileCount: 1, fileSize: Int64(self.completedSize))
}
}
}
/// 删除原数据
@objc func deleteButtonAction(){
if let model = self.model {
let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [model.localIdentifier], options: nil)
......@@ -275,15 +273,11 @@ class CompressCompletedViewController : BaseViewController{
if(success){
PhotoManager.shared.removeDataWhenDeleteInPage(data: [self.model!])
print("删除文件成功")
}else {
if let error = error {
print("删除文件时出错: \(error.localizedDescription)")
}
}
self.jumpToRootVC()
self.showDeleteSuccess(fileCount: 1, fileSize: Int64(self.completedSize))
}
}else {
self.jumpToRootVC()
self.showDeleteSuccess(fileCount: 1, fileSize: Int64(self.completedSize))
}
}
......@@ -291,16 +285,25 @@ class CompressCompletedViewController : BaseViewController{
// MARK: 辅助方法
// 删除成功页面
func showDeleteSuccess(fileCount:Int,fileSize:Int64){
DispatchQueue.main.async {
self.jumpToRootVC()
let vc = DelSuccessViewController()
vc.delType = fileCount > 1 ? "photos" : "photo"
vc.fileSzie = fileSize
vc.fileCount = fileCount
vc.modalPresentationStyle = .fullScreen
self.present(vc, animated: true)
}
}
/// 返回压缩控制器
func jumpToRootVC(){
DispatchQueue.main.async {
if let viewControllers = self.navigationController?.viewControllers {
for controller in viewControllers.reversed() {
if controller is CompressController {
self.navigationController?.popToViewController(controller, animated: true)
break
}
}
if let targetVC = self.navigationController?.viewControllers.first(where: { $0 is CompressController }) {
self.navigationController?.popToViewController(targetVC, animated: true)
}
}
}
......
......@@ -153,7 +153,36 @@ extension CompressController:WaterfallMutiSectionDelegate,UICollectionViewDataSo
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
self.startCompress(model: self.resourceData[indexPath.row])
if self.currentResourceType == .compressPhoto{
// 判断资源是否存在
let options = PHImageRequestOptions()
options.isNetworkAccessAllowed = false
options.progressHandler = nil
let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [self.resourceData[indexPath.row].localIdentifier], options: nil)
guard let phAsset = fetchResult.firstObject else {
showAlert()
return
}
PHImageManager.default().requestImageDataAndOrientation(
for: phAsset,
options: options
) { data, _, _, info in
if data == nil {
self.showAlert()
}else {
self.startCompress(model: self.resourceData[indexPath.row])
}
}
}else {
self.getVideoURLFromLocalIdentifier(localIdentifier: self.resourceData[indexPath.row].localIdentifier) { url, error in
if url != nil {
self.startCompress(model: self.resourceData[indexPath.row])
}else {
self.showAlert()
}
}
}
}
func referenceSizeForHeader(collectionView collection: UICollectionView, layout: WaterfallMutiSectionFlowLayout, section: Int) -> CGSize {
return CGSize (width: self.collectionView.width, height: 188)
......@@ -271,11 +300,59 @@ extension CompressController:WaterfallMutiSectionDelegate,UICollectionViewDataSo
}
func jumpToNextPage(model:AssetModel){
// 先将值传到下一个页面
let vc : CompressQualityController = CompressQualityController()
vc.currentMediaType = self.currentResourceType
vc.model = model
self.navigationController?.pushViewController(vc, animated: true)
}
func showAlert(){
let alert = UIAlertController(title: nil, message: "ICloud video cannot be viewed", preferredStyle: .alert)
self.present(alert, animated: true, completion: nil)
// 2 秒后关闭弹窗
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
alert.dismiss(animated: true, completion: nil)
}
}
// 注意私有方法(某些参数不一样) 这个只能在这个类中使用
private func getVideoURLFromLocalIdentifier(localIdentifier: String, completion: @escaping (URL?, Error?) -> Void) {
// 通过 localIdentifier 获取 PHAsset
let fetchOptions = PHFetchOptions()
let assets = PHAsset.fetchAssets(withLocalIdentifiers: [localIdentifier], options: fetchOptions)
guard let asset = assets.firstObject, asset.mediaType == .video else {
DispatchQueue.main.async {
completion(nil, NSError(domain: "com.example.error", code: 1, userInfo: [NSLocalizedDescriptionKey: "未找到对应视频资源"]))
}
return
}
let options = PHVideoRequestOptions()
options.isNetworkAccessAllowed = false // 允许从网络下载
options.deliveryMode = .fastFormat // 要求高质量格式
PHImageManager.default().requestAVAsset(forVideo: asset, options: options) { (avAsset, audioMix, info) in
if let error = info?[PHImageErrorKey] as? Error {
DispatchQueue.main.async {
completion(nil, error)
}
return
}
if let urlAsset = avAsset as? AVURLAsset {
DispatchQueue.main.async {
completion(urlAsset.url, nil)
}
} else {
DispatchQueue.main.async {
completion(nil, NSError(domain: "CustomErrorDomain", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to get video URL"]))
}
}
}
}
......
......@@ -258,7 +258,7 @@ class CompressQualityController : BaseViewController{
fileprivate func updateNextView(_ compressAllSize: Double, _ compressingView: CompressingView,_ comDataSource : [Data],_ comVideoDataSource : [URL?]) {
fileprivate func updateNextView(_ compressAllSize: Double, _ compressingView: CompressingView,_ comDataSource : [Data],_ comVideoDataSource : [URL]) {
DispatchQueue.main.async {
compressingView.removeFromSuperview()
let vc = CompressCompletedViewController()
......
......@@ -22,13 +22,11 @@ class CompressingViewController: BaseViewController {
}
}
var qualityType : CompressQualityType? {
var qualityType : CompressQualityType = .low {
didSet{
if let model = model {
if let qualityType = qualityType {
DispatchQueue.main.async {
self.targetSizeLabel.text = formatFileSize(model.assetSize * qualityType.rawValue)
}
DispatchQueue.main.async {
self.targetSizeLabel.text = formatFileSize(model.assetSize * self.qualityType.rawValue)
}
}
}
......@@ -85,7 +83,7 @@ class CompressingViewController: BaseViewController {
lazy var progressBar : CustomCompressProgressBar = {
let bar = CustomCompressProgressBar()
bar.thumbImage = UIImage(systemName: "arrowtriangle.right.fill")?.withTintColor(.red)
bar.thumbImage = UIImage(named: "Group_1171275116")
bar.layer.cornerRadius = 3.5
bar.clipsToBounds = true
return bar
......@@ -104,66 +102,85 @@ class CompressingViewController: BaseViewController {
// MARK:设置数据
// MARK: 压缩方法
/// 开始压缩
func startCompress(){
if let qualityType = qualityType {
if self.currentMediaType == .compressPhoto {
self.compressPhoto(model: self.model)
}else {
self.compressVideo(model: self.model)
}
}
/// 压缩单个图片
/// - Parameter model: 资源
private func compressPhoto(model : AssetModel?){
if let model = model {
var comDataSource : [Data] = []
let manager : CompressViewModel = CompressViewModel()
if self.currentMediaType == .compressPhoto {
// 表示压缩图片
var comDataSource : [Data] = []
manager.compress(assets: [self.model!], compressionQuality: qualityType.rawValue) {progress in
Print(progress)
DispatchQueue.main.async {
self.progressBar.updateProgress(to: CGFloat(progress))
}
} completion: { compressedDataArray, errorArray in
var compressAllSize = 0.0
for (index, data) in compressedDataArray.enumerated() {
if let error = errorArray[index] {
print("第 \(index + 1) 个文件压缩出错: \(error.localizedDescription)")
} else if let data = data {
print("第 \(index + 1) 个文件压缩完成,压缩后大小: \(data.count) 字节")
compressAllSize = compressAllSize + Double(data.count)
comDataSource.append(data)
} else {
print("第 \(index + 1) 个文件压缩失败")
}
manager.compress(assets: [model], compressionQuality: qualityType.rawValue) {progress in
DispatchQueue.main.async {
self.progressBar.updateProgress(to: CGFloat(progress))
}
} completion: { compressedDataArray, errorArray in
var compressAllSize = 0.0
for (_, data) in compressedDataArray.enumerated() {
if let data = data {
compressAllSize = compressAllSize + Double(data.count)
comDataSource.append(data)
}
}
self.progressBar.animationCompletion = {
// 不管成功失败都跳转下一页
self.updateNextView(compressAllSize,comDataSource,[])
}
}else{
// 压缩视频
var compressAllSize : Double = 0.0
CompressViewModel.compressVideos(models: [self.model!], quality: Float(qualityType.rawValue)) { progress in
DispatchQueue.main.async {
self.progressBar.updateProgress(to: CGFloat(progress))
}
} completion: { (outputURLs, errors) in
for (index, outputURL) in outputURLs.enumerated() {
if let outputURL = outputURL {
do {
let attributes = try FileManager.default.attributesOfItem(atPath: outputURL.path)
if let fileSize = attributes[.size] as? Int64 {
compressAllSize = compressAllSize + Double(fileSize)
}
} catch {
Print("获取视频文件大小失败")
}
}
}
/// 压缩单个视频
/// - Parameter model: 资源
private func compressVideo(model : AssetModel?){
if let model = model {
var compressAllSize : Double = 0.0
CompressViewModel.compressVideos(models: [model], quality: Float(qualityType.rawValue)) { progress in
DispatchQueue.main.async {
self.progressBar.updateProgress(to: CGFloat(progress))
}
} completion: { (outputURLs, errors) in
var finallyUrls : [URL] = []
for (_, outputURL) in outputURLs.enumerated() {
if let outputURL = outputURL {
do {
let attributes = try FileManager.default.attributesOfItem(atPath: outputURL.path)
if let fileSize = attributes[.size] as? Int64 {
compressAllSize = compressAllSize + Double(fileSize)
}
} else if let error = errors[index] {
print("Error compressing video \(index): \(error.localizedDescription)")
} catch {
Print("获取视频文件大小失败")
}
finallyUrls.append(outputURL)
}
Print("---------压缩后的大小:\(compressAllSize)")
}
self.progressBar.animationCompletion = {
// 不管成功失败都跳转下一页
self.updateNextView(compressAllSize,[],outputURLs)
self.updateNextView(compressAllSize,[],finallyUrls)
}
}
}
}
fileprivate func updateNextView(_ compressAllSize: Double,_ comDataSource : [Data],_ comVideoDataSource : [URL?]) {
/// 压缩结果处理
/// - Parameters:
/// - compressAllSize: 压缩后总大小
/// - comDataSource: 压缩后的图片数据数组
/// - comVideoDataSource: 压缩后的视频链接数组
fileprivate func updateNextView(_ compressAllSize: Double,_ comDataSource : [Data],_ comVideoDataSource : [URL]) {
DispatchQueue.main.async {
let vc = CompressCompletedViewController()
vc.currentMediaType = self.currentMediaType
......@@ -176,7 +193,7 @@ class CompressingViewController: BaseViewController {
}
// MARK:设置UI
// MARK: 设置UI
func setUI(){
self.view.addSubview(self.bacImageView)
self.bacImageView.snp.makeConstraints { make in
......@@ -222,8 +239,8 @@ class CompressingViewController: BaseViewController {
self.progressBar.snp.makeConstraints { make in
make.left.equalTo(16)
make.right.equalToSuperview().offset(-16)
make.height.equalTo(7)
make.bottom.equalTo(-43 - safeHeight)
make.height.equalTo(24)
make.bottom.equalTo(-34 - safeHeight)
}
self.animationView.play(fromProgress: 0, toProgress: 1 , loopMode: .loop)
......
......@@ -8,23 +8,47 @@
import UIKit
class CustomCompressProgressBar: UIView {
var animationCompletion: (() -> Void) = {}
// 背景色(已完成进度颜色)
private let backgroundLayer = CALayer()
// 前景色(已完成进度颜色)
private let foregroundLayer = CALayer()
// 进度指示图标
private let thumbLayer = CALayer()
// 当前进度值(0-1)
private var progress: CGFloat = 0.0
// 最后一次更新时间
private var lastUpdateTime: Date?
// 动画持续时间
private let animationDuration: CFTimeInterval = 4.0
// 动画定时器
private var displayLink: CADisplayLink?
private var animationStartTime: CFTimeInterval = 0
private var startProgress: CGFloat = 0
private var targetProgress: CGFloat = 0
// 缩略图图片
var thumbImage: UIImage? {
didSet {
thumbLayer.contents = thumbImage?.cgImage
thumbLayer.frame = CGRect(origin: .zero, size: thumbImage?.size ?? CGSize(width: 20, height: 20))
// 调整缩略图锚点
thumbLayer.anchorPoint = CGPoint(x: 0.5, y: 0.5)
// 设置正确尺寸并居中
thumbLayer.frame = CGRect(
x: 0,
y: bounds.height/2 - 12, // 24/2=12
width: 24,
height: 24
)
}
}
// 添加视图配置防止裁剪
override func didMoveToSuperview() {
super.didMoveToSuperview()
self.clipsToBounds = false
layer.masksToBounds = false
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
......@@ -36,82 +60,90 @@ class CustomCompressProgressBar: UIView {
}
private func setup() {
backgroundColor = .white
layer.cornerRadius = frame.height / 2
layer.masksToBounds = true
// 设置背景层
backgroundLayer.backgroundColor = UIColor(red: 1, green: 1, blue: 1, alpha: 1).cgColor
backgroundLayer.frame = CGRect(x: 0, y: self.bounds.height/2 - 3.5, width: self.bounds.width, height: 7)
backgroundLayer.cornerRadius = 3.5
layer.addSublayer(backgroundLayer)
// 设置前景层
foregroundLayer.backgroundColor = UIColor(red: 0, green: 0.51, blue: 1, alpha: 1).cgColor
foregroundLayer.cornerRadius = layer.cornerRadius
foregroundLayer.cornerRadius = 3.5
layer.addSublayer(foregroundLayer)
// 设置缩略图层
thumbLayer.contentsGravity = .resizeAspect
layer.addSublayer(thumbLayer)
}
override func layoutSubviews() {
super.layoutSubviews()
CATransaction.begin()
CATransaction.setDisableActions(true)
updateLayers(for: progress)
CATransaction.commit()
}
func updateProgress(to newProgress: CGFloat) {
let currentTime = Date()
let clampedProgress = max(0, min(newProgress, 1.0))
var duration: TimeInterval = 0
if let lastTime = lastUpdateTime {
let timeSinceLast = currentTime.timeIntervalSince(lastTime)
duration = timeSinceLast < 1.0 ? 1.0 : timeSinceLast
startProgress = currentPresentationProgress()
targetProgress = clampedProgress
startAnimation()
}
private func currentPresentationProgress() -> CGFloat {
if let presentation = foregroundLayer.presentation() {
return presentation.bounds.width / bounds.width
}
animateProgress(from: progress, to: clampedProgress, duration: duration)
lastUpdateTime = currentTime
return progress
}
private func updateLayers(for progress: CGFloat) {
let foregroundWidth = bounds.width * progress
foregroundLayer.frame = CGRect(x: 0, y: 0, width: foregroundWidth, height: bounds.height)
let thumbSize = thumbLayer.bounds.size
let thumbX = progress * bounds.width - thumbSize.width/2
thumbLayer.frame.origin = CGPoint(
x: max(0, min(thumbX, bounds.width - thumbSize.width)),
y: (bounds.height - thumbSize.height)/2
)
private func startAnimation() {
animationStartTime = CACurrentMediaTime()
displayLink?.invalidate()
displayLink = CADisplayLink(target: self, selector: #selector(updateAnimation))
displayLink?.add(to: .main, forMode: .common)
}
private func animateProgress(from oldProgress: CGFloat, to newProgress: CGFloat, duration: TimeInterval) {
foregroundLayer.removeAllAnimations()
thumbLayer.removeAllAnimations()
guard duration > 0 else {
progress = newProgress
updateLayers(for: newProgress)
return
}
let foregroundAnimation = CABasicAnimation(keyPath: "bounds.size.width")
foregroundAnimation.fromValue = oldProgress * bounds.width
foregroundAnimation.toValue = newProgress * bounds.width
foregroundAnimation.duration = duration
foregroundAnimation.timingFunction = CAMediaTimingFunction(name: .linear)
@objc private func updateAnimation() {
let elapsed = CACurrentMediaTime() - animationStartTime
let percent = min(elapsed / animationDuration, 1.0)
let thumbAnimation = CABasicAnimation(keyPath: "position.x")
thumbAnimation.fromValue = (oldProgress * bounds.width) - thumbLayer.bounds.width/2
thumbAnimation.toValue = (newProgress * bounds.width) - thumbLayer.bounds.width/2
thumbAnimation.duration = duration
thumbAnimation.timingFunction = CAMediaTimingFunction(name: .linear)
let currentProgress = startProgress + (targetProgress - startProgress) * CGFloat(percent)
progress = currentProgress
progress = newProgress
CATransaction.begin()
CATransaction.setDisableActions(true)
foregroundLayer.bounds.size.width = newProgress * bounds.width
thumbLayer.position.x = (newProgress * bounds.width) - thumbLayer.bounds.width/2
updateLayers(for: currentProgress)
CATransaction.commit()
foregroundLayer.add(foregroundAnimation, forKey: nil)
thumbLayer.add(thumbAnimation, forKey: nil)
if percent >= 1.0 {
displayLink?.invalidate()
displayLink = nil
// 触发完成回调
animationCompletion()
}
}
private func updateLayers(for progress: CGFloat) {
let foregroundWidth = bounds.width * progress
foregroundLayer.frame = CGRect(
x: 0,
y: bounds.height/2 - 3.5,
width: foregroundWidth,
height: 7
)
// 修改缩略图位置计算
let thumbSize = thumbLayer.bounds.size
// 计算中心点位置(确保始终显示完整缩略图)
let thumbCenterX = foregroundWidth - thumbSize.width/2
// 限制缩略图显示范围(不超出进度条边界)
let minX = -thumbSize.width/2 // 允许左侧部分超出
let maxX = bounds.width - thumbSize.width/2
thumbLayer.frame.origin.x = max(minX, min(thumbCenterX, maxX))
// 设置垂直居中
thumbLayer.frame.origin.y = bounds.height/2 - thumbSize.height/2
}
}
......@@ -136,8 +136,8 @@ class CompressViewModel{
private func compressSingleAsset(_ asset: PHAsset,_ compressionQuality : CGFloat, progress: @escaping (Float) -> Void, completion: @escaping (Data?, Error?) -> Void) {
let options = PHImageRequestOptions()
options.isSynchronous = false
options.deliveryMode = .highQualityFormat
// 设置为原图片
options.version = .original
PHImageManager.default().requestImageDataAndOrientation(for: asset, options: options) { (imageData, _, _, error) in
guard let originalData = imageData else {
completion(nil, nil)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment