Commit acba8491 authored by shenyong's avatar shenyong

保留列表 移除openvc 重复判断处理

parent b3db5b71
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
04CF31772DA7E790001C87CA /* ChargeShow.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 04CF316F2DA7E78F001C87CA /* ChargeShow.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 04CF31772DA7E790001C87CA /* ChargeShow.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 04CF316F2DA7E78F001C87CA /* ChargeShow.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
04CF317D2DA7E7BE001C87CA /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 04CF317C2DA7E7BE001C87CA /* Intents.intentdefinition */; }; 04CF317D2DA7E7BE001C87CA /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 04CF317C2DA7E7BE001C87CA /* Intents.intentdefinition */; };
04CF317E2DA7E7BE001C87CA /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 04CF317C2DA7E7BE001C87CA /* Intents.intentdefinition */; }; 04CF317E2DA7E7BE001C87CA /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 04CF317C2DA7E7BE001C87CA /* Intents.intentdefinition */; };
04EC294B2DD33B480049739A /* GoogleSignIn in Frameworks */ = {isa = PBXBuildFile; productRef = 04EC294A2DD33B480049739A /* GoogleSignIn */; };
3A00E856852A8783E544CD7D /* Pods_PhoneManager.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6028F60B696E2F97EAA2325C /* Pods_PhoneManager.framework */; }; 3A00E856852A8783E544CD7D /* Pods_PhoneManager.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6028F60B696E2F97EAA2325C /* Pods_PhoneManager.framework */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
...@@ -83,6 +84,7 @@ ...@@ -83,6 +84,7 @@
04BD915F2D9D68AD00055CEB /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; 04BD915F2D9D68AD00055CEB /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
04CF316F2DA7E78F001C87CA /* ChargeShow.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ChargeShow.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 04CF316F2DA7E78F001C87CA /* ChargeShow.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ChargeShow.appex; sourceTree = BUILT_PRODUCTS_DIR; };
04CF317C2DA7E7BE001C87CA /* Intents.intentdefinition */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; path = Intents.intentdefinition; sourceTree = "<group>"; }; 04CF317C2DA7E7BE001C87CA /* Intents.intentdefinition */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; path = Intents.intentdefinition; sourceTree = "<group>"; };
04EC294C2DD342220049739A /* SVProgressHUD.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SVProgressHUD.framework; sourceTree = BUILT_PRODUCTS_DIR; };
295785B9009F20AC4C1C36C4 /* Pods-PhoneManager.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PhoneManager.debug.xcconfig"; path = "Target Support Files/Pods-PhoneManager/Pods-PhoneManager.debug.xcconfig"; sourceTree = "<group>"; }; 295785B9009F20AC4C1C36C4 /* Pods-PhoneManager.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PhoneManager.debug.xcconfig"; path = "Target Support Files/Pods-PhoneManager/Pods-PhoneManager.debug.xcconfig"; sourceTree = "<group>"; };
6028F60B696E2F97EAA2325C /* Pods_PhoneManager.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PhoneManager.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 6028F60B696E2F97EAA2325C /* Pods_PhoneManager.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PhoneManager.framework; sourceTree = BUILT_PRODUCTS_DIR; };
8937DC9D81CEDE823C329A80 /* Pods-PhoneManager.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PhoneManager.release.xcconfig"; path = "Target Support Files/Pods-PhoneManager/Pods-PhoneManager.release.xcconfig"; sourceTree = "<group>"; }; 8937DC9D81CEDE823C329A80 /* Pods-PhoneManager.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PhoneManager.release.xcconfig"; path = "Target Support Files/Pods-PhoneManager/Pods-PhoneManager.release.xcconfig"; sourceTree = "<group>"; };
...@@ -196,6 +198,7 @@ ...@@ -196,6 +198,7 @@
files = ( files = (
3A00E856852A8783E544CD7D /* Pods_PhoneManager.framework in Frameworks */, 3A00E856852A8783E544CD7D /* Pods_PhoneManager.framework in Frameworks */,
04BBB4E62DC0748F00D7E3AB /* StoreKit.framework in Frameworks */, 04BBB4E62DC0748F00D7E3AB /* StoreKit.framework in Frameworks */,
04EC294B2DD33B480049739A /* GoogleSignIn in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
...@@ -205,6 +208,7 @@ ...@@ -205,6 +208,7 @@
27ECDADD9059AB5043B8E1E9 /* Frameworks */ = { 27ECDADD9059AB5043B8E1E9 /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
04EC294C2DD342220049739A /* SVProgressHUD.framework */,
04BBB4E52DC0748F00D7E3AB /* StoreKit.framework */, 04BBB4E52DC0748F00D7E3AB /* StoreKit.framework */,
6028F60B696E2F97EAA2325C /* Pods_PhoneManager.framework */, 6028F60B696E2F97EAA2325C /* Pods_PhoneManager.framework */,
04BD915D2D9D68AD00055CEB /* WidgetKit.framework */, 04BD915D2D9D68AD00055CEB /* WidgetKit.framework */,
...@@ -378,6 +382,9 @@ ...@@ -378,6 +382,9 @@
); );
mainGroup = EB388E522D8A61A800629B0D; mainGroup = EB388E522D8A61A800629B0D;
minimizedProjectReferenceProxies = 1; minimizedProjectReferenceProxies = 1;
packageReferences = (
04EC26182DD33B070049739A /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */,
);
preferredProjectObjectVersion = 77; preferredProjectObjectVersion = 77;
productRefGroup = EB388E5C2D8A61A800629B0D /* Products */; productRefGroup = EB388E5C2D8A61A800629B0D /* Products */;
projectDirPath = ""; projectDirPath = "";
...@@ -990,6 +997,25 @@ ...@@ -990,6 +997,25 @@
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
/* End XCConfigurationList section */ /* End XCConfigurationList section */
/* Begin XCRemoteSwiftPackageReference section */
04EC26182DD33B070049739A /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/google/GoogleSignIn-iOS";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 8.0.0;
};
};
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
04EC294A2DD33B480049739A /* GoogleSignIn */ = {
isa = XCSwiftPackageProductDependency;
package = 04EC26182DD33B070049739A /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */;
productName = GoogleSignIn;
};
/* End XCSwiftPackageProductDependency section */
}; };
rootObject = EB388E532D8A61A800629B0D /* Project object */; rootObject = EB388E532D8A61A800629B0D /* Project object */;
} }
{
"originHash" : "68aa00e3cba36db2e0a76f54dbc84d1f079d8aa9a19bfa9fce02d6286bd620b7",
"pins" : [
{
"identity" : "app-check",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/app-check.git",
"state" : {
"revision" : "61b85103a1aeed8218f17c794687781505fbbef5",
"version" : "11.2.0"
}
},
{
"identity" : "appauth-ios",
"kind" : "remoteSourceControl",
"location" : "https://github.com/openid/AppAuth-iOS.git",
"state" : {
"revision" : "2781038865a80e2c425a1da12cc1327bcd56501f",
"version" : "1.7.6"
}
},
{
"identity" : "googlesignin-ios",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleSignIn-iOS",
"state" : {
"revision" : "65fb3f1aa6ffbfdc79c4e22178a55cd91561f5e9",
"version" : "8.0.0"
}
},
{
"identity" : "googleutilities",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GoogleUtilities.git",
"state" : {
"revision" : "60da361632d0de02786f709bdc0c4df340f7613e",
"version" : "8.1.0"
}
},
{
"identity" : "gtm-session-fetcher",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/gtm-session-fetcher.git",
"state" : {
"revision" : "a2ab612cb980066ee56d90d60d8462992c07f24b",
"version" : "3.5.0"
}
},
{
"identity" : "gtmappauth",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/GTMAppAuth.git",
"state" : {
"revision" : "5d7d66f647400952b1758b230e019b07c0b4b22a",
"version" : "4.1.1"
}
},
{
"identity" : "promises",
"kind" : "remoteSourceControl",
"location" : "https://github.com/google/promises.git",
"state" : {
"revision" : "540318ecedd63d883069ae7f1ed811a2df00b6ac",
"version" : "2.4.0"
}
}
],
"version" : 3
}
This diff is collapsed.
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Rectangle_1543788350@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "ic_list_setting@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "ic_list_setting@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "重复项说明-icon-选中@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "重复项说明-icon-选中@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Frame@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Frame@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Frame@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Frame@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Vector@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Vector@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
...@@ -197,6 +197,63 @@ class TrashDatabase { ...@@ -197,6 +197,63 @@ class TrashDatabase {
sqlite3_finalize(queryStatement) sqlite3_finalize(queryStatement)
return result return result
} }
// 批量删除数据
func batchDelete(localIdentifiers: [String]) -> Bool {
// 使用事务来确保原子性
let beginTransaction = "BEGIN TRANSACTION;"
let commitTransaction = "COMMIT TRANSACTION;"
let rollbackTransaction = "ROLLBACK TRANSACTION;"
// 准备删除语句
let deleteStatementString = "DELETE FROM trash WHERE localIdentifier = ?;"
var deleteStatement: OpaquePointer?
// 开始事务
if sqlite3_exec(db, beginTransaction, nil, nil, nil) != SQLITE_OK {
print("开始事务失败")
return false
}
// 准备删除语句
if sqlite3_prepare_v2(db, deleteStatementString, -1, &deleteStatement, nil) == SQLITE_OK {
// 遍历所有需要删除的标识符
for identifier in localIdentifiers {
// 重置语句以重新使用
sqlite3_reset(deleteStatement)
// 绑定参数
sqlite3_bind_text(deleteStatement, 1, (identifier as NSString).utf8String, -1, nil)
// 执行删除
if sqlite3_step(deleteStatement) != SQLITE_DONE {
print("删除数据失败: \(identifier)")
// 如果有任何失败,回滚事务
sqlite3_exec(db, rollbackTransaction, nil, nil, nil)
sqlite3_finalize(deleteStatement)
return false
}
}
// 完成后释放语句
sqlite3_finalize(deleteStatement)
// 提交事务
if sqlite3_exec(db, commitTransaction, nil, nil, nil) == SQLITE_OK {
print("成功批量删除数据")
return true
} else {
print("提交事务失败")
sqlite3_exec(db, rollbackTransaction, nil, nil, nil)
return false
}
}
// 如果准备语句失败
print("准备删除语句失败")
sqlite3_exec(db, rollbackTransaction, nil, nil, nil)
return false
}
deinit { deinit {
sqlite3_close(db) sqlite3_close(db)
......
...@@ -37,6 +37,7 @@ class PhotoDuplicateManager: @unchecked Sendable { ...@@ -37,6 +37,7 @@ class PhotoDuplicateManager: @unchecked Sendable {
func findDuplicateAssets(in assets: [PHAsset], func findDuplicateAssets(in assets: [PHAsset],
mediaType: MediaType = .photo, mediaType: MediaType = .photo,
loacalHandler: (([[AssetModel]]) -> Void)?,
progressHandler: (([AssetModel]) -> Void)?, progressHandler: (([AssetModel]) -> Void)?,
completionHandler: (([[AssetModel]]) -> Void)?) { completionHandler: (([[AssetModel]]) -> Void)?) {
...@@ -49,29 +50,36 @@ class PhotoDuplicateManager: @unchecked Sendable { ...@@ -49,29 +50,36 @@ class PhotoDuplicateManager: @unchecked Sendable {
let cachedGroups = await stateManager.getAllDuplicateGroups() let cachedGroups = await stateManager.getAllDuplicateGroups()
print("通知已缓存的结果", cachedGroups.count) print("通知已缓存的结果", cachedGroups.count)
await MainActor.run { await MainActor.run {
for group in cachedGroups { // for group in cachedGroups {
progressHandler?(group.assets) // progressHandler?(group.assets)
} // }
let groups = cachedGroups.map{$0.assets}
loacalHandler?(groups)
} }
// 4. 按分辨率预分组 // 4. 按特征预分组(分辨率、时间、文件大小)
var resolutionGroups: [[PHAsset]] = [] var featureGroups: [[PHAsset]] = []
var tempGroups: [String: [PHAsset]] = [:] // 临时用于分组 var tempGroups: [String: [PHAsset]] = [:] // 临时用于分组
// 第一次遍历:收集相同分辨率的资源 // 第一次遍历:收集相同特征的资源
for asset in assets { for asset in assets {
// 创建特征键(分辨率+创建时间+文件大小)
let resolution = "\(asset.pixelWidth)x\(asset.pixelHeight)" let resolution = "\(asset.pixelWidth)x\(asset.pixelHeight)"
if tempGroups[resolution] == nil { // let createTime = asset.creationDate?.timeIntervalSince1970 ?? 0
tempGroups[resolution] = [] let fileSize = getAssetSize(asset)
let featureKey = "\(resolution)_\(fileSize)"
if tempGroups[featureKey] == nil {
tempGroups[featureKey] = []
} }
tempGroups[resolution]?.append(asset) tempGroups[featureKey]?.append(asset)
} }
// 第二次遍历:只保留有多个资源的组 // 第二次遍历:只保留有多个资源的组
resolutionGroups = tempGroups.values.filter { $0.count > 1 } featureGroups = tempGroups.values.filter { $0.count > 1 }
// 如果没有需要处理的组,直接返回缓存结果 // 如果没有需要处理的组,直接返回缓存结果
if resolutionGroups.isEmpty { if featureGroups.isEmpty {
let total = cachedGroups.map { $0.assets } let total = cachedGroups.map { $0.assets }
await MainActor.run { await MainActor.run {
completionHandler?(total) completionHandler?(total)
...@@ -80,18 +88,18 @@ class PhotoDuplicateManager: @unchecked Sendable { ...@@ -80,18 +88,18 @@ class PhotoDuplicateManager: @unchecked Sendable {
} }
let maxConcurrency = 2 // 最大并发数 let maxConcurrency = 2 // 最大并发数
let batchSize = max(1, resolutionGroups.count / maxConcurrency) let batchSize = max(1, featureGroups.count / maxConcurrency)
for batchIndex in stride(from: 0, to: resolutionGroups.count, by: batchSize) { for batchIndex in stride(from: 0, to: featureGroups.count, by: batchSize) {
let endIndex = min(batchIndex + batchSize, resolutionGroups.count) let endIndex = min(batchIndex + batchSize, featureGroups.count)
let batch = Array(resolutionGroups[batchIndex..<endIndex]) let batch = Array(featureGroups[batchIndex..<endIndex])
await withTaskGroup(of: Void.self) { group in await withTaskGroup(of: Void.self) { group in
for assets in batch { for assets in batch {
group.addTask { [weak self] in group.addTask { [weak self] in
guard let self = self else { return } guard let self = self else { return }
// 5.1 计算该组所有图片的hash // 只需要计算phash值并比较
var hashGroups: [String: [PHAsset]] = [:] var hashGroups: [String: [PHAsset]] = [:]
for asset in assets { for asset in assets {
...@@ -103,12 +111,10 @@ class PhotoDuplicateManager: @unchecked Sendable { ...@@ -103,12 +111,10 @@ class PhotoDuplicateManager: @unchecked Sendable {
} }
} }
// 5.2 找出完全相同的组 // 找出phash值相同的组
let duplicateGroups = hashGroups.values.filter { group in let duplicateGroups = hashGroups.values.filter { $0.count > 1 }
group.count > 1 && self.areAssetsExactlyDuplicate(group)
}
// 5.3 处理找到的重复组 // 处理找到的重复组
for duplicateGroup in duplicateGroups { for duplicateGroup in duplicateGroups {
let groupId = UUID().uuidString let groupId = UUID().uuidString
let assetModels = await self.createAssetModels(from: duplicateGroup) let assetModels = await self.createAssetModels(from: duplicateGroup)
...@@ -165,13 +171,13 @@ class PhotoDuplicateManager: @unchecked Sendable { ...@@ -165,13 +171,13 @@ class PhotoDuplicateManager: @unchecked Sendable {
} }
// 检查创建时间是否接近(避免连拍照片) // 检查创建时间是否接近(避免连拍照片)
if let time1 = asset.creationDate, // if let time1 = asset.creationDate,
let time2 = firstAsset.creationDate { // let time2 = firstAsset.creationDate {
let timeDiff = abs(time1.timeIntervalSince(time2)) // let timeDiff = abs(time1.timeIntervalSince(time2))
if timeDiff < 1.0 { // 1秒内的连拍照片不算重复 // if timeDiff < 1.0 { // 1秒内的连拍照片不算重复
return false // return false
} // }
} // }
return true return true
} }
......
...@@ -571,6 +571,18 @@ extension PhotoManager{ ...@@ -571,6 +571,18 @@ extension PhotoManager{
} }
} }
// 根据Id获取PHAsset
func fetchAsset(with localIdentifier: String, completion: @escaping (PHAsset?) -> Void) {
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format: "localIdentifier == %@", localIdentifier)
let assets = PHAsset.fetchAssets(with: fetchOptions)
if let asset = assets.firstObject {
completion(asset)
} else {
completion(nil)
}
}
// 获取所有资产的总数 // 获取所有资产的总数
// func fetchTotalAssets(completion: @escaping ([PHAsset]) -> Void) { // func fetchTotalAssets(completion: @escaping ([PHAsset]) -> Void) {
// DispatchQueue.global(qos: .background).async { // DispatchQueue.global(qos: .background).async {
......
...@@ -12,7 +12,8 @@ struct AssetModel :Codable,Hashable { ...@@ -12,7 +12,8 @@ struct AssetModel :Codable,Hashable {
var localIdentifier : String var localIdentifier : String
var assetSize : Double var assetSize : Double
var createDate : Date var createDate : Date
var mediaType:Int // 1 图片 2视频 var mediaType:Int? // 1 图片 2视频
init(localIdentifier: String, assetSize: Double, createDate: Date,mediaType:Int = 1) { init(localIdentifier: String, assetSize: Double, createDate: Date,mediaType:Int = 1) {
self.localIdentifier = localIdentifier self.localIdentifier = localIdentifier
...@@ -27,12 +28,13 @@ struct AssetModel :Codable,Hashable { ...@@ -27,12 +28,13 @@ struct AssetModel :Codable,Hashable {
hasher.combine(createDate) hasher.combine(createDate)
hasher.combine(mediaType) hasher.combine(mediaType)
} }
static func ==(lhs: AssetModel, rhs: AssetModel) -> Bool { static func ==(lhs: AssetModel, rhs: AssetModel) -> Bool {
return lhs.localIdentifier == rhs.localIdentifier && return lhs.localIdentifier == rhs.localIdentifier &&
lhs.assetSize == rhs.assetSize && lhs.assetSize == rhs.assetSize &&
lhs.createDate == rhs.createDate && lhs.mediaType == rhs.mediaType lhs.createDate == rhs.createDate && lhs.mediaType == rhs.mediaType
} }
} }
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
import Foundation import Foundation
import Contacts import Contacts
import UIKit
class ContactAllViewController : BaseViewController { class ContactAllViewController : BaseViewController {
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
import Foundation import Foundation
import Contacts import Contacts
import UIKit
class ContactBackupDetailViewController : BaseViewController { class ContactBackupDetailViewController : BaseViewController {
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
// //
import Foundation import Foundation
import UIKit
class ContactBackupViewController : BaseViewController { class ContactBackupViewController : BaseViewController {
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
import Foundation import Foundation
import SnapKit import SnapKit
import Contacts import Contacts
import UIKit
class ContactIncompleteViewController : BaseViewController { class ContactIncompleteViewController : BaseViewController {
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
import Foundation import Foundation
import Contacts import Contacts
import UIKit
class ContactViewController : BaseViewController { class ContactViewController : BaseViewController {
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
// //
import Foundation import Foundation
import UIKit
class ContactsDupPreViewController : BaseViewController { class ContactsDupPreViewController : BaseViewController {
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
import Foundation import Foundation
import SnapKit import SnapKit
import Contacts import Contacts
import UIKit
class ContactsDupViewController : BaseViewController { class ContactsDupViewController : BaseViewController {
var dataSourceModel : [[ContactModel]]? var dataSourceModel : [[ContactModel]]?
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
import Foundation import Foundation
import SnapKit import SnapKit
import UIKit
class ContactAllView : UIView { class ContactAllView : UIView {
static let CONTACT_ALL = "contact_all" static let CONTACT_ALL = "contact_all"
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
// //
import Foundation import Foundation
import UIKit
class ContactNoAllView : UIView { class ContactNoAllView : UIView {
lazy var titleLabel: UILabel = { lazy var titleLabel: UILabel = {
let label = UILabel() let label = UILabel()
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
// //
import Foundation import Foundation
import UIKit
class ContactBackUpNormalView : UIView { class ContactBackUpNormalView : UIView {
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
// //
import Foundation import Foundation
import UIKit
class ContactNoBackUpView : UIView { class ContactNoBackUpView : UIView {
var newBackupCallback : ()->Void = {} var newBackupCallback : ()->Void = {}
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
// //
import Foundation import Foundation
import UIKit
class CustomContactAllViewTableViewCell : UITableViewCell { class CustomContactAllViewTableViewCell : UITableViewCell {
var model : ContactModel? var model : ContactModel?
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
// //
import Foundation import Foundation
import UIKit
class CustomContactBacDetailTableViewCell : UITableViewCell { class CustomContactBacDetailTableViewCell : UITableViewCell {
var model : ContactModel? var model : ContactModel?
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
// //
import Foundation import Foundation
import UIKit
class CustomContactDupPreTableViewCell : UITableViewCell { class CustomContactDupPreTableViewCell : UITableViewCell {
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
// //
import Foundation import Foundation
import UIKit
class CustomContactDupTableViewCell : UITableViewCell { class CustomContactDupTableViewCell : UITableViewCell {
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
// //
import Foundation import Foundation
import UIKit
class CustomDupHeaderView : UITableViewHeaderFooterView { class CustomDupHeaderView : UITableViewHeaderFooterView {
var model : [ContactModel] = [] { var model : [ContactModel] = [] {
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
// //
import Foundation import Foundation
import UIKit
class CustomDupPreHeaderView : UITableViewHeaderFooterView { class CustomDupPreHeaderView : UITableViewHeaderFooterView {
var model : [ContactModel] = [] { var model : [ContactModel] = [] {
......
...@@ -6,6 +6,8 @@ ...@@ -6,6 +6,8 @@
// //
import Foundation import Foundation
import UIKit
class ContactBackUpCompletedAlertView : UIView { class ContactBackUpCompletedAlertView : UIView {
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
// //
import Foundation import Foundation
import UIKit
class ContactBackUpDeleteCompletedAlertView : UIView { class ContactBackUpDeleteCompletedAlertView : UIView {
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
// //
import Foundation import Foundation
import UIKit
class ContactBackUpNoDataAlertView : UIView { class ContactBackUpNoDataAlertView : UIView {
// 懒加载背景视图 // 懒加载背景视图
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
// //
import Foundation import Foundation
import UIKit
class ContactBackupAlertView : UIView { class ContactBackupAlertView : UIView {
var sureCallBack: (Bool)->Void = {isSure in } var sureCallBack: (Bool)->Void = {isSure in }
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
// //
import Foundation import Foundation
import UIKit
class ContactNavView : UIView { class ContactNavView : UIView {
public var backButton:UIButton! public var backButton:UIButton!
......
...@@ -6,6 +6,8 @@ ...@@ -6,6 +6,8 @@
// //
import Foundation import Foundation
import UIKit
class DeleteButtonView : UIView { class DeleteButtonView : UIView {
var submitCallBack : (()->Void) = {} var submitCallBack : (()->Void) = {}
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
// //
import Foundation import Foundation
import UIKit
class RestoreButtonView : UIView { class RestoreButtonView : UIView {
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
// //
import Foundation import Foundation
import UIKit
class SelectAllButton : UIView { class SelectAllButton : UIView {
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
// //
import Foundation import Foundation
import UIKit
class ContactNoDupPreView : UIView { class ContactNoDupPreView : UIView {
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
// //
import Foundation import Foundation
import UIKit
class ContactNoDupView : UIView { class ContactNoDupView : UIView {
lazy var titleLabel: UILabel = { lazy var titleLabel: UILabel = {
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
// //
import Foundation import Foundation
import UIKit
class ContactModuleView : UIView { class ContactModuleView : UIView {
......
...@@ -146,16 +146,23 @@ class CustomProgressBar: UIView { ...@@ -146,16 +146,23 @@ class CustomProgressBar: UIView {
private let dotSize: CGFloat = 8 private let dotSize: CGFloat = 8
private let labelSpacing: CGFloat = 18 private let labelSpacing: CGFloat = 18
// 添加防抖计时器
private var updateTimer: Timer?
// 缓存上一次的进度值,用于动画
private var lastUsedProgress: CGFloat = 0
private var lastChaoticProgress: CGFloat = 0
var usedProgress: CGFloat = 0 { var usedProgress: CGFloat = 0 {
didSet{ didSet {
self.updateProgress() scheduleProgressUpdate()
} }
} }
var totalProgress: CGFloat = 0 var totalProgress: CGFloat = 0
var chaoticProgress: CGFloat = 0 { var chaoticProgress: CGFloat = 0 {
didSet{ didSet {
// Print("获取到的资源大小",chaoticProgress) scheduleProgressUpdate()
self.updateProgress()
} }
} }
...@@ -165,12 +172,6 @@ class CustomProgressBar: UIView { ...@@ -165,12 +172,6 @@ class CustomProgressBar: UIView {
let disk = WidgetPublicModel.getDiskSpace() let disk = WidgetPublicModel.getDiskSpace()
self.totalProgress = Double(disk.0) self.totalProgress = Double(disk.0)
self.usedProgress = Double(disk.0) - Double(disk.1) self.usedProgress = Double(disk.0) - Double(disk.1)
// Task {
// let photoData = await Double(StorageManager.manager.getPhotoResourceMemory())
// self.chaoticProgress = photoData
// }
} }
required init?(coder: NSCoder) { required init?(coder: NSCoder) {
...@@ -224,32 +225,53 @@ class CustomProgressBar: UIView { ...@@ -224,32 +225,53 @@ class CustomProgressBar: UIView {
idleLabel.centerY = dotY + 4 idleLabel.centerY = dotY + 4
} }
private func scheduleProgressUpdate() {
// 取消之前的计时器
updateTimer?.invalidate()
// 设置新的计时器,延迟0.2秒更新UI
updateTimer = Timer.scheduledTimer(withTimeInterval: 0.2, repeats: false) { [weak self] _ in
self?.updateProgress()
}
}
private func updateProgress() { private func updateProgress() {
// 回到主线程更新 UI // 确保在主线程更新UI
DispatchQueue.main.async { DispatchQueue.main.async { [weak self] in
let total = self.chaoticProgress + self.totalProgress guard let self = self else { return }
let usedProgress = CGFloat((self.usedProgress) / total)
let chaoticProgress = CGFloat(self.chaoticProgress / total) // 计算总容量和各部分比例
let total = max(self.totalProgress, 0.001) // 避免除以0
let usedRatio = min(max(self.usedProgress / total, 0), 1)
let chaoticRatio = min(max(self.chaoticProgress / total, 0), 1)
let totalProgress = usedProgress + chaoticProgress // 计算实际宽度
let remainingProgress = 1 - totalProgress let usedWidth = self.bounds.width * usedRatio
let chaoticWidth = self.bounds.width * chaoticRatio
let usedWidth = self.bounds.width * usedProgress // 设置进度条背景为白色
let chaoticWidth = self.bounds.width * chaoticProgress self.progressLayer.backgroundColor = self.idleColor.cgColor
let idleWidth = self.bounds.width * remainingProgress
let usedLayer = CALayer() // 使用CATransaction确保所有动画同步进行
CATransaction.begin()
CATransaction.setAnimationDuration(0.3)
CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: .easeInEaseOut))
// 更新或创建used层
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: self.progressLayer.bounds.height)
usedLayer.backgroundColor = self.usedColor.cgColor usedLayer.backgroundColor = self.usedColor.cgColor
let chaoticLayer = CALayer() // 更新或创建chaotic层
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: self.progressLayer.bounds.height)
chaoticLayer.backgroundColor = self.chaoticColor.cgColor chaoticLayer.backgroundColor = self.chaoticColor.cgColor
let idleLayer = CALayer() // 一次性设置所有子层
idleLayer.frame = CGRect(x: usedWidth + chaoticWidth, y: 0, width: idleWidth, height: self.progressLayer.bounds.height) if self.progressLayer.sublayers == nil {
idleLayer.backgroundColor = self.idleColor.cgColor self.progressLayer.sublayers = [usedLayer, chaoticLayer]
self.progressLayer.sublayers = [usedLayer, chaoticLayer, idleLayer] }
CATransaction.commit()
} }
} }
...@@ -261,3 +283,10 @@ class CustomProgressBar: UIView { ...@@ -261,3 +283,10 @@ class CustomProgressBar: UIView {
layer.addSublayer(dotLayer) layer.addSublayer(dotLayer)
} }
} }
// 添加安全数组访问扩展
private extension Array {
subscript(safe index: Index) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}
...@@ -34,16 +34,6 @@ class HomeView:UIView { ...@@ -34,16 +34,6 @@ class HomeView:UIView {
var viewModel:HomeViewModel? var viewModel:HomeViewModel?
var isScroll = false {
didSet {
// if isScroll {
// DispatchQueue.main.async {
// self.tipLabel.attributedText = self.attribet
// }
// }
}
}
var attribet:NSAttributedString? var attribet:NSAttributedString?
lazy var collectionView:UICollectionView = { lazy var collectionView:UICollectionView = {
...@@ -210,14 +200,7 @@ class HomeView:UIView { ...@@ -210,14 +200,7 @@ class HomeView:UIView {
extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICollectionViewDelegate { extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICollectionViewDelegate {
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
isScroll = true
}
func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
isScroll = true
}
func scrollViewDidScroll(_ scrollView: UIScrollView) { func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offsetY = scrollView.contentOffset.y let offsetY = scrollView.contentOffset.y
...@@ -236,16 +219,7 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol ...@@ -236,16 +219,7 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol
} }
tipLabel.centerY = centerY tipLabel.centerY = centerY
} }
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if !decelerate {
isScroll = false // 拖动停止且无需减速
}
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
isScroll = false // 减速完全停止
}
func numberOfSections(in collectionView: UICollectionView) -> Int { func numberOfSections(in collectionView: UICollectionView) -> Int {
...@@ -285,12 +259,12 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol ...@@ -285,12 +259,12 @@ extension HomeView:WaterfallMutiSectionDelegate,UICollectionViewDataSource,UICol
self.titleCallBack(model,.similar) self.titleCallBack(model,.similar)
} }
} }
cell.reloadCoverData(viewModel?.headCoverImages[indexPath.row] ?? [])
if indexPath.row == 0 { if indexPath.row == 0 {
self.dupHeadCell = cell self.dupHeadCell = cell
cell.reloadCoverData(viewModel?.dupCoverImage ?? []) // cell.reloadCoverData(viewModel?.dupCoverImage ?? [])
}else{ }else{
cell.reloadCoverData(viewModel?.similarCoverImage ?? []) // cell.reloadCoverData(viewModel?.similarCoverImage ?? [])
self.similarHeadCell = cell self.similarHeadCell = cell
} }
......
...@@ -130,15 +130,16 @@ class HomeTitleCollectionCell:UICollectionViewCell { ...@@ -130,15 +130,16 @@ class HomeTitleCollectionCell:UICollectionViewCell {
fileLabel?.text = "\(count)" + " Photos " + (model.allFileSize > 0 ? "(\(formatFileSize(model.allFileSize)))" : "(Calculating...)") fileLabel?.text = "\(count)" + " Photos " + (model.allFileSize > 0 ? "(\(formatFileSize(model.allFileSize)))" : "(Calculating...)")
fileLabel?.sizeToFit() fileLabel?.sizeToFit()
collectionView?.reloadData()
} }
func reloadCoverData(_ assets:[AssetModel]){ func reloadCoverData(_ assets:[AssetModel]){
assetsModels.removeAll() assetsModels.removeAll()
assetsModels = assets.compactMap({ model in assetsModels = assets.compactMap({ model in
return ImageCollectionModel.init(asset: model) return ImageCollectionModel.init(asset: model)
}) })
Print("刷新头部封面\(self)",assets.count)
collectionView?.reloadData() collectionView?.reloadData()
} }
...@@ -162,13 +163,10 @@ class HomeTitleCollectionCell:UICollectionViewCell { ...@@ -162,13 +163,10 @@ class HomeTitleCollectionCell:UICollectionViewCell {
fileLabel?.sizeToFit() fileLabel?.sizeToFit()
collectionView?.snp.makeConstraints({ make in collectionView?.snp.makeConstraints({ make in
make.left.equalTo(16)
make.left.equalToSuperview().offset(16) make.bottom.equalTo(-16)
make.bottom.equalToSuperview().offset(-16) make.right.equalTo(-16)
make.width.equalTo(width-32) make.top.equalTo(48)
make.height.equalTo(height-64)
// make.width.equalToSuperview().offset((model?.assets.count ?? 0) > 2 ? -16 : -32)
// make.height.equalToSuperview().offset(-64)
}) })
nextImage?.snp.makeConstraints({ make in nextImage?.snp.makeConstraints({ make in
......
...@@ -74,6 +74,13 @@ class HomeViewModel { ...@@ -74,6 +74,13 @@ class HomeViewModel {
var similarCoverImage:[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
...@@ -134,7 +141,7 @@ class HomeViewModel { ...@@ -134,7 +141,7 @@ class HomeViewModel {
weakSelf.getSimilarOptimizer() weakSelf.getSimilarOptimizer()
weakSelf.getSimilarScreenOptimizer() weakSelf.getSimilarScreenOptimizer()
weakSelf.getSimilarVideoOptimizer() weakSelf.getSimilarVideoOptimizer()
// weakSelf.getGroupDuplicateImages() weakSelf.getGroupDuplicateImages()
} }
} }
...@@ -165,8 +172,10 @@ class HomeViewModel { ...@@ -165,8 +172,10 @@ class HomeViewModel {
weakSelf.homeDataChanged?(0,type.index) weakSelf.homeDataChanged?(0,type.index)
} completionHandler: { totalGroup in } completionHandler: {[weak self] totalGroup in
guard let weakSelf = self else { return }
print("获取相似图片完成",totalGroup.count) print("获取相似图片完成",totalGroup.count)
weakSelf.photoManager.similarModels = totalGroup
} }
} }
...@@ -196,8 +205,10 @@ class HomeViewModel { ...@@ -196,8 +205,10 @@ class HomeViewModel {
weakSelf.homeDataChanged?(1,type.index) weakSelf.homeDataChanged?(1,type.index)
} completionHandler: { totalGroup in } completionHandler: {[weak self] totalGroup in
guard let weakSelf = self else { return }
print("获取相似截图完成",totalGroup.count) print("获取相似截图完成",totalGroup.count)
weakSelf.photoManager.similarScreenShotModels = totalGroup
} }
} }
...@@ -215,7 +226,6 @@ class HomeViewModel { ...@@ -215,7 +226,6 @@ class HomeViewModel {
weakSelf.cardGroup[type.index].assets = currentGorup weakSelf.cardGroup[type.index].assets = currentGorup
weakSelf.cardGroup[type.index].allFileSize = currentSize weakSelf.cardGroup[type.index].allFileSize = currentSize
// weakSelf.totalSize += Int64(currentSize)
if let id = currentGorup.first?.first?.localIdentifier{ if let id = currentGorup.first?.first?.localIdentifier{
if firstId != id{ if firstId != id{
...@@ -225,8 +235,10 @@ class HomeViewModel { ...@@ -225,8 +235,10 @@ class HomeViewModel {
} }
weakSelf.homeDataChanged?(1,type.index) weakSelf.homeDataChanged?(1,type.index)
} completionHandler: { totalGroup in } completionHandler: {[weak self] totalGroup in
print("获取相似视频完成",totalGroup.count) print("获取相似视频完成",totalGroup.count)
guard let weakSelf = self else { return }
weakSelf.photoManager.similarVideoModels = totalGroup
} }
} }
...@@ -239,12 +251,28 @@ class HomeViewModel { ...@@ -239,12 +251,28 @@ class HomeViewModel {
var currentSize:Double = 0 var currentSize:Double = 0
var firstId:String? var firstId:String?
PhotoDuplicateManager.shared.findDuplicateAssets(in: photoManager.photosAssets, mediaType: .photo) {[weak self] group in PhotoDuplicateManager.shared.findDuplicateAssets(in: photoManager.otherAssets, mediaType: .photo) {[weak self] groups in
guard let weakSelf = self else { return }
// let size = groups.map{$0}.reduce(into: 0){$0+$1.assetSize}
weakSelf.headerGroup[type.index].assets = groups
weakSelf.headerGroup[type.index].allFileSize = currentSize
if let id = groups.first?.first?.localIdentifier{
if firstId != id{
firstId = id
weakSelf.dupCoverImage = groups.first ?? []
weakSelf.coverHadChange?()
}
}
weakSelf.homeDataChanged?(0,type.index)
} progressHandler: {[weak self] group in
guard let weakSelf = self else { return } guard let weakSelf = self else { return }
currentGorup.append(group) currentGorup.append(group)
currentSize += group.reduce(0){$0+$1.assetSize} currentSize += group.reduce(0){$0+$1.assetSize}
// weakSelf.totalSize += Int64(currentSize)
weakSelf.headerGroup[type.index].assets = currentGorup weakSelf.headerGroup[type.index].assets = currentGorup
weakSelf.headerGroup[type.index].allFileSize = currentSize weakSelf.headerGroup[type.index].allFileSize = currentSize
...@@ -258,9 +286,10 @@ class HomeViewModel { ...@@ -258,9 +286,10 @@ class HomeViewModel {
} }
weakSelf.homeDataChanged?(0,type.index) weakSelf.homeDataChanged?(0,type.index)
} completionHandler: { totalGroup in } completionHandler: {[weak self] totalGroup in
print("获取重复完成",totalGroup.count) guard let weakSelf = self else { return }
weakSelf.photoManager.duplicateModels = totalGroup
} }
} }
......
//
// MaintaiDetailViewController.swift
// PhoneManager
//
// Created by edy on 2025/5/13.
//
import UIKit
class MaintaiDetailViewController: BaseViewController {
var dataSource:[[AssetModel]] = []
var tempUrl:URL?
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
tableView.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
make.top.equalTo(titleView.snp.bottom)
}
if let asset = PhotoManager.shared.videoAssets.first{
PhotoManager.shared.getVideoURL(localIdentifier: asset.localIdentifier, completion: {[weak self] url in
guard let weakSelf = self else { return }
weakSelf.tempUrl = url
weakSelf.tableView.reloadData()
})
}
}
lazy var tableView:UITableView = {
let tableView = UITableView()
tableView.delegate = self
tableView.dataSource = self
tableView.separatorStyle = .none
tableView.showsVerticalScrollIndicator = false
tableView.register(UINib(nibName: "MaintaiDetailTableViewCell", bundle: nil), forCellReuseIdentifier: "MaintaiDetailTableViewCell")
tableView.register(UINib(nibName: "MaintaiDetailPicCell", bundle: nil), forCellReuseIdentifier: "MaintaiDetailPicCell")
tableView.register(UINib(nibName: "MaintaiDetialVideoCell", bundle: nil), forCellReuseIdentifier: "MaintaiDetialVideoCell")
return tableView
}()
}
extension MaintaiDetailViewController:UITableViewDelegate,UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3 //dataSource.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 360.RW()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0{
let cell = tableView.dequeueReusableCell(withIdentifier: "MaintaiDetailTableViewCell") as! MaintaiDetailTableViewCell
return cell
}
if indexPath.row == 1{
let cell = tableView.dequeueReusableCell(withIdentifier: "MaintaiDetialVideoCell") as! MaintaiDetialVideoCell
cell.configure(with: tempUrl)
return cell
}
let cell = tableView.dequeueReusableCell(withIdentifier: "MaintaiDetailPicCell") as! MaintaiDetailPicCell
return cell
}
}
//
// MaintainViewListController.swift
// PhoneManager
//
// Created by edy on 2025/5/13.
//
import UIKit
class MaintainViewListController: BaseViewController {
var selectStatus:[Int] = [0,0,0,0,0,0,0,0,0,0,0]{
didSet{
if selectStatus.contains(1){
maintaiBottomView.show()
}else{
maintaiBottomView.disMiss()
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(collectionView)
collectionView.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
make.top.equalTo(titleView.snp.bottom)
}
maintaiTipsAlertView.confirmBlock = {[weak self] in
// guard let weakSelf = self else { return }
}
maintaiBottomView.removeMaintaiBlock = {[weak self] in
guard let weakSelf = self else { return }
weakSelf.maintaiTipsAlertView.show()
}
}
lazy var collectionView:UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: (ScreenW-35)/2.0, height: (ScreenW-35)/2.0)
layout.scrollDirection = .vertical
layout.sectionInset = UIEdgeInsets(top: 22, left: 16, bottom: 20, right: 16)
layout.minimumInteritemSpacing = 3
layout.minimumLineSpacing = 10
let collectionView = UICollectionView(frame: CGRect(x: 0, y: 0, width: view.width, height: view.height), collectionViewLayout:layout)
collectionView.delegate = self
collectionView.backgroundColor = .white
collectionView.dataSource = self
collectionView.register(UINib(nibName: "MaintainViewListCell", bundle: nil), forCellWithReuseIdentifier: "MaintainViewListCell")
return collectionView
}()
lazy var maintaiBottomView:MaintaiBottomView = {
let maintaiBottomView = Bundle.main.loadNibNamed("MaintaiBottomView", owner: nil)?.last as! MaintaiBottomView
maintaiBottomView.frame = CGRect(x: 0, y: ScreenH, width: ScreenW, height: 78+kSafeAreaInsets.bottom)
return maintaiBottomView
}()
lazy var maintaiTipsAlertView:MaintaiTipsAlertView = {
let maintaiTipsAlertView = Bundle.main.loadNibNamed("MaintaiTipsAlertView", owner: nil)?.last as! MaintaiTipsAlertView
return maintaiTipsAlertView
}()
}
extension MaintainViewListController:UICollectionViewDataSource,UICollectionViewDelegate{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int{
return selectStatus.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MaintainViewListCell", for: indexPath) as! MaintainViewListCell
cell.selectBlock = {[weak self] in
guard let weakSelf = self else { return }
weakSelf.view.addSubview(weakSelf.maintaiBottomView)
weakSelf.selectStatus[indexPath.row] = weakSelf.selectStatus[indexPath.row] == 1 ? 0 : 1
weakSelf.collectionView.reloadData()
// weakSelf.collectionView.reloadItems(at: [IndexPath(row: indexPath.row, section: indexPath.section)])
}
cell.selectBtn.isSelected = selectStatus[indexPath.row] == 1
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath){
let vc = MaintaiDetailViewController()
navigationController?.pushViewController(vc, animated: true)
}
}
//
// MaintaiBottomView.swift
// PhoneManager
//
// Created by edy on 2025/5/13.
//
import UIKit
class MaintaiBottomView: UIView {
@IBOutlet weak var numberL: UILabel!
var isshow:Bool = false
var removeMaintaiBlock:(() ->Void)?
override func awakeFromNib() {
super.awakeFromNib()
}
@IBAction func noClick(_ sender: Any) {
removeMaintaiBlock?()
disMiss()
}
func show(){
guard isshow == false else{
return
}
isshow = true
self.frame = CGRect(x: self.x, y: self.y, width: self.width, height: self.height)
UIView.animate(withDuration: 0.2) {
self.frame = CGRect(x: self.x, y: self.y - self.height, width: self.width, height: self.height)
}
}
func disMiss(){
guard isshow else{
return
}
isshow = false
UIView.animate(withDuration: 0.2) {
self.frame = CGRect(x: self.x, y: self.y + self.height, width: self.width, height: self.height)
}completion: { _ in
self.removeFromSuperview()
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="CTb-Ac-3zM" customClass="MaintaiBottomView" customModule="PhoneManager" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="420" height="106"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="1" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="neb-y6-gXk">
<rect key="frame" x="13" y="26.333333333333329" width="11" height="27.666666666666671"/>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="24"/>
<color key="textColor" red="0.066666666669999999" green="0.066666666669999999" blue="0.066666666669999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Selected picture" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3B9-bn-apH">
<rect key="frame" x="48" y="33" width="97" height="14.333333333333336"/>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="12"/>
<color key="textColor" red="0.066666666669999999" green="0.066666666669999999" blue="0.066666666669999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="XE9-Mv-kN1">
<rect key="frame" x="230" y="20" width="174" height="40"/>
<color key="backgroundColor" red="0.0" green="0.50980392159999999" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="width" constant="174" id="EG9-xJ-hJZ"/>
<constraint firstAttribute="height" constant="40" id="ddO-RT-eKr"/>
</constraints>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
<state key="normal" title="No retention"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="number" keyPath="Radius">
<real key="value" value="10"/>
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
<connections>
<action selector="noClick:" destination="CTb-Ac-3zM" eventType="touchUpInside" id="kMM-xZ-dTM"/>
</connections>
</button>
</subviews>
<viewLayoutGuide key="safeArea" id="7oF-X4-Rdp"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="7oF-X4-Rdp" firstAttribute="trailing" secondItem="XE9-Mv-kN1" secondAttribute="trailing" constant="16" id="4te-5s-zea"/>
<constraint firstItem="XE9-Mv-kN1" firstAttribute="top" secondItem="CTb-Ac-3zM" secondAttribute="top" constant="20" id="Cog-R5-MKA"/>
<constraint firstItem="neb-y6-gXk" firstAttribute="leading" secondItem="CTb-Ac-3zM" secondAttribute="leading" constant="13" id="NOG-dJ-CwV"/>
<constraint firstItem="XE9-Mv-kN1" firstAttribute="centerY" secondItem="neb-y6-gXk" secondAttribute="centerY" id="fgn-3P-w8I"/>
<constraint firstItem="3B9-bn-apH" firstAttribute="leading" secondItem="neb-y6-gXk" secondAttribute="trailing" constant="24" id="mqv-bV-sLV"/>
<constraint firstItem="3B9-bn-apH" firstAttribute="centerY" secondItem="neb-y6-gXk" secondAttribute="centerY" id="p28-zT-gUD"/>
</constraints>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<connections>
<outlet property="numberL" destination="neb-y6-gXk" id="chO-cs-5Eo"/>
</connections>
<point key="canvasLocation" x="-216.79389312977099" y="-187.32394366197184"/>
</view>
</objects>
<resources>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>
//
// MaintaiDetailImageCell.swift
// PhoneManager
//
// Created by edy on 2025/5/13.
//
import UIKit
class MaintaiDetailImageCell: UICollectionViewCell {
@IBOutlet weak var num: UILabel!
@IBOutlet weak var coverImage: UIImageView!
var selectBtn:UIButton!
var selectChangeBlock:(() ->Void)?
override func awakeFromNib() {
super.awakeFromNib()
setUI()
}
private func setUI(){
selectBtn = UIButton()
selectBtn.setImage(UIImage.init(named: "icon_maintai_unselect_big"), for: .normal)
selectBtn.setImage(UIImage.init(named: "icon_maintai_select_big"), for: .selected)
contentView.addSubview(selectBtn)
contentView.bringSubviewToFront(selectBtn)
selectBtn.addTarget(self, action: #selector(selectChange), for: .touchUpInside)
selectBtn.snp.makeConstraints { make in
make.right.bottom.equalToSuperview()
make.size.equalTo(30)
}
}
override func layoutSubviews() {
super.layoutSubviews()
coverImage.layer.cornerRadius = 12
coverImage.layer.masksToBounds = true
}
@objc func selectChange(){
selectChangeBlock?()
}
}
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="gTV-IL-0wX" customClass="MaintaiDetailImageCell" customModule="PhoneManager" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="196" height="196"/>
<autoresizingMask key="autoresizingMask"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
<rect key="frame" x="0.0" y="0.0" width="196" height="196"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Rectangle_1543788350" translatesAutoresizingMaskIntoConstraints="NO" id="T4a-zL-UKQ">
<rect key="frame" x="0.0" y="0.0" width="196" height="196"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="number" keyPath="Radius">
<real key="value" value="12"/>
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="0" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="TpI-ir-4xJ">
<rect key="frame" x="93" y="87.666666666666671" width="10.333333333333329" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</view>
<viewLayoutGuide key="safeArea" id="ZTg-uK-7eu"/>
<constraints>
<constraint firstItem="TpI-ir-4xJ" firstAttribute="centerX" secondItem="gTV-IL-0wX" secondAttribute="centerX" id="0GX-h1-RaA"/>
<constraint firstAttribute="bottom" secondItem="T4a-zL-UKQ" secondAttribute="bottom" id="B7R-M8-dpU"/>
<constraint firstItem="T4a-zL-UKQ" firstAttribute="leading" secondItem="gTV-IL-0wX" secondAttribute="leading" id="MoV-lR-Hqz"/>
<constraint firstItem="TpI-ir-4xJ" firstAttribute="centerY" secondItem="gTV-IL-0wX" secondAttribute="centerY" id="NdE-xI-xGz"/>
<constraint firstItem="T4a-zL-UKQ" firstAttribute="top" secondItem="gTV-IL-0wX" secondAttribute="top" id="dJs-Pa-MhO"/>
<constraint firstAttribute="trailing" secondItem="T4a-zL-UKQ" secondAttribute="trailing" id="gyj-E3-guF"/>
</constraints>
<size key="customSize" width="196" height="196"/>
<connections>
<outlet property="coverImage" destination="T4a-zL-UKQ" id="jMv-d1-6ah"/>
<outlet property="num" destination="TpI-ir-4xJ" id="Eiw-uk-OeQ"/>
</connections>
<point key="canvasLocation" x="218.32061068702288" y="71.126760563380287"/>
</collectionViewCell>
</objects>
<resources>
<image name="Rectangle_1543788350" width="222" height="299"/>
</resources>
</document>
//
// MaintaiDetailImageSmallCell.swift
// PhoneManager
//
// Created by edy on 2025/5/14.
//
import UIKit
class MaintaiDetailImageSmallCell: UICollectionViewCell {
var selectBtn:UIButton!
var selectChangeBlock:(() ->Void)?
@IBOutlet weak var coverImage: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
setUI()
}
private func setUI(){
selectBtn = UIButton()
selectBtn.setImage(UIImage.init(named: "icon_maintai_unselect_small"), for: .normal)
selectBtn.setImage(UIImage.init(named: "icon_maintai_select_small"), for: .selected)
contentView.addSubview(selectBtn)
contentView.bringSubviewToFront(selectBtn)
selectBtn.addTarget(self, action: #selector(selectChange), for: .touchUpInside)
selectBtn.snp.makeConstraints { make in
make.right.bottom.equalTo(0)
make.size.equalTo(20)
}
}
override func layoutSubviews() {
super.layoutSubviews()
coverImage.layer.cornerRadius = 12
coverImage.layer.masksToBounds = true
}
@objc func selectChange(){
selectChangeBlock?()
}
}
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="gTV-IL-0wX" customClass="MaintaiDetailImageSmallCell" customModule="PhoneManager" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="190" height="188"/>
<autoresizingMask key="autoresizingMask"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
<rect key="frame" x="0.0" y="0.0" width="190" height="188"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Rectangle_1543788350" translatesAutoresizingMaskIntoConstraints="NO" id="Aef-Vw-tDB">
<rect key="frame" x="0.0" y="0.0" width="190" height="188"/>
</imageView>
</subviews>
</view>
<viewLayoutGuide key="safeArea" id="ZTg-uK-7eu"/>
<constraints>
<constraint firstItem="Aef-Vw-tDB" firstAttribute="top" secondItem="gTV-IL-0wX" secondAttribute="top" id="8Au-1a-fnf"/>
<constraint firstAttribute="trailing" secondItem="Aef-Vw-tDB" secondAttribute="trailing" id="b7v-jZ-vx9"/>
<constraint firstAttribute="bottom" secondItem="Aef-Vw-tDB" secondAttribute="bottom" id="ehN-dk-15A"/>
<constraint firstItem="Aef-Vw-tDB" firstAttribute="leading" secondItem="gTV-IL-0wX" secondAttribute="leading" id="oRS-xG-d6k"/>
</constraints>
<size key="customSize" width="190" height="188"/>
<connections>
<outlet property="coverImage" destination="Aef-Vw-tDB" id="bXL-jg-zs7"/>
</connections>
<point key="canvasLocation" x="201.52671755725191" y="68.309859154929583"/>
</collectionViewCell>
</objects>
<resources>
<image name="Rectangle_1543788350" width="222" height="299"/>
</resources>
</document>
//
// MaintaiDetailPicCell.swift
// PhoneManager
//
// Created by edy on 2025/5/14.
//
import UIKit
class MaintaiDetailPicCell: UITableViewCell {
var selectBtn:UIButton!
var selectChangeBlock:(() ->Void)?
override func awakeFromNib() {
super.awakeFromNib()
setUI()
}
private func setUI(){
selectBtn = UIButton()
selectBtn.setImage(UIImage.init(named: "icon_maintai_unselect_big"), for: .normal)
selectBtn.setImage(UIImage.init(named: "icon_maintai_select_big"), for: .selected)
contentView.addSubview(selectBtn)
contentView.bringSubviewToFront(selectBtn)
selectBtn.addTarget(self, action: #selector(selectChange), for: .touchUpInside)
selectBtn.snp.makeConstraints { make in
make.right.bottom.equalTo(0)
make.size.equalTo(30)
}
}
@objc func selectChange(){
selectChangeBlock?()
}
}
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableViewCell contentMode="scaleToFill" selectionStyle="none" indentationWidth="10" rowHeight="498" id="KGk-i7-Jjw" customClass="MaintaiDetailPicCell" customModule="PhoneManager" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="582" height="498"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<rect key="frame" x="0.0" y="0.0" width="582" height="498"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Rectangle_1543788350" translatesAutoresizingMaskIntoConstraints="NO" id="P9x-dp-1Do">
<rect key="frame" x="0.0" y="10" width="582" height="478"/>
</imageView>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="M1a-AV-qRW">
<rect key="frame" x="522" y="468" width="30" height="30"/>
<constraints>
<constraint firstAttribute="width" constant="30" id="A2L-RK-3Li"/>
<constraint firstAttribute="height" constant="30" id="NVo-5n-WbH"/>
</constraints>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
<state key="normal" image="icon_maintai_unselect_big"/>
<state key="selected" image="icon_maintai_select_big"/>
</button>
</subviews>
<constraints>
<constraint firstItem="P9x-dp-1Do" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" id="KvT-aU-rXa"/>
<constraint firstItem="P9x-dp-1Do" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" constant="10" id="Qt9-R6-tFP"/>
<constraint firstAttribute="trailing" secondItem="M1a-AV-qRW" secondAttribute="trailing" constant="30" id="Uu9-Zr-OBB"/>
<constraint firstAttribute="bottom" secondItem="M1a-AV-qRW" secondAttribute="bottom" id="W3f-Hw-yYB"/>
<constraint firstAttribute="trailing" secondItem="P9x-dp-1Do" secondAttribute="trailing" id="bHi-TZ-Q3w"/>
<constraint firstAttribute="bottom" secondItem="P9x-dp-1Do" secondAttribute="bottom" constant="10" id="oJ9-2y-hx9"/>
</constraints>
</tableViewCellContentView>
<viewLayoutGuide key="safeArea" id="njF-e1-oar"/>
<point key="canvasLocation" x="479.38931297709922" y="115.49295774647888"/>
</tableViewCell>
</objects>
<resources>
<image name="Rectangle_1543788350" width="222" height="299"/>
<image name="icon_maintai_select_big" width="18" height="18"/>
<image name="icon_maintai_unselect_big" width="18" height="18"/>
</resources>
</document>
//
// MaintaiDetailTableViewCell.swift
// PhoneManager
//
// Created by edy on 2025/5/13.
//
import UIKit
class MaintaiDetailTableViewCell: UITableViewCell {
@IBOutlet weak var mainCollectionView: UICollectionView!
@IBOutlet weak var pageCollectionView: UICollectionView!
enum draggViewEnum{
case big,small,null
}
let smallW:CGFloat = 54.RW()
let bigW:CGFloat = ScreenW-44
var draggView:draggViewEnum = .null
override func awakeFromNib() {
super.awakeFromNib()
configMain()
configChild()
}
func configMain(){
let layout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: bigW, height: bigW)
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
layout.scrollDirection = .horizontal
mainCollectionView.setCollectionViewLayout(layout, animated: false)
mainCollectionView.dataSource = self
mainCollectionView.delegate = self
mainCollectionView.isPagingEnabled = true
mainCollectionView.register(UINib(nibName: "MaintaiDetailImageCell", bundle: nil), forCellWithReuseIdentifier: "MaintaiDetailImageCell")
}
func configChild(){
let layout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: smallW, height: smallW)
layout.minimumLineSpacing = 8
layout.minimumInteritemSpacing = 8
layout.sectionInset = UIEdgeInsets(top: 0, left: 12, bottom: 0, right: 12)
layout.scrollDirection = .horizontal
pageCollectionView.setCollectionViewLayout(layout, animated: false)
pageCollectionView.dataSource = self
pageCollectionView.delegate = self
let inset = ScreenW - smallW
pageCollectionView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: inset)
pageCollectionView.backgroundColor = UIColor(hex: "#E2EEFF")
pageCollectionView.layer.cornerRadius = 8
pageCollectionView.register(UINib(nibName: "MaintaiDetailImageCell", bundle: nil), forCellWithReuseIdentifier: "MaintaiDetailImageCell")
pageCollectionView.register(UINib(nibName: "MaintaiDetailImageSmallCell", bundle: nil), forCellWithReuseIdentifier: "MaintaiDetailImageSmallCell")
}
}
extension MaintaiDetailTableViewCell:UICollectionViewDelegate,UICollectionViewDataSource{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if collectionView == mainCollectionView{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MaintaiDetailImageCell", for: indexPath) as! MaintaiDetailImageCell
cell.num.text = "\(indexPath.row)"
return cell
}
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MaintaiDetailImageSmallCell", for: indexPath) as! MaintaiDetailImageSmallCell
return cell
}
}
extension MaintaiDetailTableViewCell:UIScrollViewDelegate{
func scrollViewDidScroll(_ scrollView: UIScrollView) {
Print("滑动距离",scrollView.contentOffset.x)
if scrollView == mainCollectionView,draggView == .big{
// 拖动主图结束
Print("主图滑动距离",scrollView.contentOffset.x)
let index = Int(scrollView.contentOffset.x/bigW)
Print("小图滑动到下标\(index)")
UIView.animate(withDuration: 0.3) {
self.pageCollectionView.contentOffset = CGPoint(x: index * Int(self.smallW+8)+4, y: 0)
}
}
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
if scrollView == mainCollectionView{
print("----开始拖拽----大图")
draggView = .big
}else{
print("----开始拖拽----小图")
draggView = .small
}
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
print("----结束拖拽----")
if !decelerate{
if scrollView == pageCollectionView,draggView == .small{
setPageOffetX(offsetX: scrollView.contentOffset.x)
}
}
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
if !scrollView.isDecelerating,!scrollView.isDragging,!scrollView.isTracking {
if scrollView == pageCollectionView,draggView == .small{
setPageOffetX(offsetX: scrollView.contentOffset.x)
}
}
}
func setPageOffetX(offsetX:CGFloat){
print("----结束滑动----小图")
Print("小图滑动距离",offsetX)
let index = Int((offsetX/(smallW+12)).rounded()) //Int(offsetX/(smallW+12).rounded()) //rounded(offsetX/smallW)
Print("大图滑动到下标\(index)")
UIView.animate(withDuration: 0.3) {
self.pageCollectionView.contentOffset = CGPoint(x: index * Int(self.smallW+8)+4, y: 0)
}
self.mainCollectionView.contentOffset = CGPoint(x: index * Int(self.bigW), y: 0)
draggView = .null
}
}
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableViewCell contentMode="scaleToFill" selectionStyle="none" indentationWidth="10" rowHeight="334" id="KGk-i7-Jjw" customClass="MaintaiDetailTableViewCell" customModule="PhoneManager" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="391" height="334"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<rect key="frame" x="0.0" y="0.0" width="391" height="334"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" dataMode="none" translatesAutoresizingMaskIntoConstraints="NO" id="9JA-CH-xnv">
<rect key="frame" x="16" y="239" width="359" height="74"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstAttribute="height" constant="74" id="Yuh-X4-ufy" customClass="ScreenWidthRatioConstraint" customModule="PhoneManager" customModuleProvider="target"/>
</constraints>
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="10" minimumInteritemSpacing="10" id="Uqc-9e-Xng">
<size key="itemSize" width="128" height="128"/>
<size key="headerReferenceSize" width="0.0" height="0.0"/>
<size key="footerReferenceSize" width="0.0" height="0.0"/>
<inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
</collectionViewFlowLayout>
</collectionView>
<collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" dataMode="none" translatesAutoresizingMaskIntoConstraints="NO" id="4r4-C6-foP">
<rect key="frame" x="22" y="10" width="347" height="219"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="10" minimumInteritemSpacing="10" id="57U-4z-GRH">
<size key="itemSize" width="128" height="128"/>
<size key="headerReferenceSize" width="0.0" height="0.0"/>
<size key="footerReferenceSize" width="0.0" height="0.0"/>
<inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
</collectionViewFlowLayout>
</collectionView>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="9JA-CH-xnv" secondAttribute="trailing" constant="16" id="7Fd-DM-xAF"/>
<constraint firstItem="9JA-CH-xnv" firstAttribute="top" secondItem="4r4-C6-foP" secondAttribute="bottom" constant="10" id="Fd7-nF-vTF"/>
<constraint firstAttribute="trailing" secondItem="4r4-C6-foP" secondAttribute="trailing" constant="22" id="Icw-bE-RYb"/>
<constraint firstItem="4r4-C6-foP" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" constant="22" id="Q2D-GA-Ygc"/>
<constraint firstItem="9JA-CH-xnv" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leading" constant="16" id="SJa-88-0xA"/>
<constraint firstAttribute="bottomMargin" secondItem="9JA-CH-xnv" secondAttribute="bottom" constant="10" id="fP5-UX-RUN"/>
<constraint firstItem="4r4-C6-foP" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" constant="10" id="lsa-B1-Lu7"/>
</constraints>
</tableViewCellContentView>
<viewLayoutGuide key="safeArea" id="njF-e1-oar"/>
<connections>
<outlet property="mainCollectionView" destination="4r4-C6-foP" id="eWl-pE-bo0"/>
<outlet property="pageCollectionView" destination="9JA-CH-xnv" id="FaY-dU-DRN"/>
</connections>
<point key="canvasLocation" x="159.54198473282443" y="121.83098591549296"/>
</tableViewCell>
</objects>
<resources>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>
//
// MaintaiDetialVideoCell.swift
// PhoneManager
//
// Created by edy on 2025/5/14.
//
import UIKit
import AVKit
class MaintaiDetialVideoCell: UITableViewCell {
private var player: AVPlayer?
private var playerLayer: AVPlayerLayer?
var selectBtn:UIButton!
var selectChangeBlock:(() ->Void)?
override func awakeFromNib() {
super.awakeFromNib()
setupPlayer()
setUI()
}
private func setUI(){
selectBtn = UIButton()
selectBtn.setImage(UIImage.init(named: "icon_maintai_unselect_big"), for: .normal)
selectBtn.setImage(UIImage.init(named: "icon_maintai_select_big"), for: .selected)
contentView.addSubview(selectBtn)
contentView.bringSubviewToFront(selectBtn)
selectBtn.addTarget(self, action: #selector(selectChange), for: .touchUpInside)
selectBtn.snp.makeConstraints { make in
make.right.bottom.equalTo(0)
make.size.equalTo(30)
}
}
private func setupPlayer() {
// 创建播放器层
playerLayer = AVPlayerLayer()
playerLayer?.videoGravity = .resizeAspect
contentView.layer.addSublayer(playerLayer!)
}
override func layoutSubviews() {
super.layoutSubviews()
// 设置播放器层的frame
playerLayer?.frame = CGRect(x: 0, y: 10, width: contentView.width, height: contentView.height-20)
}
func configure(with videoURL: URL?) {
guard let videoURL = videoURL else{
player?.pause()
player = nil
return
}
// 创建播放器项
let playerItem = AVPlayerItem(url: videoURL)
// 创建播放器
player = AVPlayer(playerItem: playerItem)
playerLayer?.player = player
// 设置静音
player?.volume = 0
// 播放视频
player?.play()
// 添加播放完成的观察者
NotificationCenter.default.addObserver(self,
selector: #selector(playerDidFinishPlaying),
name: .AVPlayerItemDidPlayToEndTime,
object: playerItem)
}
@objc private func playerDidFinishPlaying() {
// 播放结束后不做任何操作,因为只需要播放一次
player?.pause()
player?.seek(to: .zero)
}
override func prepareForReuse() {
super.prepareForReuse()
// 清理播放器
player?.pause()
player = nil
NotificationCenter.default.removeObserver(self)
}
@objc func selectChange(){
selectChangeBlock?()
}
}
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableViewCell contentMode="scaleToFill" selectionStyle="none" indentationWidth="10" rowHeight="369" id="KGk-i7-Jjw" customClass="MaintaiDetialVideoCell" customModule="PhoneManager" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="341" height="369"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<rect key="frame" x="0.0" y="0.0" width="341" height="369"/>
<autoresizingMask key="autoresizingMask"/>
</tableViewCellContentView>
<viewLayoutGuide key="safeArea" id="njF-e1-oar"/>
<point key="canvasLocation" x="12.977099236641221" y="134.1549295774648"/>
</tableViewCell>
</objects>
</document>
//
// MaintaiTipsAlertView.swift
// PhoneManager
//
// Created by edy on 2025/5/13.
//
import UIKit
class MaintaiTipsAlertView: UIView {
var confirmBlock:(() ->Void)?
override func awakeFromNib() {
super.awakeFromNib()
frame = CGRectMake(0, 0, ScreenW, ScreenH)
}
@IBAction func confirmClick(_ sender: Any) {
confirmBlock?()
}
@IBAction func cancelClick(_ sender: Any) {
disMiss()
}
func disMiss(){
self.removeFromSuperview()
}
func show(){
KEYWINDOW()?.addSubview(self)
}
}
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="p11-ka-d5j" customClass="MaintaiTipsAlertView" customModule="PhoneManager" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="SyR-iC-5WC">
<rect key="frame" x="16" y="301" width="361" height="250"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="ic_list_setting" translatesAutoresizingMaskIntoConstraints="NO" id="aer-UI-Hdf">
<rect key="frame" x="166.66666666666666" y="22" width="28" height="28"/>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Stop retaining all photos?" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ITE-9H-tkI">
<rect key="frame" x="84.333333333333329" y="74" width="192.66666666666669" height="19.333333333333329"/>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="16"/>
<color key="textColor" red="0.066666666669999999" green="0.066666666669999999" blue="0.066666666669999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="The retained list will be cleared and all photos will be displayed in the next scan." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="35j-Q4-9L6">
<rect key="frame" x="26" y="97.333333333333314" width="309" height="28.666666666666671"/>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="12"/>
<color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="FLo-zT-dpO">
<rect key="frame" x="73" y="150" width="215" height="43"/>
<color key="backgroundColor" red="0.0" green="0.50980392159999999" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="43" id="1O1-hE-Gz9"/>
<constraint firstAttribute="width" constant="215" id="gfq-Nf-3kk"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="14"/>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
<state key="normal" title="Confirm"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="number" keyPath="Radius">
<real key="value" value="11"/>
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
<connections>
<action selector="confirmClick:" destination="p11-ka-d5j" eventType="touchUpInside" id="STq-N7-Gj3"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="68F-oy-zob">
<rect key="frame" x="157" y="205" width="47" height="29"/>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="14"/>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
<state key="normal" title="Cancel">
<color key="titleColor" red="0.59999999999999998" green="0.59999999999999998" blue="0.59999999999999998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<connections>
<action selector="cancelClick:" destination="p11-ka-d5j" eventType="touchUpInside" id="wbe-AG-DS9"/>
</connections>
</button>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<constraints>
<constraint firstItem="ITE-9H-tkI" firstAttribute="top" secondItem="aer-UI-Hdf" secondAttribute="bottom" constant="24" id="4n6-Nx-JEk"/>
<constraint firstItem="FLo-zT-dpO" firstAttribute="centerX" secondItem="35j-Q4-9L6" secondAttribute="centerX" id="Btb-F7-pNk"/>
<constraint firstItem="FLo-zT-dpO" firstAttribute="top" secondItem="35j-Q4-9L6" secondAttribute="bottom" constant="24" id="D5x-HU-13q"/>
<constraint firstAttribute="height" constant="250" id="ROB-W2-QK9"/>
<constraint firstAttribute="trailing" secondItem="35j-Q4-9L6" secondAttribute="trailing" constant="26" id="XKK-A0-h6s"/>
<constraint firstItem="68F-oy-zob" firstAttribute="centerX" secondItem="FLo-zT-dpO" secondAttribute="centerX" id="dfa-Sn-bR1"/>
<constraint firstItem="aer-UI-Hdf" firstAttribute="centerX" secondItem="SyR-iC-5WC" secondAttribute="centerX" id="gaW-p7-bEw"/>
<constraint firstItem="68F-oy-zob" firstAttribute="top" secondItem="FLo-zT-dpO" secondAttribute="bottom" constant="12" id="iwJ-qh-Bcp"/>
<constraint firstItem="ITE-9H-tkI" firstAttribute="centerX" secondItem="aer-UI-Hdf" secondAttribute="centerX" id="pPT-En-OiR"/>
<constraint firstItem="aer-UI-Hdf" firstAttribute="top" secondItem="SyR-iC-5WC" secondAttribute="top" constant="22" id="qQG-VB-7uJ"/>
<constraint firstItem="35j-Q4-9L6" firstAttribute="top" secondItem="ITE-9H-tkI" secondAttribute="bottom" constant="4" id="xdP-7n-d3I"/>
<constraint firstItem="35j-Q4-9L6" firstAttribute="leading" secondItem="SyR-iC-5WC" secondAttribute="leading" constant="26" id="yW5-v3-r3e"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="number" keyPath="Radius">
<real key="value" value="20"/>
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
</view>
</subviews>
<viewLayoutGuide key="safeArea" id="Sg0-yf-Vq3"/>
<color key="backgroundColor" red="0.066666666666666666" green="0.066666666666666666" blue="0.066666666666666666" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="Sg0-yf-Vq3" firstAttribute="trailing" secondItem="SyR-iC-5WC" secondAttribute="trailing" constant="16" id="3rm-dW-0Hp"/>
<constraint firstItem="SyR-iC-5WC" firstAttribute="centerY" secondItem="p11-ka-d5j" secondAttribute="centerY" id="Pm6-OB-Ccw"/>
<constraint firstItem="SyR-iC-5WC" firstAttribute="leading" secondItem="Sg0-yf-Vq3" secondAttribute="leading" constant="16" id="RLd-be-6qR"/>
</constraints>
<point key="canvasLocation" x="273" y="-259"/>
</view>
</objects>
<resources>
<image name="ic_list_setting" width="28" height="28"/>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</systemColor>
</resources>
</document>
//
// MaintainViewListCell.swift
// PhoneManager
//
// Created by edy on 2025/5/13.
//
import UIKit
class MaintainViewListCell: UICollectionViewCell {
@IBOutlet weak var converImage: UIImageView!
@IBOutlet weak var numberL: UILabel!
@IBOutlet weak var selectBtn: UIButton!
var selectBlock:(() ->Void)?
override func awakeFromNib() {
super.awakeFromNib()
}
override func layoutSubviews() {
super.layoutSubviews()
converImage.cornerCut(radius: 12, corner: .allCorners)
numberL.cornerCut(radius: 12, corner: [.topLeft,.bottomRight])
}
@IBAction func selectClick(_ sender: Any) {
selectBlock?()
}
}
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="gTV-IL-0wX" customClass="MaintainViewListCell" customModule="PhoneManager" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="214" height="221"/>
<autoresizingMask key="autoresizingMask"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
<rect key="frame" x="0.0" y="0.0" width="214" height="221"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Rectangle_1543788350" translatesAutoresizingMaskIntoConstraints="NO" id="i7u-VT-vKs">
<rect key="frame" x="0.0" y="0.0" width="214" height="221"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="1 Videos" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="iqx-1Y-Adf">
<rect key="frame" x="0.0" y="0.0" width="71" height="21"/>
<color key="backgroundColor" red="0.0" green="0.50980392159999999" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" constant="21" id="9hE-nM-Q7K"/>
<constraint firstAttribute="width" constant="71" id="aXj-hv-npu"/>
</constraints>
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="12"/>
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Q1W-PS-1dc">
<rect key="frame" x="184" y="191" width="30" height="30"/>
<constraints>
<constraint firstAttribute="height" constant="30" id="Bco-o8-izQ"/>
<constraint firstAttribute="width" constant="30" id="ZiH-wz-731"/>
</constraints>
<inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
<state key="normal" image="icon_maintai_unselect_big"/>
<state key="selected" image="icon_maintai_select_big"/>
<connections>
<action selector="selectClick:" destination="gTV-IL-0wX" eventType="touchUpInside" id="5bZ-pE-O7n"/>
</connections>
</button>
</subviews>
</view>
<viewLayoutGuide key="safeArea" id="ZTg-uK-7eu"/>
<constraints>
<constraint firstItem="iqx-1Y-Adf" firstAttribute="leading" secondItem="gTV-IL-0wX" secondAttribute="leading" id="09C-Be-9Up"/>
<constraint firstAttribute="bottom" secondItem="i7u-VT-vKs" secondAttribute="bottom" id="6Ji-XJ-F0x"/>
<constraint firstItem="i7u-VT-vKs" firstAttribute="leading" secondItem="gTV-IL-0wX" secondAttribute="leading" id="DBG-CS-ov5"/>
<constraint firstItem="iqx-1Y-Adf" firstAttribute="top" secondItem="gTV-IL-0wX" secondAttribute="top" id="Hoa-Mf-i04"/>
<constraint firstAttribute="trailing" secondItem="i7u-VT-vKs" secondAttribute="trailing" id="QFW-Tr-p6r"/>
<constraint firstAttribute="trailing" secondItem="Q1W-PS-1dc" secondAttribute="trailing" id="Tam-gc-4zu"/>
<constraint firstItem="i7u-VT-vKs" firstAttribute="top" secondItem="gTV-IL-0wX" secondAttribute="top" id="Zru-ne-uig"/>
<constraint firstAttribute="bottom" secondItem="Q1W-PS-1dc" secondAttribute="bottom" id="fdb-bz-yoQ"/>
</constraints>
<size key="customSize" width="214" height="221"/>
<connections>
<outlet property="converImage" destination="i7u-VT-vKs" id="8ga-Gd-AsW"/>
<outlet property="numberL" destination="iqx-1Y-Adf" id="tnd-yu-2VT"/>
<outlet property="selectBtn" destination="Q1W-PS-1dc" id="g51-G2-RWz"/>
</connections>
<point key="canvasLocation" x="232.06106870229007" y="79.929577464788736"/>
</collectionViewCell>
</objects>
<resources>
<image name="Rectangle_1543788350" width="222" height="299"/>
<image name="icon_maintai_select_big" width="18" height="18"/>
<image name="icon_maintai_unselect_big" width="18" height="18"/>
</resources>
</document>
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
// //
import Foundation import Foundation
import UIKit
class PayCompletedViewController : BaseViewController{ class PayCompletedViewController : BaseViewController{
......
...@@ -62,11 +62,13 @@ func getSettingViewInfo() -> [SettingModel] { ...@@ -62,11 +62,13 @@ func getSettingViewInfo() -> [SettingModel] {
[RowInfoModel(imageName: "ic_widgets_setting",title: "Widgets")]), [RowInfoModel(imageName: "ic_widgets_setting",title: "Widgets")]),
SettingModel(sectionTitle: "OTHERS",rowInfo: SettingModel(sectionTitle: "OTHERS",rowInfo:
[RowInfoModel(imageName: "ic_list_setting",title: "Keep List")]), [RowInfoModel(imageName: "ic_list_setting",title: "Keep List")]),
// SettingModel(sectionTitle: "SECRET SPACE",rowInfo: [ // SettingModel(sectionTitle: "SECRET SPACE",rowInfo: [
// RowInfoModel(imageName: "ic_pin_setting",title: "Use PIN")]), // RowInfoModel(imageName: "ic_pin_setting",title: "Use PIN")]),
SettingModel(sectionTitle: "STAY IN TOUCH",rowInfo: SettingModel(sectionTitle: "STAY IN TOUCH",rowInfo:
[RowInfoModel(imageName: "ic_rate_setting",title: "Rate App"), [RowInfoModel(imageName: "ic_rate_setting",title: "Rate App"),
RowInfoModel(imageName: "ic_share_setting",title: "Share App"), RowInfoModel(imageName: "ic_share_setting",title: "Share App"),
RowInfoModel(imageName: "ic_share_setting", title: "Restore Purchase")
/*RowInfoModel(imageName: "ic_ins_setting",title: "Follow on Instagram")*/]), /*RowInfoModel(imageName: "ic_ins_setting",title: "Follow on Instagram")*/]),
SettingModel(sectionTitle: "STAY IN TOUCH",rowInfo: SettingModel(sectionTitle: "STAY IN TOUCH",rowInfo:
[RowInfoModel(imageName: "ic_email_setting",title: "Email support") ])] [RowInfoModel(imageName: "ic_email_setting",title: "Email support") ])]
......
...@@ -149,6 +149,16 @@ class SettingViewController : BaseViewController , UITableViewDelegate, UITableV ...@@ -149,6 +149,16 @@ class SettingViewController : BaseViewController , UITableViewDelegate, UITableV
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let title = modelData?[indexPath.section].rowInfo[indexPath.row].title else{
return
}
if title == "Restore Purchase"{
let vc = MaintainViewListController()
navigationController?.pushViewController(vc, animated: true)
return
}
switch indexPath.section { switch indexPath.section {
case 0: case 0:
break break
......
...@@ -25,7 +25,6 @@ enum TrashTypeEnum{ ...@@ -25,7 +25,6 @@ enum TrashTypeEnum{
} }
struct TrashPageScrollModel{ struct TrashPageScrollModel{
var offset:CGFloat = 0 var offset:CGFloat = 0
var page:Int = 1 var page:Int = 1
......
...@@ -124,7 +124,6 @@ class NotificationManager { ...@@ -124,7 +124,6 @@ class NotificationManager {
} }
Print("缓存数据获取完成,注册通知") Print("缓存数据获取完成,注册通知")
self.createNotifications() self.createNotifications()
}) })
} }
......
//
// OpenCVWrapper.h
// PhoneManager
//
// Created by edy on 2025/3/25.
//
#ifdef __OBJC__
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface OpenCVWrapper : NSObject
/// 是否相似
+ (BOOL)areImagesSimilar:(UIImage *)image1 withImage2:(UIImage *)image2 threshold:(double)threshold;
/// 获取相似度
+ (double)compareImageSimilarity:(UIImage *)image1 withImage2:(UIImage *)image2;
@end
#endif
//
// OpenCVWrapper.m
// PhoneManager
//
// Created by edy on 2025/3/25.
//
#import "OpenCVWrapper.h"
#import <opencv2/core.hpp>
#import <opencv2/imgproc.hpp>
#import <opencv2/features2d.hpp>
@implementation OpenCVWrapper
+ (cv::Mat)cvMatFromUIImage:(UIImage *)image {
CGColorSpaceRef colorSpace = CGImageGetColorSpace(image.CGImage);
CGFloat cols = image.size.width;
CGFloat rows = image.size.height;
cv::Mat cvMat(rows, cols, CV_8UC4); // 8 bits per component, 4 channels (RGBA)
CGContextRef contextRef = CGBitmapContextCreate(cvMat.data, // Pointer to data
cols, // Width of bitmap
rows, // Height of bitmap
8, // Bits per component
cvMat.step[0], // Bytes per row
colorSpace, // Colorspace
kCGImageAlphaNoneSkipLast |
kCGBitmapByteOrderDefault); // Bitmap info flags
CGContextDrawImage(contextRef, CGRectMake(0, 0, cols, rows), image.CGImage);
CGContextRelease(contextRef);
return cvMat;
}
+ (double)compareImageSimilarity:(UIImage *)image1 withImage2:(UIImage *)image2 {
@try {
// 检查输入
if (!image1 || !image2) {
return 0.0;
}
// 转换为 OpenCV 格式
cv::Mat mat1 = [self cvMatFromUIImage:image1];
cv::Mat mat2 = [self cvMatFromUIImage:image2];
// 检查矩阵是否为空
if (mat1.empty() || mat2.empty()) {
return 0.0;
}
// 转换为灰度图
cv::Mat gray1, gray2;
cv::cvtColor(mat1, gray1, cv::COLOR_RGBA2GRAY);
cv::cvtColor(mat2, gray2, cv::COLOR_RGBA2GRAY);
// 调整大小
cv::Size size(256, 256);
cv::resize(gray1, gray1, size);
cv::resize(gray2, gray2, size);
// 计算直方图
cv::Mat hist1, hist2;
int channels[] = {0};
int histSize[] = {256};
float range[] = {0, 256};
const float* ranges[] = {range};
cv::calcHist(&gray1, 1, channels, cv::Mat(), hist1, 1, histSize, ranges);
cv::calcHist(&gray2, 1, channels, cv::Mat(), hist2, 1, histSize, ranges);
// 归一化直方图
cv::normalize(hist1, hist1, 0, 1, cv::NORM_MINMAX);
cv::normalize(hist2, hist2, 0, 1, cv::NORM_MINMAX);
// 计算相似度
double similarity = cv::compareHist(hist1, hist2, cv::HISTCMP_CORREL);
// 确保返回值在 0-1 之间
return std::max(0.0, std::min(1.0, similarity));
}
@catch (NSException *exception) {
NSLog(@"Error comparing images: %@", exception);
return 0.0;
}
}
+ (BOOL)areImagesSimilar:(UIImage *)image1 withImage2:(UIImage *)image2 threshold:(double)threshold {
@try {
if (!image1 || !image2) {
return NO;
}
double similarity = [self compareImageSimilarity:image1 withImage2:image2];
return similarity >= threshold;
}
@catch (NSException *exception) {
NSLog(@"Error comparing images: %@", exception);
return NO;
}
}
@end
...@@ -2,5 +2,6 @@ ...@@ -2,5 +2,6 @@
// Use this file to import your target's public headers that you would like to expose to Swift. // Use this file to import your target's public headers that you would like to expose to Swift.
// //
#import "OpenCVWrapper.h"
#import "NSString+Ex.h" #import "NSString+Ex.h"
#import <UIKit/UIKit.h>
...@@ -9,6 +9,7 @@ import UIKit ...@@ -9,6 +9,7 @@ import UIKit
import GoogleSignIn import GoogleSignIn
import GoogleAPIClientForREST import GoogleAPIClientForREST
class PMEmailManager: NSObject { class PMEmailManager: NSObject {
static let cloud_id = "797378266354-no81qsffrlpj6p6i1umh981ne2mgtiua.apps.googleusercontent.com" static let cloud_id = "797378266354-no81qsffrlpj6p6i1umh981ne2mgtiua.apps.googleusercontent.com"
......
# Uncomment the next line to define a global platform for your project # Uncomment the next line to define a global platform for your project
platform :ios, '14.0' platform :ios, '14.0'
#source 'https://mirrors.tuna.tsinghua.edu.cn/git/CocoaPods/Specs.git' # source 'https://mirrors.tuna.tsinghua.edu.cn/git/CocoaPods/Specs.git'
target 'PhoneManager' do target 'PhoneManager' do
...@@ -13,17 +13,18 @@ target 'PhoneManager' do ...@@ -13,17 +13,18 @@ target 'PhoneManager' do
pod 'lottie-ios' pod 'lottie-ios'
pod 'SnapKit' pod 'SnapKit'
pod 'HXPhotoPicker' pod 'HXPhotoPicker'
pod 'OpenCV' # pod 'OpenCV'
pod 'SVProgressHUD' pod 'SVProgressHUD'
pod 'Google-Mobile-Ads-SDK' pod 'Google-Mobile-Ads-SDK'
pod 'GoogleSignIn' # pod 'GoogleSignIn', '8.0.0'
pod 'GoogleAPIClientForREST/Gmail' pod 'GoogleAPIClientForREST/Gmail'
# pod 'GoogleAPIClientForREST', '4.1.0', :subspecs => ['Gmail']
post_install do |installer| post_install do |installer|
installer.pods_project.targets.each do |target| installer.pods_project.targets.each do |target|
target.build_configurations.each do |config| target.build_configurations.each do |config|
config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'arm64' # Exclude arm64 for simulator config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = '' # Exclude arm64 for simulator
end end
end end
end end
......
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