Commit 02803439 authored by shenyong's avatar shenyong

优化保留 和首页

parent 656d7fa9
...@@ -49,7 +49,7 @@ class PhotoDuplicateManager: @unchecked Sendable { ...@@ -49,7 +49,7 @@ class PhotoDuplicateManager: @unchecked Sendable {
print("本地数据加载完成") print("本地数据加载完成")
// 2. 通知已缓存的结果 // 2. 通知已缓存的结果
let cachedGroups = await stateManager.getAllDuplicateGroups() let cachedGroups = await loadSimilarGroups() //stateManager.getAllDuplicateGroups()
print("通知已缓存的结果", cachedGroups.count) print("通知已缓存的结果", cachedGroups.count)
await MainActor.run { await MainActor.run {
let groups = cachedGroups.map{$0.assets} let groups = cachedGroups.map{$0.assets}
...@@ -143,7 +143,7 @@ class PhotoDuplicateManager: @unchecked Sendable { ...@@ -143,7 +143,7 @@ class PhotoDuplicateManager: @unchecked Sendable {
await self.savePendingDuplicateGroups() await self.savePendingDuplicateGroups()
} }
let allGroups = await stateManager.getAllDuplicateGroups() let allGroups = await loadSimilarGroups() //stateManager.getAllDuplicateGroups()
await MainActor.run { await MainActor.run {
print("执行完毕") print("执行完毕")
completionHandler?(allGroups.map { $0.assets }) completionHandler?(allGroups.map { $0.assets })
...@@ -209,17 +209,30 @@ extension PhotoDuplicateManager { ...@@ -209,17 +209,30 @@ extension PhotoDuplicateManager {
private func savePendingDuplicateGroups() async { private func savePendingDuplicateGroups() async {
await stateManager.savePendingGroups() await stateManager.savePendingGroups()
if let data = try? JSONEncoder().encode(await stateManager.getAllDuplicateGroups()) { if let data = try? JSONEncoder().encode(await loadSimilarGroups()) {
try? data.write(to: URL(fileURLWithPath: duplicateGroupsPath)) try? data.write(to: URL(fileURLWithPath: duplicateGroupsPath))
} }
} }
private func loadSimilarGroups() async -> [SimilarGroupModel] {
let groups = await stateManager.getAllDuplicateGroups()
// 验证资源有效性
return groups.map { group in
var validAssets = group.assets
validAssets.removeAll { asset in
let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [asset.localIdentifier], options: nil)
return fetchResult.firstObject == nil
}
return SimilarGroupModel(groupId: group.groupId, assets: validAssets)
}.filter { !$0.assets.isEmpty }
}
// 移除本地文件资源 // 移除本地文件资源
func removeLocalFileWithIds(for ids:[String]) async{ func removeLocalFileWithIds(for ids:[String]) async{
await stateManager.deleteData(for: ids) await stateManager.deleteData(for: ids)
// 保存到文件 // 保存到文件
if let data = try? JSONEncoder().encode(await stateManager.getAllDuplicateGroups()) { if let data = try? JSONEncoder().encode(await loadSimilarGroups()) {
try? data.write(to: URL(fileURLWithPath: duplicateGroupsPath)) try? data.write(to: URL(fileURLWithPath: duplicateGroupsPath))
} }
} }
......
...@@ -113,6 +113,11 @@ class PhotoManager{ ...@@ -113,6 +113,11 @@ class PhotoManager{
private(set) var screenShotTotalSize:Int64 = 0 private(set) var screenShotTotalSize:Int64 = 0
private(set) var videoTotalSize:Int64 = 0 private(set) var videoTotalSize:Int64 = 0
private(set) var otherTotalSize:Int64 = 0 private(set) var otherTotalSize:Int64 = 0
var trash = TrashDatabase.shared.queryAll().compactMap{$0.localIdentifier}
var keep = GroupDatabase.shared.queryAll().compactMap{$0.localIdentifier}
private var currentPage: Int = 0 private var currentPage: Int = 0
private let pageSize: Int = 50 // 每次加载的数量 private let pageSize: Int = 50 // 每次加载的数量
...@@ -722,11 +727,12 @@ extension PhotoManager{ ...@@ -722,11 +727,12 @@ extension PhotoManager{
let similarPhotos = filterGroups(similarModels, byExcludingIDs: deletes) let similarPhotos = filterGroups(similarModels, byExcludingIDs: deletes)
let similarVideos = filterGroups(similarVideoModels, byExcludingIDs: deletes) let similarVideos = filterGroups(similarVideoModels, byExcludingIDs: deletes)
let similarShots = filterGroups(similarScreenShotModels, byExcludingIDs: deletes) let similarShots = filterGroups(similarScreenShotModels, byExcludingIDs: deletes)
let duplicates = filterGroups(duplicateModels, byExcludingIDs: deletes)
similarModels = similarPhotos similarModels = similarPhotos
similarVideoModels = similarVideos similarVideoModels = similarVideos
similarScreenShotModels = similarShots similarScreenShotModels = similarShots
duplicateModels = duplicates
// 保存到本地 // 保存到本地
saveToLocal(type: "video", models: self.videoModels) saveToLocal(type: "video", models: self.videoModels)
...@@ -756,4 +762,35 @@ extension PhotoManager{ ...@@ -756,4 +762,35 @@ extension PhotoManager{
} }
} }
// 重写获取垃圾桶和保留刷新进行刷新
func reloadTrashAndKeep(){
trash = TrashDatabase.shared.queryAll().compactMap{$0.localIdentifier}
keep = GroupDatabase.shared.queryAll().compactMap{$0.localIdentifier}
filterResource()
}
// 基本资源过滤保留和垃圾桶资源
func filterResource(){
let filterArray = trash + keep
let others = removeAssets(withIdentifiers: filterArray, from: otherModels)
let videos = removeAssets(withIdentifiers: filterArray, from: videoModels)
let screens = removeAssets(withIdentifiers: filterArray, from: screenShotModels)
filterOtherModels = others
filterVideoModels = videos
filterScreenShotModels = screens
let similarPhotos = filterGroups(similarModels, byExcludingIDs: filterArray)
let similarVideos = filterGroups(similarVideoModels, byExcludingIDs: filterArray)
let similarShots = filterGroups(similarScreenShotModels, byExcludingIDs: filterArray)
filterSimilarModels = similarPhotos
filterSimilarVideoModels = similarVideos
filterSimilarScreenShotModels = similarShots
}
} }
...@@ -59,7 +59,7 @@ class PhotoSimilarManager: @unchecked Sendable { ...@@ -59,7 +59,7 @@ class PhotoSimilarManager: @unchecked Sendable {
} }
// 3. 通知已缓存的结果 // 3. 通知已缓存的结果
let cachedGroups = await stateManager.getAllSimilarGroups() let cachedGroups = await loadSimilarGroups() //await stateManager.getAllSimilarGroups()
print("通知已缓存的结果", cachedGroups.count) print("通知已缓存的结果", cachedGroups.count)
await MainActor.run { await MainActor.run {
for group in cachedGroups { for group in cachedGroups {
...@@ -176,7 +176,8 @@ class PhotoSimilarManager: @unchecked Sendable { ...@@ -176,7 +176,8 @@ class PhotoSimilarManager: @unchecked Sendable {
await savePendingSimilarGroups() await savePendingSimilarGroups()
} }
let allGroups = await stateManager.getAllSimilarGroups() // var allGroups = await stateManager.getAllSimilarGroups()
let allGroups = await loadSimilarGroups()
await MainActor.run { await MainActor.run {
print("执行完毕") print("执行完毕")
completionHandler?(allGroups.map { $0.assets }) completionHandler?(allGroups.map { $0.assets })
...@@ -332,7 +333,7 @@ extension PhotoSimilarManager{ ...@@ -332,7 +333,7 @@ extension PhotoSimilarManager{
private func savePendingSimilarGroups() async { private func savePendingSimilarGroups() async {
await stateManager.savePendingGroups() await stateManager.savePendingGroups()
// 保存到文件 // 保存到文件
if let data = try? JSONEncoder().encode(await stateManager.getAllSimilarGroups()) { if let data = try? JSONEncoder().encode(await loadSimilarGroups()) {
try? data.write(to: URL(fileURLWithPath: similarGroupsPath)) try? data.write(to: URL(fileURLWithPath: similarGroupsPath))
} }
} }
...@@ -355,7 +356,7 @@ extension PhotoSimilarManager{ ...@@ -355,7 +356,7 @@ extension PhotoSimilarManager{
await stateManager.deleteData(for: ids) await stateManager.deleteData(for: ids)
// 保存到文件 // 保存到文件
if let data = try? JSONEncoder().encode(await stateManager.getAllSimilarGroups()) { if let data = try? JSONEncoder().encode(await loadSimilarGroups()) {
try? data.write(to: URL(fileURLWithPath: similarGroupsPath)) try? data.write(to: URL(fileURLWithPath: similarGroupsPath))
} }
} }
......
...@@ -58,7 +58,7 @@ class ScreenshotSimilarJSONManager: @unchecked Sendable { ...@@ -58,7 +58,7 @@ class ScreenshotSimilarJSONManager: @unchecked Sendable {
} }
// 3. 通知已缓存的结果 // 3. 通知已缓存的结果
let cachedGroups = await stateManager.getAllSimilarGroups() let cachedGroups = await loadSimilarGroups() //await stateManager.getAllSimilarGroups()
print("通知已缓存的结果", cachedGroups.count) print("通知已缓存的结果", cachedGroups.count)
await MainActor.run { await MainActor.run {
for group in cachedGroups { for group in cachedGroups {
...@@ -175,7 +175,7 @@ class ScreenshotSimilarJSONManager: @unchecked Sendable { ...@@ -175,7 +175,7 @@ class ScreenshotSimilarJSONManager: @unchecked Sendable {
await savePendingSimilarGroups() await savePendingSimilarGroups()
} }
let allGroups = await stateManager.getAllSimilarGroups() let allGroups = await loadSimilarGroups() //await stateManager.getAllSimilarGroups()
await MainActor.run { await MainActor.run {
print("执行完毕") print("执行完毕")
completionHandler?(allGroups.map { $0.assets }) completionHandler?(allGroups.map { $0.assets })
...@@ -365,7 +365,7 @@ extension ScreenshotSimilarJSONManager{ ...@@ -365,7 +365,7 @@ extension ScreenshotSimilarJSONManager{
private func savePendingSimilarGroups() async { private func savePendingSimilarGroups() async {
await stateManager.savePendingGroups() await stateManager.savePendingGroups()
// 保存到文件 // 保存到文件
if let data = try? JSONEncoder().encode(await stateManager.getAllSimilarGroups()) { if let data = try? JSONEncoder().encode(await loadSimilarGroups()) {
try? data.write(to: URL(fileURLWithPath: similarGroupsPath)) try? data.write(to: URL(fileURLWithPath: similarGroupsPath))
} }
} }
...@@ -389,7 +389,7 @@ extension ScreenshotSimilarJSONManager{ ...@@ -389,7 +389,7 @@ extension ScreenshotSimilarJSONManager{
await stateManager.deleteData(for: ids) await stateManager.deleteData(for: ids)
// 保存到文件 // 保存到文件
if let data = try? JSONEncoder().encode(await stateManager.getAllSimilarGroups()) { if let data = try? JSONEncoder().encode(await loadSimilarGroups()) {
try? data.write(to: URL(fileURLWithPath: similarGroupsPath)) try? data.write(to: URL(fileURLWithPath: similarGroupsPath))
} }
} }
......
...@@ -89,7 +89,7 @@ class VideoSimilarJSONManager: @unchecked Sendable { ...@@ -89,7 +89,7 @@ class VideoSimilarJSONManager: @unchecked Sendable {
} }
// 3. 通知已缓存的结果 // 3. 通知已缓存的结果
let cachedGroups = await stateManager.getAllSimilarGroups() let cachedGroups = await loadSimilarGroups() //await stateManager.getAllSimilarGroups()
await MainActor.run { await MainActor.run {
for group in cachedGroups { for group in cachedGroups {
progressHandler?(group.assets) progressHandler?(group.assets)
...@@ -202,7 +202,7 @@ class VideoSimilarJSONManager: @unchecked Sendable { ...@@ -202,7 +202,7 @@ class VideoSimilarJSONManager: @unchecked Sendable {
await savePendingSimilarGroups() await savePendingSimilarGroups()
} }
let allGroups = await stateManager.getAllSimilarGroups() let allGroups = await loadSimilarGroups() //await stateManager.getAllSimilarGroups()
await MainActor.run { await MainActor.run {
completionHandler?(allGroups.map { $0.assets }) completionHandler?(allGroups.map { $0.assets })
} }
...@@ -577,22 +577,55 @@ class VideoSimilarJSONManager: @unchecked Sendable { ...@@ -577,22 +577,55 @@ class VideoSimilarJSONManager: @unchecked Sendable {
return zip(hash1, hash2).filter { $0 != $1 }.count return zip(hash1, hash2).filter { $0 != $1 }.count
} }
private func createAssetModels(from assets: [PHAsset]) -> [AssetModel] { // private func createAssetModels(from assets: [PHAsset]) -> [AssetModel] {
return assets.map { asset in // return assets.map { asset in
let assetSize: Double // let assetSize: Double
if let resource = PHAssetResource.assetResources(for: asset).first, // if let resource = PHAssetResource.assetResources(for: asset).first,
let size = resource.value(forKey: "fileSize") as? Int64 { // let size = resource.value(forKey: "fileSize") as? Int64 {
assetSize = Double(size) // assetSize = Double(size)
} else { // } else {
assetSize = 0 // assetSize = 0
// }
//
// return AssetModel(
// localIdentifier: asset.localIdentifier,
// assetSize: assetSize,
// createDate: asset.creationDate ?? Date(),
// mediaType: 2
// )
// }
// }
private func createAssetModels(from assets: [PHAsset]) async -> [AssetModel] {
return await withTaskGroup(of: AssetModel.self) { modelGroup in
var models: [AssetModel] = []
for asset in assets {
modelGroup.addTask {
return await withCheckedContinuation { continuation in
let assetSize: Double
if let resource = PHAssetResource.assetResources(for: asset).first,
let size = resource.value(forKey: "fileSize") as? Int64 {
assetSize = Double(size)
} else {
assetSize = 0
}
let model = AssetModel(
localIdentifier: asset.localIdentifier,
assetSize: assetSize,
createDate: asset.creationDate ?? Date(),
mediaType: 2
)
continuation.resume(returning: model)
}
}
} }
return AssetModel( for await model in modelGroup {
localIdentifier: asset.localIdentifier, models.append(model)
assetSize: assetSize, }
createDate: asset.creationDate ?? Date(), return models
mediaType: 2
)
} }
} }
...@@ -629,7 +662,7 @@ extension VideoSimilarJSONManager { ...@@ -629,7 +662,7 @@ extension VideoSimilarJSONManager {
private func savePendingSimilarGroups() async { private func savePendingSimilarGroups() async {
await stateManager.savePendingGroups() await stateManager.savePendingGroups()
if let data = try? JSONEncoder().encode(await stateManager.getAllSimilarGroups()) { if let data = try? JSONEncoder().encode(await loadSimilarGroups()) {
try? data.write(to: URL(fileURLWithPath: similarGroupsPath)) try? data.write(to: URL(fileURLWithPath: similarGroupsPath))
} }
} }
...@@ -639,10 +672,23 @@ extension VideoSimilarJSONManager { ...@@ -639,10 +672,23 @@ extension VideoSimilarJSONManager {
await stateManager.deleteData(for: ids) await stateManager.deleteData(for: ids)
// 保存到文件 // 保存到文件
if let data = try? JSONEncoder().encode(await stateManager.getAllSimilarGroups()) { if let data = try? JSONEncoder().encode(await loadSimilarGroups()) {
try? data.write(to: URL(fileURLWithPath: similarGroupsPath)) try? data.write(to: URL(fileURLWithPath: similarGroupsPath))
} }
} }
private func loadSimilarGroups() async -> [SimilarGroupModel] {
let groups = await stateManager.getAllSimilarGroups()
// 验证资源有效性
return groups.map { group in
var validAssets = group.assets
validAssets.removeAll { asset in
let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [asset.localIdentifier], options: nil)
return fetchResult.firstObject == nil
}
return SimilarGroupModel(groupId: group.groupId, assets: validAssets)
}.filter { !$0.assets.isEmpty }
}
} }
......
...@@ -113,7 +113,7 @@ class CompressController : BaseViewController { ...@@ -113,7 +113,7 @@ class CompressController : BaseViewController {
/// 获取当前页面数据 /// 获取当前页面数据
func getViewData(){ func getViewData(){
if PhotoAndVideoMananger.mananger.permissionStatus == .denied{ if PhotoManager.shared.permissionStatus == .denied{
loadPermissView(CGRect(x: 0, y: 200, width: ScreenW, height: 450)) loadPermissView(CGRect(x: 0, y: 200, width: ScreenW, height: 450))
return return
} }
......
...@@ -90,12 +90,10 @@ class HomeInfoViewController:BaseViewController { ...@@ -90,12 +90,10 @@ class HomeInfoViewController:BaseViewController {
// 更新免费次数 // 更新免费次数
if isAfterAdv == false { if isAfterAdv == false {
updateFreeTimes() updateFreeTimes()
} }
self.showDeleteSuccess(fileCount: tempStringArray.count, fileSize: fileSize) self.showDeleteSuccess(fileCount: tempStringArray.count, fileSize: fileSize)
PhotoManager.shared.removeDataWhenDeleteInPage(data: imgs) PhotoManager.shared.removeDataWhenDeleteInPage(data: imgs)
let new = self.ids?.removingElementsAndSmallGroups(ids: imgs.compactMap{$0.localIdentifier}) let new = self.ids?.removingElementsAndSmallGroups(ids: imgs.compactMap{$0.localIdentifier})
...@@ -410,7 +408,7 @@ class HomeInfoViewController:BaseViewController { ...@@ -410,7 +408,7 @@ class HomeInfoViewController:BaseViewController {
func setDefaultPage(){ func setDefaultPage(){
DispatchQueue.main.async { DispatchQueue.main.async {
if PhotoAndVideoMananger.mananger.permissionStatus == .denied{ if PhotoManager.shared.permissionStatus == .denied{
self.loadPermissView() self.loadPermissView()
}else{ }else{
if self.ids?.count == 0 { if self.ids?.count == 0 {
......
...@@ -214,7 +214,7 @@ class HomePhotosDetailViewController : BaseViewController { ...@@ -214,7 +214,7 @@ class HomePhotosDetailViewController : BaseViewController {
//设置空白页 //设置空白页
func setDefaultPage(){ func setDefaultPage(){
DispatchQueue.main.async { DispatchQueue.main.async {
if PhotoAndVideoMananger.mananger.permissionStatus == .denied{ if PhotoManager.shared.permissionStatus == .denied{
self.loadPermissView() self.loadPermissView()
}else{ }else{
if self.resourceData.count == 0 { if self.resourceData.count == 0 {
......
...@@ -554,7 +554,7 @@ extension HomeVideoDetailController:WaterfallMutiSectionDelegate,UICollectionVie ...@@ -554,7 +554,7 @@ extension HomeVideoDetailController:WaterfallMutiSectionDelegate,UICollectionVie
func setDefaultPage(){ func setDefaultPage(){
DispatchQueue.main.async { DispatchQueue.main.async {
if PhotoAndVideoMananger.mananger.permissionStatus == .denied{ if PhotoManager.shared.permissionStatus == .denied{
self.loadPermissView() self.loadPermissView()
}else{ }else{
if self.resourceData.count == 0 { if self.resourceData.count == 0 {
......
...@@ -202,14 +202,17 @@ class CustomProgressBar: UIView { ...@@ -202,14 +202,17 @@ class CustomProgressBar: UIView {
idleLabel.font = UIFont.systemFont(ofSize: 12) idleLabel.font = UIFont.systemFont(ofSize: 12)
idleLabel.textColor = UIColor.colorWithHex(hexStr: black6Color) idleLabel.textColor = UIColor.colorWithHex(hexStr: black6Color)
addSubview(idleLabel) addSubview(idleLabel)
progressLayer.frame = CGRect(x: 0, y: 0, width: ScreenW-48, height: 10)
progressLayer.cornerRadius = 5
progressLayer.masksToBounds = true
} }
override func layoutSubviews() { override func layoutSubviews() {
super.layoutSubviews() super.layoutSubviews()
progressLayer.frame = CGRect(x: 0, y: 0, width: bounds.width, height: 10)
progressLayer.cornerRadius = 5 // updateProgress()
progressLayer.masksToBounds = true
updateProgress()
let dotY = progressLayer.frame.maxY + 9 let dotY = progressLayer.frame.maxY + 9
let usedDotX = bounds.minX let usedDotX = bounds.minX
...@@ -231,11 +234,12 @@ class CustomProgressBar: UIView { ...@@ -231,11 +234,12 @@ class CustomProgressBar: UIView {
private func scheduleProgressUpdate() { private func scheduleProgressUpdate() {
// 取消之前的计时器 // 取消之前的计时器
updateTimer?.invalidate() // updateTimer?.invalidate()
// 设置新的计时器,延迟0.2秒更新UI // // 设置新的计时器,延迟0.2秒更新UI
updateTimer = Timer.scheduledTimer(withTimeInterval: 0.2, repeats: false) { [weak self] _ in // updateTimer = Timer.scheduledTimer(withTimeInterval: 0.2, repeats: false) { [weak self] _ in
self?.updateProgress() // self?.updateProgress()
} // }
updateProgress()
} }
private func updateProgress() { private func updateProgress() {
...@@ -249,33 +253,26 @@ class CustomProgressBar: UIView { ...@@ -249,33 +253,26 @@ class CustomProgressBar: UIView {
let chaoticRatio = min(max(self.chaoticProgress / total, 0), 1) let chaoticRatio = min(max(self.chaoticProgress / total, 0), 1)
// 计算实际宽度 // 计算实际宽度
let usedWidth = self.bounds.width * usedRatio let usedWidth = (ScreenW-48) * usedRatio
let chaoticWidth = self.bounds.width * chaoticRatio let chaoticWidth = (ScreenW-48) * chaoticRatio
// 设置进度条背景为白色 // 设置进度条背景为白色
self.progressLayer.backgroundColor = self.idleColor.cgColor self.progressLayer.backgroundColor = self.idleColor.cgColor
// 使用CATransaction确保所有动画同步进行
CATransaction.begin()
CATransaction.setAnimationDuration(0.3)
CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: .easeInEaseOut))
// 更新或创建used层 // 更新或创建used层
let usedLayer = self.progressLayer.sublayers?.first as? CALayer ?? CALayer() let usedLayer = self.progressLayer.sublayers?.first as? CALayer ?? CALayer()
usedLayer.frame = CGRect(x: 0, y: 0, width: usedWidth, height: self.progressLayer.bounds.height) usedLayer.frame = CGRect(x: 0, y: 0, width: usedWidth, height: 10)
usedLayer.backgroundColor = self.usedColor.cgColor usedLayer.backgroundColor = self.usedColor.cgColor
// 更新或创建chaotic层 // 更新或创建chaotic层
let chaoticLayer = self.progressLayer.sublayers?[safe: 1] as? CALayer ?? CALayer() let chaoticLayer = self.progressLayer.sublayers?[safe: 1] as? CALayer ?? CALayer()
chaoticLayer.frame = CGRect(x: usedWidth, y: 0, width: chaoticWidth, height: self.progressLayer.bounds.height) chaoticLayer.frame = CGRect(x: usedWidth, y: 0, width: chaoticWidth, height: 10)
chaoticLayer.backgroundColor = self.chaoticColor.cgColor chaoticLayer.backgroundColor = self.chaoticColor.cgColor
// 一次性设置所有子层 // 一次性设置所有子层
if self.progressLayer.sublayers == nil { if self.progressLayer.sublayers == nil {
self.progressLayer.sublayers = [usedLayer, chaoticLayer] self.progressLayer.sublayers = [usedLayer, chaoticLayer]
} }
CATransaction.commit()
} }
} }
......
...@@ -82,17 +82,17 @@ class HomeView:UIView { ...@@ -82,17 +82,17 @@ class HomeView:UIView {
viewModel.homeDataChanged = {[weak self] section,row in viewModel.homeDataChanged = {[weak self] section,row in
guard let weakSelf = self else { return } guard let weakSelf = self else { return }
DispatchQueue.main.async { DispatchQueue.main.async {
if let cell = weakSelf.collectionView.cellForItem(at: IndexPath(row: row, section: section)) as? HomeTitleCollectionCell { // if let cell = weakSelf.collectionView.cellForItem(at: IndexPath(row: row, section: section)) as? HomeTitleCollectionCell {
// 只更新需要改变的内容 // // 只更新需要改变的内容
let model = weakSelf.viewModel.headerGroup[row] // let model = weakSelf.viewModel.headerGroup[row]
cell.reloadUIWithModel(model: model) // cell.reloadUIWithModel(model: model)
} // }
if let cell = weakSelf.collectionView.cellForItem(at: IndexPath(row: row, section: section)) as? HomeOtherCollectionCell { // if let cell = weakSelf.collectionView.cellForItem(at: IndexPath(row: row, section: section)) as? HomeOtherCollectionCell {
// 只更新需要改变的内容 // // 只更新需要改变的内容
let model = weakSelf.viewModel.cardGroup[row] // let model = weakSelf.viewModel.cardGroup[row]
cell.reloadUIWithModel(model: model) // cell.reloadUIWithModel(model: model)
} // }
weakSelf.collectionView.reloadData()
weakSelf.homeHeader?.progressBar.chaoticProgress = CGFloat(weakSelf.viewModel.totalSize) weakSelf.homeHeader?.progressBar.chaoticProgress = CGFloat(weakSelf.viewModel.totalSize)
weakSelf.reloadHeadSize() weakSelf.reloadHeadSize()
} }
......
...@@ -11,6 +11,7 @@ class VideocompressionHeadView: UIView { ...@@ -11,6 +11,7 @@ class VideocompressionHeadView: UIView {
@IBOutlet weak var sizeL: UILabel! @IBOutlet weak var sizeL: UILabel!
@IBOutlet weak var sizeW: NSLayoutConstraint!
override func awakeFromNib() { override func awakeFromNib() {
super.awakeFromNib() super.awakeFromNib()
layer.cornerRadius = 8 layer.cornerRadius = 8
...@@ -21,15 +22,20 @@ class VideocompressionHeadView: UIView { ...@@ -21,15 +22,20 @@ class VideocompressionHeadView: UIView {
let totall = PhotoManager.shared.getTotalSize(source: [PhotoManager.shared.videoModels]) let totall = PhotoManager.shared.getTotalSize(source: [PhotoManager.shared.videoModels])
let sizeKB : Double = totall/2 let sizeKB : Double = Double(totall/2000)
if sizeKB < 1000{ if sizeKB < 1000{
self.sizeL.text = String(format: "(%.2lf) KB" ,sizeKB) self.sizeL.text = String(format: "%.0fKB" ,sizeKB)
}else if sizeKB < (1000 * 1000) && sizeKB > 1000{ }else if sizeKB < (1000 * 1000) && sizeKB > 1000{
self.sizeL.text = String(format: "(%.2lf) MB" ,sizeKB/1000) self.sizeL.text = String(format: "%.0fMB" ,sizeKB/1000)
}else{ }else{
self.sizeL.text = String(format: "(%.2lf) GB" ,sizeKB/(1000 * 1000)) self.sizeL.text = String(format: "%.0fGB" ,sizeKB/(1000 * 1000))
} }
let width = sizeL.text?.textWidthFromTextString(textHeight: 21, font: UIFont.systemFont(ofSize: 14, weight: .semibold))
sizeW.constant = (width ?? 30) + 10
} }
......
...@@ -60,6 +60,7 @@ ...@@ -60,6 +60,7 @@
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/> <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<connections> <connections>
<outlet property="sizeL" destination="rDm-i5-MN1" id="AYR-5C-uKV"/> <outlet property="sizeL" destination="rDm-i5-MN1" id="AYR-5C-uKV"/>
<outlet property="sizeW" destination="ijO-3x-MyS" id="zT9-KQ-fTp"/>
</connections> </connections>
<point key="canvasLocation" x="187.78625954198472" y="250"/> <point key="canvasLocation" x="187.78625954198472" y="250"/>
</view> </view>
......
...@@ -10,19 +10,7 @@ class HomeViewModel { ...@@ -10,19 +10,7 @@ class HomeViewModel {
func config(){} func config(){}
var trash = TrashDatabase.shared.queryAll().compactMap{$0.localIdentifier}
var keep = GroupDatabase.shared.queryAll().compactMap{$0.localIdentifier}
func reloadTrashAndKeep(){
trash = TrashDatabase.shared.queryAll().compactMap{$0.localIdentifier}
keep = GroupDatabase.shared.queryAll().compactMap{$0.localIdentifier}
filterResource()
}
// 封面图片资源缓存 // 封面图片资源缓存
// actor CoverCacheActor { // actor CoverCacheActor {
// //
...@@ -114,17 +102,6 @@ class HomeViewModel { ...@@ -114,17 +102,6 @@ class HomeViewModel {
var reloadCellHeight:(() ->Void)? var reloadCellHeight:(() ->Void)?
// var dupCoverImage:[AssetModel] = []
//
// var similarCoverImage:[AssetModel] = []
//
// var headCoverImages:[[AssetModel]]{
// return [
// dupCoverImage,
// similarCoverImage
// ]
// }
func setupBindings() { func setupBindings() {
NotificationCenter.default.addObserver(forName: .getBaseAssetsSuccess, object: nil, queue: nil) {[weak self] _ in NotificationCenter.default.addObserver(forName: .getBaseAssetsSuccess, object: nil, queue: nil) {[weak self] _ in
...@@ -283,48 +260,14 @@ class HomeViewModel { ...@@ -283,48 +260,14 @@ class HomeViewModel {
} }
} }
} func reloadTrashAndKeep(){
photoManager.reloadTrashAndKeep()
extension HomeViewModel{
// 基本资源过滤保留和垃圾桶资源
func filterResource(){
let filterArray = trash + keep
let others = removeAssets(withIdentifiers: filterArray, from: photoManager.otherModels)
let videos = removeAssets(withIdentifiers: filterArray, from: photoManager.videoModels)
let screens = removeAssets(withIdentifiers: filterArray, from: photoManager.screenShotModels)
photoManager.filterOtherModels = others
photoManager.filterVideoModels = videos
photoManager.filterScreenShotModels = screens
let similarPhotos = filterGroups(photoManager.similarModels, byExcludingIDs: filterArray)
let similarVideos = filterGroups(photoManager.similarVideoModels, byExcludingIDs: filterArray)
let similarShots = filterGroups(photoManager.similarScreenShotModels, byExcludingIDs: filterArray)
photoManager.filterSimilarModels = similarPhotos
photoManager.filterSimilarVideoModels = similarVideos
photoManager.filterSimilarScreenShotModels = similarShots
reloadCellHeight?() reloadCellHeight?()
} }
func removeAssets(withIdentifiers identifiers: [String], from assets: [AssetModel]) -> [AssetModel] {
let identifierSet = Set(identifiers)
return assets.filter { !identifierSet.contains($0.localIdentifier) }
}
func filterGroups(_ groups: [[AssetModel]], byExcludingIDs ids: [String]) -> [[AssetModel]] { func filterResource(){
let excludeSet = Set(ids) photoManager.filterResource()
return groups.filter { group in
// 检查子数组中是否所有元素的ID都不在排除列表中
group.allSatisfy { !excludeSet.contains($0.localIdentifier) }
}
} }
} }
...@@ -17,6 +17,10 @@ class MaintaiDetailViewController: BaseViewController { ...@@ -17,6 +17,10 @@ class MaintaiDetailViewController: BaseViewController {
var scrollIndex = 0 var scrollIndex = 0
var isFirstLoad = true var isFirstLoad = true
var removeBtn:UIButton!
var isSelectAll = false
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
configUI() configUI()
...@@ -40,25 +44,56 @@ class MaintaiDetailViewController: BaseViewController { ...@@ -40,25 +44,56 @@ class MaintaiDetailViewController: BaseViewController {
weakSelf.maintaiTipsAlertView.disMiss() weakSelf.maintaiTipsAlertView.disMiss()
} }
maintaiTipsAlertView.cancelBlock = {[weak self] in
guard let weakSelf = self else { return }
weakSelf.isSelectAll = false
}
maintaiBottomView.removeMaintaiBlock = {[weak self] in maintaiBottomView.removeMaintaiBlock = {[weak self] in
guard let weakSelf = self else { return } guard let weakSelf = self else { return }
weakSelf.maintaiTipsAlertView.show() weakSelf.maintaiTipsAlertView.show()
} }
removeBtn = UIButton()
removeBtn.setTitle("Un-keep All", for: .normal)
removeBtn.addTarget(self, action: #selector(removeAll), for: .touchUpInside)
removeBtn.setTitleColor(.black, for: .normal)
removeBtn.titleLabel?.font = UIFont.systemFont(ofSize: 14, weight: .semibold)
removeBtn.isHidden = true
titleView.addSubview(removeBtn)
removeBtn.snp.makeConstraints { make in
make.right.equalTo(-16)
make.centerY.equalTo(titleView.titleLabel)
}
}
// 移除所有
@objc func removeAll(){
isSelectAll = true
maintaiTipsAlertView.show()
} }
func removeFromKeep(){ func removeFromKeep(){
if isSelectAll{
let ids = selectAsset.flatMap{$0}.compactMap{$0.localIdentifier} let ids = viewModel.souces.flatMap{$0}.compactMap{$0.localIdentifier}
if GroupDatabase.shared.batchDelete(localIdentifiers: ids){
if GroupDatabase.shared.batchDelete(localIdentifiers: ids){ self.navigationController?.popViewController(animated: true)
getData() }
selectAsset.removeAll() isSelectAll = false
}else{
let ids = selectAsset.flatMap{$0}.compactMap{$0.localIdentifier}
if GroupDatabase.shared.batchDelete(localIdentifiers: ids){
getData()
selectAsset.removeAll()
}
} }
} }
func dealBottomView(){ func dealBottomView(){
if selectAsset.count > 0{ if selectAsset.count > 0{
let count = selectAsset.flatMap{$0}.count let count = selectAsset.flatMap{$0}.count
maintaiBottomView.numberL.text = "\(count)" maintaiBottomView.numberL.text = "\(count)"
...@@ -72,10 +107,16 @@ class MaintaiDetailViewController: BaseViewController { ...@@ -72,10 +107,16 @@ class MaintaiDetailViewController: BaseViewController {
func getData(){ func getData(){
viewModel.refreshData {[weak self] in viewModel.refreshData {[weak self] in
guard let self = self else { return } guard let self = self else { return }
self.removeBtn.isHidden = viewModel.souces.count == 0
self.tableView.reloadData() self.tableView.reloadData()
if self.isFirstLoad{ if self.isFirstLoad{
self.tableView.scrollToRow(at: IndexPath.init(row: self.scrollIndex, section: 0), at: .middle, animated: true) self.tableView.layoutIfNeeded()
self.isFirstLoad = false self.isFirstLoad = false
// 异步执行滚动,确保布局已稳定
DispatchQueue.main.async {
self.tableView.scrollToRow(at: IndexPath.init(row: self.scrollIndex, section: 0), at: .middle, animated: true)
}
} }
} }
} }
...@@ -123,7 +164,7 @@ extension MaintaiDetailViewController:UITableViewDelegate,UITableViewDataSource{ ...@@ -123,7 +164,7 @@ extension MaintaiDetailViewController:UITableViewDelegate,UITableViewDataSource{
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if viewModel.souces[indexPath.row].first?.mediaType == 1,viewModel.souces[indexPath.row].count > 1{ if viewModel.souces[indexPath.row].first?.mediaType == 1{
let cell = tableView.dequeueReusableCell(withIdentifier: "MaintaiDetailTableViewCell") as! MaintaiDetailTableViewCell let cell = tableView.dequeueReusableCell(withIdentifier: "MaintaiDetailTableViewCell") as! MaintaiDetailTableViewCell
......
...@@ -15,6 +15,9 @@ class MaintainViewListController: BaseViewController { ...@@ -15,6 +15,9 @@ class MaintainViewListController: BaseViewController {
var viewModel:MaintaiViewModel = MaintaiViewModel() var viewModel:MaintaiViewModel = MaintaiViewModel()
var removeBtn:UIButton!
var isSelectAll = false
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
...@@ -37,22 +40,54 @@ class MaintainViewListController: BaseViewController { ...@@ -37,22 +40,54 @@ class MaintainViewListController: BaseViewController {
weakSelf.maintaiTipsAlertView.disMiss() weakSelf.maintaiTipsAlertView.disMiss()
} }
maintaiTipsAlertView.cancelBlock = {[weak self] in
guard let weakSelf = self else { return }
weakSelf.isSelectAll = false
}
maintaiBottomView.removeMaintaiBlock = {[weak self] in maintaiBottomView.removeMaintaiBlock = {[weak self] in
guard let weakSelf = self else { return } guard let weakSelf = self else { return }
weakSelf.maintaiTipsAlertView.show() weakSelf.maintaiTipsAlertView.show()
} }
removeBtn = UIButton()
removeBtn.setTitle("Un-keep All", for: .normal)
removeBtn.addTarget(self, action: #selector(removeAll), for: .touchUpInside)
removeBtn.setTitleColor(.black, for: .normal)
removeBtn.titleLabel?.font = UIFont.systemFont(ofSize: 14, weight: .semibold)
removeBtn.isHidden = true
titleView.addSubview(removeBtn)
removeBtn.snp.makeConstraints { make in
make.right.equalTo(-16)
make.centerY.equalTo(titleView.titleLabel)
}
}
// 移除所有
@objc func removeAll(){
isSelectAll = true
maintaiTipsAlertView.show()
} }
func removeFromKeep(){ func removeFromKeep(){
if isSelectAll{
let ids = selectAsset.flatMap{$0}.compactMap{$0.localIdentifier} let ids = viewModel.souces.flatMap{$0}.compactMap{$0.localIdentifier}
if GroupDatabase.shared.batchDelete(localIdentifiers: ids){
getData()
selectAsset.removeAll()
}
isSelectAll = false
}else{
let ids = selectAsset.flatMap{$0}.compactMap{$0.localIdentifier}
if GroupDatabase.shared.batchDelete(localIdentifiers: ids){ if GroupDatabase.shared.batchDelete(localIdentifiers: ids){
getData() getData()
selectAsset.removeAll() selectAsset.removeAll()
}
} }
} }
func getData(){ func getData(){
...@@ -63,7 +98,7 @@ class MaintainViewListController: BaseViewController { ...@@ -63,7 +98,7 @@ class MaintainViewListController: BaseViewController {
} else { } else {
self.maintaiEmptyView.removeFromSuperview() self.maintaiEmptyView.removeFromSuperview()
} }
self.removeBtn.isHidden = viewModel.souces.count == 0
self.collectionView.reloadData() self.collectionView.reloadData()
} }
} }
......
...@@ -39,7 +39,6 @@ class MaintaiDetailTableViewCell: UITableViewCell { ...@@ -39,7 +39,6 @@ class MaintaiDetailTableViewCell: UITableViewCell {
} }
} }
func configMain(){ func configMain(){
let layout = UICollectionViewFlowLayout() let layout = UICollectionViewFlowLayout()
...@@ -47,6 +46,7 @@ class MaintaiDetailTableViewCell: UITableViewCell { ...@@ -47,6 +46,7 @@ class MaintaiDetailTableViewCell: UITableViewCell {
layout.minimumLineSpacing = 0 layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0 layout.minimumInteritemSpacing = 0
layout.scrollDirection = .horizontal layout.scrollDirection = .horizontal
// layout.sectionInset = UIEdgeInsets(top: 0, left: 12, bottom: 0, right: 12)
mainCollectionView.setCollectionViewLayout(layout, animated: false) mainCollectionView.setCollectionViewLayout(layout, animated: false)
mainCollectionView.dataSource = self mainCollectionView.dataSource = self
...@@ -121,11 +121,7 @@ extension MaintaiDetailTableViewCell:UICollectionViewDelegate,UICollectionViewDa ...@@ -121,11 +121,7 @@ extension MaintaiDetailTableViewCell:UICollectionViewDelegate,UICollectionViewDa
selectCallBack?(selectAsset) selectCallBack?(selectAsset)
} }
} }
extension MaintaiDetailTableViewCell:UIScrollViewDelegate{ extension MaintaiDetailTableViewCell:UIScrollViewDelegate{
...@@ -173,6 +169,15 @@ extension MaintaiDetailTableViewCell:UIScrollViewDelegate{ ...@@ -173,6 +169,15 @@ extension MaintaiDetailTableViewCell:UIScrollViewDelegate{
} }
} }
// func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
// if scrollView == mainCollectionView{
// let currentOffset = targetContentOffset.pointee.x
// let page = round(currentOffset / bigW) // 计算目标页(以 Cell 宽度为基准)
// targetContentOffset.pointee.x = page * bigW // 强制对齐到 Cell 边界
// Print("强制对齐",targetContentOffset.pointee.x )
// }
// }
func setPageOffetX(offsetX:CGFloat){ func setPageOffetX(offsetX:CGFloat){
print("----结束滑动----小图") print("----结束滑动----小图")
Print("小图滑动距离",offsetX) Print("小图滑动距离",offsetX)
......
...@@ -10,12 +10,18 @@ import AVKit ...@@ -10,12 +10,18 @@ import AVKit
class MaintaiDetialVideoCell: UITableViewCell { class MaintaiDetialVideoCell: UITableViewCell {
private var player: AVPlayer? // private var player: AVPlayer?
private var playerLayer: AVPlayerLayer? // private var playerLayer: AVPlayerLayer?
var selectBtn:UIButton! var selectBtn:UIButton!
var selectChangeBlock:(() ->Void)? var selectChangeBlock:(() ->Void)?
// var videoPath:String?{
// didSet{
// setPlayerInfo()
// }
// }
override func awakeFromNib() { override func awakeFromNib() {
super.awakeFromNib() super.awakeFromNib()
setupPlayer() setupPlayer()
...@@ -31,22 +37,25 @@ class MaintaiDetialVideoCell: UITableViewCell { ...@@ -31,22 +37,25 @@ class MaintaiDetialVideoCell: UITableViewCell {
contentView.bringSubviewToFront(selectBtn) contentView.bringSubviewToFront(selectBtn)
selectBtn.addTarget(self, action: #selector(selectChange), for: .touchUpInside) selectBtn.addTarget(self, action: #selector(selectChange), for: .touchUpInside)
selectBtn.snp.makeConstraints { make in selectBtn.snp.makeConstraints { make in
make.right.bottom.equalTo(0) make.right.equalTo(-15)
make.bottom.equalTo(-10)
make.size.equalTo(30) make.size.equalTo(30)
} }
} }
private func setupPlayer() { private func setupPlayer() {
// 创建播放器层 // 创建播放器层
playerLayer = AVPlayerLayer() // playerLayer = AVPlayerLayer()
playerLayer?.videoGravity = .resizeAspect // playerLayer?.videoGravity = .resizeAspect
contentView.layer.addSublayer(playerLayer!) contentView.layer.addSublayer(playerLayer)
} }
override func layoutSubviews() { override func layoutSubviews() {
super.layoutSubviews() super.layoutSubviews()
// 设置播放器层的frame // 设置播放器层的frame
playerLayer?.frame = CGRect(x: 0, y: 10, width: contentView.width, height: contentView.height-20) playerLayer.frame = CGRect(x: 15, y: 10, width: contentView.width-30, height: contentView.height-20)
playerLayer.cornerRadius = 15
playerLayer.masksToBounds = true
} }
var model:AssetModel?{ var model:AssetModel?{
...@@ -62,46 +71,72 @@ class MaintaiDetialVideoCell: UITableViewCell { ...@@ -62,46 +71,72 @@ class MaintaiDetialVideoCell: UITableViewCell {
} }
} }
lazy var videoPlayer:AVPlayer = {
let palyer = AVPlayer.init()
palyer.volume = 0
return palyer
}()
lazy var playerLayer:AVPlayerLayer = {
let playerLayer = AVPlayerLayer.init(player: videoPlayer)
playerLayer.backgroundColor = UIColor.black.cgColor
return playerLayer
}()
func configure(with videoURL: URL?) { func configure(with videoURL: URL?) {
guard let videoURL = videoURL else{ guard let videoURL = videoURL else{
player?.pause() videoPlayer.pause()
player = nil
return return
} }
// 创建播放器项
let playerItem = AVPlayerItem(url: videoURL)
// 创建播放器
player = AVPlayer(playerItem: playerItem)
playerLayer?.player = player let item = AVPlayerItem.init(url: videoURL)
// 设置静音 videoPlayer.replaceCurrentItem(with: item)
player?.volume = 0
// 播放视频 videoPlayer.play()
player?.play()
// // 创建播放器项
// let playerItem = AVPlayerItem(url: videoURL)
//
// // 创建播放器
// player = AVPlayer(playerItem: playerItem)
// playerLayer?.player = player
//
// // 设置静音
// player?.volume = 0
//
// // 播放视频
// player?.play()
//
// 添加播放完成的观察者 // 添加播放完成的观察者
NotificationCenter.default.addObserver(self, NotificationCenter.default.addObserver(self,
selector: #selector(playerDidFinishPlaying), selector: #selector(playerDidFinishPlaying),
name: .AVPlayerItemDidPlayToEndTime, name: .AVPlayerItemDidPlayToEndTime,
object: playerItem) object: item)
} }
//
@objc private func playerDidFinishPlaying() { @objc private func playerDidFinishPlaying() {
// 播放结束后不做任何操作,因为只需要播放一次 // // 播放结束后不做任何操作,因为只需要播放一次
player?.pause() // player?.pause()
player?.seek(to: .zero) // player?.seek(to: .zero)
//playerLayer.player?.seek(to: .zero)
videoPlayer.seek(to: .zero)
videoPlayer.play()
} }
//
override func prepareForReuse() { // override func prepareForReuse() {
super.prepareForReuse() // super.prepareForReuse()
// 清理播放器 // // 清理播放器
player?.pause() // player?.pause()
player = nil // player = nil
NotificationCenter.default.removeObserver(self) // NotificationCenter.default.removeObserver(self)
} // }
//
@objc func selectChange(){ @objc func selectChange(){
selectChangeBlock?() selectChangeBlock?()
......
...@@ -10,6 +10,8 @@ import UIKit ...@@ -10,6 +10,8 @@ import UIKit
class MaintaiTipsAlertView: UIView { class MaintaiTipsAlertView: UIView {
var confirmBlock:(() ->Void)? var confirmBlock:(() ->Void)?
var cancelBlock:(() ->Void)?
override func awakeFromNib() { override func awakeFromNib() {
super.awakeFromNib() super.awakeFromNib()
...@@ -25,6 +27,7 @@ class MaintaiTipsAlertView: UIView { ...@@ -25,6 +27,7 @@ class MaintaiTipsAlertView: UIView {
} }
func disMiss(){ func disMiss(){
cancelBlock?()
self.removeFromSuperview() self.removeFromSuperview()
} }
......
...@@ -93,7 +93,7 @@ class PhotoAndVideoMananger { ...@@ -93,7 +93,7 @@ class PhotoAndVideoMananger {
class func getPrivacy(suc:@escaping callBack<Any> = {text in}) { class func getPrivacy(suc:@escaping callBack<Any> = {text in}) {
PHPhotoLibrary.requestAuthorization { status in PHPhotoLibrary.requestAuthorization { status in
PhotoAndVideoMananger.mananger.permissionStatus = status PhotoManager.shared.permissionStatus = status
switch status { switch status {
case .authorized: case .authorized:
// 用户授权访问照片库,可以继续操作 // 用户授权访问照片库,可以继续操作
......
...@@ -138,6 +138,19 @@ extension String { ...@@ -138,6 +138,19 @@ extension String {
.replacingOccurrences(of: "\r", with: "") .replacingOccurrences(of: "\r", with: "")
return cleaned return cleaned
} }
/// 动态计算宽度
/// - Parameters:
/// - textWidth: <#textWidth description#>
/// - font: <#font description#>
/// - isBold: <#isBold description#>
/// - Returns: <#description#>
public func textWidthFromTextString(textHeight: CGFloat, font: UIFont) -> CGFloat {
let dict: NSDictionary = NSDictionary(object: font,forKey: NSAttributedString.Key.font as NSCopying)
let rect: CGRect = (self as NSString).boundingRect(with: CGSize(width: CGFloat(MAXFLOAT), height: textHeight), options: [NSStringDrawingOptions.truncatesLastVisibleLine, NSStringDrawingOptions.usesFontLeading,NSStringDrawingOptions.usesLineFragmentOrigin],attributes: dict as? [NSAttributedString.Key : Any] ,context: nil)
return rect.size.width
}
} }
......
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