Commit 56bb9c9b authored by CZ1004's avatar CZ1004

【优化】优化视频压缩代码,添加重复联系人UI

parent 28ae9c45
...@@ -9,11 +9,22 @@ import Foundation ...@@ -9,11 +9,22 @@ import Foundation
import GoogleMobileAds import GoogleMobileAds
import UserMessagingPlatform import UserMessagingPlatform
enum AdvertisementType {
case rewardedInterstitialType
case interstitialType
}
class AdvManager : NSObject,FullScreenContentDelegate { class AdvManager : NSObject,FullScreenContentDelegate {
// fixme:上线前更改
private static let REWARDED_INTERSTITIALAD_KEY : String = "ca-app-pub-3480207748580737/4276457203" // private static let REWARDED_INTERSTITIALAD_KEY : String = "ca-app-pub-3480207748580737/4276457203"
private static let INTERSTITIALAD_KEY : String = "ca-app-pub-3480207748580737/5836950888" // private static let INTERSTITIALAD_KEY : String = "ca-app-pub-3480207748580737/5836950888"
// info.plist: ca-app-pub-3480207748580737~4236262472
private static let REWARDED_INTERSTITIALAD_KEY : String = "ca-app-pub-3940256099942544/6978759866"
private static let INTERSTITIALAD_KEY : String = "ca-app-pub-3940256099942544/4411468910"
static let shared : AdvManager = AdvManager() static let shared : AdvManager = AdvManager()
...@@ -26,6 +37,8 @@ class AdvManager : NSObject,FullScreenContentDelegate { ...@@ -26,6 +37,8 @@ class AdvManager : NSObject,FullScreenContentDelegate {
// 插页广告 // 插页广告
var interstitial: InterstitialAd? var interstitial: InterstitialAd?
var currentAdvType : AdvertisementType = .rewardedInterstitialType
var currentTimes : Int = 3 var currentTimes : Int = 3
/// 默认每日免费删除次数 /// 默认每日免费删除次数
...@@ -137,9 +150,11 @@ class AdvManager : NSObject,FullScreenContentDelegate { ...@@ -137,9 +150,11 @@ class AdvManager : NSObject,FullScreenContentDelegate {
/// - Parameter completed: 准备完成后回调 /// - Parameter completed: 准备完成后回调
func showInterstitialAd(vc:UIViewController) { func showInterstitialAd(vc:UIViewController) {
guard let ad = self.interstitial else { guard let ad = self.interstitial else {
self.currentAdvType = .rewardedInterstitialType
self.showRewardedInterstitialAd(vc: vc) self.showRewardedInterstitialAd(vc: vc)
return return
} }
self.currentAdvType = .interstitialType
ad.present(from: vc) ad.present(from: vc)
} }
...@@ -156,12 +171,19 @@ class AdvManager : NSObject,FullScreenContentDelegate { ...@@ -156,12 +171,19 @@ class AdvManager : NSObject,FullScreenContentDelegate {
func adDidDismissFullScreenContent(_ ad: FullScreenPresentingAd) { func adDidDismissFullScreenContent(_ ad: FullScreenPresentingAd) {
print("Ad did dismiss full screen content.") print("Ad did dismiss full screen content.")
self.rewardedInterstitialAd = nil if self.currentAdvType == .interstitialType {
// 广告结束之后缓存新的 // 如果播放的是插页广告,播放完成应该再缓存一份
Task { self.interstitial = nil
// 同时load两个广告内容 Task {
await self.loadInterstitial() await self.loadInterstitial()
await self.loadRewardedInterstitialAd() }
}else{
// 如果播放的是插页激励,则应该同时缓存两个(因为只有在插页广告没有的情况下才会到插页激励广告)
self.rewardedInterstitialAd = nil
Task {
await self.loadInterstitial()
await self.loadRewardedInterstitialAd()
}
} }
// 更新值 // 更新值
......
...@@ -190,14 +190,22 @@ class CompressViewModel{ ...@@ -190,14 +190,22 @@ class CompressViewModel{
Print("---------原始大小:\(originalSize)") Print("---------原始大小:\(originalSize)")
// 激进压缩设置 // 压缩设置
let targetBitrate: Float let targetBitrate: Float
if originalBitrate > 0 { if originalBitrate > 0 {
// 强制压缩到原始比特率的quality比例,最低不低于500kbps // 强制压缩到原始比特率的quality比例,最低不低于500kbps
targetBitrate = max(originalBitrate * quality, 500_000) // 最低500kbps if(originalSize <= 100000){
// 当大小已经没有100KB时,按照0.95去压缩
targetBitrate = min(originalBitrate * quality, originalBitrate * 0.95)
}else{
// 最低500kbps
targetBitrate = max(originalBitrate * quality, 500_000)
}
} else { } else {
// 无法获取原始比特率时的默认值 // 无法获取原始比特率时的默认值
targetBitrate = quality * 1_000_000 // 1Mbps为基准 // 1Mbps为基准
targetBitrate = quality * 1_000_000
} }
// 创建输出URL // 创建输出URL
...@@ -205,19 +213,24 @@ class CompressViewModel{ ...@@ -205,19 +213,24 @@ class CompressViewModel{
.appendingPathComponent(UUID().uuidString) .appendingPathComponent(UUID().uuidString)
.appendingPathExtension("mp4") .appendingPathExtension("mp4")
// 激进压缩参数 // 压缩参数
let compressionProperties: [String: Any] = [ let compressionProperties: [String: Any] = [
AVVideoAverageBitRateKey: targetBitrate, AVVideoAverageBitRateKey: targetBitrate,
AVVideoMaxKeyFrameIntervalKey: 120, // 关键帧间隔加大到4秒(30fps) // 关键帧间隔加大到4秒(30fps)
AVVideoProfileLevelKey: AVVideoProfileLevelH264Baseline30, // 使用基线配置 AVVideoMaxKeyFrameIntervalKey: 120,
AVVideoAllowFrameReorderingKey: false, // 禁用B帧 // 使用基线配置
AVVideoExpectedSourceFrameRateKey: 15 // 降低帧率到15fps AVVideoProfileLevelKey: AVVideoProfileLevelH264Baseline30,
// 禁用B帧
AVVideoAllowFrameReorderingKey: false,
// 降低帧率到15fps
AVVideoExpectedSourceFrameRateKey: 15
] ]
let videoSettings: [String: Any] = [ let videoSettings: [String: Any] = [
AVVideoCodecKey: AVVideoCodecType.h264, AVVideoCodecKey: AVVideoCodecType.h264,
AVVideoWidthKey: videoTrack.naturalSize.width / 2, // 分辨率减半 // 分辨率减半
AVVideoHeightKey: videoTrack.naturalSize.height / 2, AVVideoWidthKey: videoTrack.naturalSize.width * 2 / 3,
AVVideoHeightKey: videoTrack.naturalSize.height * 2 / 3,
AVVideoScalingModeKey: AVVideoScalingModeResizeAspect, AVVideoScalingModeKey: AVVideoScalingModeResizeAspect,
AVVideoCompressionPropertiesKey: compressionProperties AVVideoCompressionPropertiesKey: compressionProperties
] ]
...@@ -225,9 +238,12 @@ class CompressViewModel{ ...@@ -225,9 +238,12 @@ class CompressViewModel{
// 音频设置也进行压缩 // 音频设置也进行压缩
let audioSettings: [String: Any] = [ let audioSettings: [String: Any] = [
AVFormatIDKey: kAudioFormatMPEG4AAC, AVFormatIDKey: kAudioFormatMPEG4AAC,
AVNumberOfChannelsKey: 1, // 单声道 // 单声道
AVSampleRateKey: 22050, // 降低采样率 AVNumberOfChannelsKey: 1,
AVEncoderBitRateKey: 64_000 // 64kbps音频比特率 // 降低采样率
AVSampleRateKey: 22050,
// 64kbps音频比特率
AVEncoderBitRateKey: 64_000
] ]
// 创建导出会话 // 创建导出会话
...@@ -257,7 +273,7 @@ class CompressViewModel{ ...@@ -257,7 +273,7 @@ class CompressViewModel{
let compressedSize = attributes[.size] as? NSNumber { let compressedSize = attributes[.size] as? NSNumber {
if compressedSize.doubleValue >= originalSize { if compressedSize.doubleValue >= originalSize {
// 如果压缩后仍然大于等于原始大小,使用更激进的设置重新压缩 // 如果压缩后大于等于原始大小,使用更激进的设置重新压缩
try? FileManager.default.removeItem(at: outputURL) try? FileManager.default.removeItem(at: outputURL)
self.recompressWithMoreAggressiveSettings(avAsset: avAsset, originalSize: originalSize, index: index) { url, error in self.recompressWithMoreAggressiveSettings(avAsset: avAsset, originalSize: originalSize, index: index) { url, error in
outputURLs[index] = url outputURLs[index] = url
...@@ -288,74 +304,6 @@ class CompressViewModel{ ...@@ -288,74 +304,6 @@ class CompressViewModel{
} }
} }
private static func videoComposition(for asset: AVAsset, quality: Float) -> AVVideoComposition? {
guard let videoTrack = asset.tracks(withMediaType: .video).first else {
return nil
}
// 1. 获取原始视频的比特率作为参考
let originalBitrate = videoTrack.estimatedDataRate
let targetBitrate: Float
// 2. 根据质量参数和原始比特率计算目标比特率
if originalBitrate > 0 {
// 确保压缩后的比特率不超过原始比特率的quality比例
// 例如quality=0.7表示压缩到原始比特率的70%
// 最多压缩到95%,避免质量损失太小
targetBitrate = min(originalBitrate * quality, originalBitrate * 0.95)
} else {
// 无法获取原始比特率时的默认值
targetBitrate = quality * 5_000_000 // 5Mbps为基准
}
// 3. 设置更高效的关键帧间隔(从默认的10秒改为30秒)
let videoCompressionProperties: [String: Any] = [
AVVideoAverageBitRateKey: targetBitrate,
// 关键帧间隔(帧数),30fps时约为3秒
AVVideoMaxKeyFrameIntervalKey: 90,
AVVideoProfileLevelKey: AVVideoProfileLevelH264HighAutoLevel,
// 禁用B帧可减小文件但可能降低质量
AVVideoAllowFrameReorderingKey: false
]
// 4. 保持原始分辨率
let naturalSize = videoTrack.naturalSize
let videoSettings: [String: Any] = [
AVVideoCodecKey: AVVideoCodecType.h264,
AVVideoWidthKey: naturalSize.width,
AVVideoHeightKey: naturalSize.height,
AVVideoScalingModeKey: AVVideoScalingModeResizeAspect,
AVVideoCompressionPropertiesKey: videoCompressionProperties
]
let composition = AVMutableVideoComposition()
composition.renderSize = naturalSize
composition.frameDuration = CMTime(value: 1, timescale: 30)
let instruction = AVMutableVideoCompositionInstruction()
instruction.timeRange = CMTimeRange(start: .zero, duration: asset.duration)
let layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack)
instruction.layerInstructions = [layerInstruction]
composition.instructions = [instruction]
return composition
}
private static func presetName(for quality: Float) -> String {
switch quality {
case 0.8...1.0: return AVAssetExportPresetHighestQuality
case 0.6..<0.8: return AVAssetExportPreset1280x720
case 0.4..<0.6: return AVAssetExportPreset960x540
case 0.2..<0.4: return AVAssetExportPreset640x480
default: return AVAssetExportPresetLowQuality
}
}
...@@ -473,17 +421,20 @@ class CompressViewModel{ ...@@ -473,17 +421,20 @@ class CompressViewModel{
// 更激进的设置 // 更激进的设置
let compressionProperties: [String: Any] = [ let compressionProperties: [String: Any] = [
AVVideoAverageBitRateKey: 250_000, // 固定250kbps // 固定250kbps
AVVideoMaxKeyFrameIntervalKey: 240, // 关键帧间隔8秒 AVVideoAverageBitRateKey: 250_000,
// 关键帧间隔8秒
AVVideoMaxKeyFrameIntervalKey: 240,
AVVideoProfileLevelKey: AVVideoProfileLevelH264Baseline30, AVVideoProfileLevelKey: AVVideoProfileLevelH264Baseline30,
AVVideoAllowFrameReorderingKey: false, AVVideoAllowFrameReorderingKey: false,
AVVideoExpectedSourceFrameRateKey: 10 // 帧率降到10fps // 帧率降到10fps
AVVideoExpectedSourceFrameRateKey: 10
] ]
let videoSettings: [String: Any] = [ let videoSettings: [String: Any] = [
AVVideoCodecKey: AVVideoCodecType.h264, AVVideoCodecKey: AVVideoCodecType.h264,
AVVideoWidthKey: videoTrack.naturalSize.width / 4, // 分辨率降到1/4 AVVideoWidthKey: videoTrack.naturalSize.width / 2,
AVVideoHeightKey: videoTrack.naturalSize.height / 4, AVVideoHeightKey: videoTrack.naturalSize.height / 2,
AVVideoScalingModeKey: AVVideoScalingModeResizeAspect, AVVideoScalingModeKey: AVVideoScalingModeResizeAspect,
AVVideoCompressionPropertiesKey: compressionProperties AVVideoCompressionPropertiesKey: compressionProperties
] ]
...@@ -491,8 +442,10 @@ class CompressViewModel{ ...@@ -491,8 +442,10 @@ class CompressViewModel{
let audioSettings: [String: Any] = [ let audioSettings: [String: Any] = [
AVFormatIDKey: kAudioFormatMPEG4AAC, AVFormatIDKey: kAudioFormatMPEG4AAC,
AVNumberOfChannelsKey: 1, AVNumberOfChannelsKey: 1,
AVSampleRateKey: 16000, // 16kHz采样率 // 16kHz采样率
AVEncoderBitRateKey: 32_000 // 32kbps音频比特率 AVSampleRateKey: 16000,
// 32kbps音频比特率
AVEncoderBitRateKey: 32_000
] ]
guard let exportSession = AVAssetExportSession(asset: avAsset, presetName: AVAssetExportPresetLowQuality) else { guard let exportSession = AVAssetExportSession(asset: avAsset, presetName: AVAssetExportPresetLowQuality) else {
...@@ -541,7 +494,8 @@ class CompressViewModel{ ...@@ -541,7 +494,8 @@ class CompressViewModel{
let audioMix = AVMutableAudioMix() let audioMix = AVMutableAudioMix()
let audioInputParams = AVMutableAudioMixInputParameters(track: audioTrack) let audioInputParams = AVMutableAudioMixInputParameters(track: audioTrack)
audioInputParams.audioTimePitchAlgorithm = .timeDomain // 保持音频处理简单 // 保持音频处理简单
audioInputParams.audioTimePitchAlgorithm = .timeDomain
audioMix.inputParameters = [audioInputParams] audioMix.inputParameters = [audioInputParams]
return audioMix return audioMix
......
...@@ -10,7 +10,7 @@ import Foundation ...@@ -10,7 +10,7 @@ import Foundation
class ContactAllViewController : BaseViewController { class ContactAllViewController : BaseViewController {
var dataSourceModel : [ContactModel] = [] var dataSourceModel : [ContactModel]?
lazy var navView : ContactNavView = { lazy var navView : ContactNavView = {
let view = ContactNavView() let view = ContactNavView()
...@@ -71,18 +71,23 @@ extension ContactAllViewController { ...@@ -71,18 +71,23 @@ extension ContactAllViewController {
override func viewWillAppear(_ animated: Bool) { override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated) super.viewWillAppear(animated)
if self.dataSourceModel.count > 0 { if let data = self.dataSourceModel {
self.setNormalPage() if data.count > 0 {
self.normalView.dataSourceModel = self.dataSourceModel self.setNormalPage()
DispatchQueue.main.async { self.normalView.dataSourceModel = data
self.normalView.subTitleLabel.text = "\(self.dataSourceModel.count) Contacts" DispatchQueue.main.async {
self.normalView.sortContacts() self.normalView.subTitleLabel.text = "\(data.count) Contacts"
self.normalView.tableView.reloadData() self.normalView.sortContacts()
self.normalView.setupCustomIndexView() self.normalView.tableView.reloadData()
self.normalView.setupCustomIndexView()
}
}else{
self.setDefaultPage()
} }
}else {
}else{
self.setDefaultPage() self.setDefaultPage()
} }
} }
} }
...@@ -73,22 +73,23 @@ class ContactBackupViewController : BaseViewController { ...@@ -73,22 +73,23 @@ class ContactBackupViewController : BaseViewController {
let vm = BackupViewModel() let vm = BackupViewModel()
// 备份之前先看看是否有可用的联系人 // 备份之前先看看是否有可用的联系人
if let data = self.dataSourceAllModel {
vm.backupAllContacts(data.allContacts) { finished, error in
vm.backupAllContacts(self.dataSourceAllModel!.allContacts) { finished, error in if let error = error {
if let error = error { Print("添加失败,\(error.localizedDescription)")
Print("添加失败,\(error.localizedDescription)") }
} DispatchQueue.main.async {
DispatchQueue.main.async { // 再次请求数据 重新刷新页面
// 再次请求数据 重新刷新页面 let buAlertVc = ContactBackUpCompletedAlertView(frame: self.view.bounds)
let buAlertVc = ContactBackUpCompletedAlertView(frame: self.view.bounds) self.view.addSubview(buAlertVc)
self.view.addSubview(buAlertVc) DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { buAlertVc.removeFromSuperview()
buAlertVc.removeFromSuperview() self.updateCurrentPageData()
self.updateCurrentPageData() }
} }
} }
} }
} }
} }
} }
......
...@@ -12,7 +12,7 @@ class ContactIncompleteViewController : BaseViewController { ...@@ -12,7 +12,7 @@ class ContactIncompleteViewController : BaseViewController {
private var widthConstraint: Constraint? private var widthConstraint: Constraint?
var dataSourceModel : [ContactModel] = [] var dataSourceModel : [ContactModel]?
lazy var navView : ContactNavView = { lazy var navView : ContactNavView = {
let view = ContactNavView() let view = ContactNavView()
...@@ -101,17 +101,20 @@ class ContactIncompleteViewController : BaseViewController { ...@@ -101,17 +101,20 @@ class ContactIncompleteViewController : BaseViewController {
extension ContactIncompleteViewController { extension ContactIncompleteViewController {
override func viewWillAppear(_ animated: Bool) { override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated) super.viewWillAppear(animated)
if let data = self.dataSourceModel {
if self.dataSourceModel.count > 0 { if data.count > 0 {
self.setNormalPage() self.setNormalPage()
self.normalView.dataSourceModel = self.dataSourceModel self.normalView.dataSourceModel = data
DispatchQueue.main.async { DispatchQueue.main.async {
self.normalView.sortContacts() self.normalView.sortContacts()
self.normalView.subTitleLabel.text = "\(self.dataSourceModel.count) Contacts" self.normalView.subTitleLabel.text = "\(data.count) Contacts"
self.normalView.tableView.reloadData() self.normalView.tableView.reloadData()
}
}else{
self.setDefaultPage()
} }
}else {
}else{
self.setDefaultPage() self.setDefaultPage()
} }
} }
......
...@@ -112,18 +112,33 @@ extension ContactViewController{ ...@@ -112,18 +112,33 @@ extension ContactViewController{
// 创建数组 // 创建数组
var incompleteContacts : [ContactModel] = [] var incompleteContacts : [ContactModel] = []
var allContacts : [ContactModel] = [] var allContacts : [ContactModel] = []
var duplicates : [[ContactModel]] = []
var contactsByName: [String: [ContactModel]] = [:]
try store.enumerateContacts(with: request) { contact, stop in try store.enumerateContacts(with: request) { contact, stop in
let givenName = contact.givenName let givenName = contact.givenName
let familyName = contact.familyName let familyName = contact.familyName
let fullName = "\(familyName) \(givenName)" let fullName = "\(familyName)\(givenName)"
let phoneNumbers = contact.phoneNumbers.map { $0.value.stringValue } let phoneNumbers = contact.phoneNumbers.map { $0.value.stringValue }
let model = ContactModel.init(name: fullName, phoneNumber: phoneNumbers,identifier: contact.identifier) let model = ContactModel.init(name: fullName, phoneNumber: phoneNumbers,identifier: contact.identifier)
if fullName.isEmpty || phoneNumbers.count <= 0 { if fullName.isEmpty || phoneNumbers.count <= 0 {
incompleteContacts.append(model) incompleteContacts.append(model)
} }
allContacts.append(model) allContacts.append(model)
self.dataSourceModel = ContactModuleModel.init(duplicates: [], incompleteContacts: incompleteContacts, backups: [], allContacts: allContacts)
if !fullName.isEmpty {
if contactsByName[fullName] == nil {
contactsByName[fullName] = [model]
} else {
contactsByName[fullName]?.append(model)
}
}
duplicates = contactsByName.values.filter { $0.count > 1 }
} }
self.dataSourceModel = ContactModuleModel.init(duplicates: sortDupDataSource(orgData: duplicates), incompleteContacts: incompleteContacts, backups: [], allContacts: allContacts)
DispatchQueue.main.async { DispatchQueue.main.async {
self.updateModuleData() self.updateModuleData()
self.moduleView.tableView.reloadData() self.moduleView.tableView.reloadData()
...@@ -148,13 +163,13 @@ extension ContactViewController{ ...@@ -148,13 +163,13 @@ extension ContactViewController{
self.moduleView.tableView.reloadData() self.moduleView.tableView.reloadData()
} }
} }
// fixme:获取重复数据
} }
// MARK: 辅助方法
/// 获取联系人权限
/// - Parameter completion: 回调
func requestContactsPermission(completion: @escaping (Bool) -> Void) { func requestContactsPermission(completion: @escaping (Bool) -> Void) {
let store = CNContactStore() let store = CNContactStore()
switch CNContactStore.authorizationStatus(for: .contacts) { switch CNContactStore.authorizationStatus(for: .contacts) {
...@@ -180,4 +195,12 @@ extension ContactViewController{ ...@@ -180,4 +195,12 @@ extension ContactViewController{
} }
} }
/// 重复项排序-做一个反序操作,让最新添加的联系人显示在最前面
/// - Parameter orgData: 原始数据
/// - Returns: 排序后的数据
func sortDupDataSource(orgData:[[ContactModel]])->[[ContactModel]]{
return orgData.map { $0.reversed() }
}
} }
...@@ -19,10 +19,15 @@ class ContactsDupViewController : BaseViewController { ...@@ -19,10 +19,15 @@ class ContactsDupViewController : BaseViewController {
let view = ContactNoDupView() let view = ContactNoDupView()
return view return view
}() }()
lazy var normalView : ContactDupNormalView = {
let view = ContactDupNormalView()
return view
}()
// 默认页面 // 默认页面
func setDefaultPage(){ func setDefaultPage(){
self.normalView.removeFromSuperview()
self.view.addSubview(self.emptyView) self.view.addSubview(self.emptyView)
self.emptyView.snp.makeConstraints { make in self.emptyView.snp.makeConstraints { make in
...@@ -32,6 +37,17 @@ class ContactsDupViewController : BaseViewController { ...@@ -32,6 +37,17 @@ class ContactsDupViewController : BaseViewController {
} }
} }
func setNormalPage(){
self.emptyView.removeFromSuperview()
self.view.addSubview(self.normalView)
self.normalView.snp.makeConstraints { make in
make.top.equalTo(self.navView.snp.bottom).offset(0)
make.left.right.equalToSuperview()
make.bottom.equalToSuperview()
}
}
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
...@@ -49,3 +65,36 @@ class ContactsDupViewController : BaseViewController { ...@@ -49,3 +65,36 @@ class ContactsDupViewController : BaseViewController {
} }
extension ContactsDupViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let data = self.dataSourceModel {
if data.count > 0 {
self.setNormalPage()
self.normalView.dataSourceModel = data
DispatchQueue.main.async {
self.normalView.subTitleLabel.text = "\(self.getCountFromDataSource(data:data)) Contacts"
self.normalView.tableView.reloadData()
}
}else{
self.setDefaultPage()
}
}else {
self.setDefaultPage()
}
}
func getCountFromDataSource(data:[[ContactModel]]) -> Int{
var totalElementCount = 0
for subArray in data {
totalElementCount += subArray.count
}
return totalElementCount
}
}
...@@ -40,13 +40,13 @@ extension ContactModel { ...@@ -40,13 +40,13 @@ extension ContactModel {
struct ContactModuleModel { struct ContactModuleModel {
var duplicates: [[ContactModel]] var duplicates: [[ContactModel]] = []
var incompleteContacts: [ContactModel] var incompleteContacts: [ContactModel] = []
var backups: [BackupInfoModel] var backups: [BackupInfoModel] = []
var allContacts: [ContactModel] var allContacts: [ContactModel] = []
init(duplicates: [[ContactModel]], incompleteContacts: [ContactModel], backups: [BackupInfoModel], allContacts: [ContactModel]) { init(duplicates: [[ContactModel]], incompleteContacts: [ContactModel], backups: [BackupInfoModel], allContacts: [ContactModel]) {
self.duplicates = duplicates self.duplicates = duplicates
......
//
// CustomContactDupTableViewCell.swift
// PhoneManager
//
// Created by edy on 2025/5/6.
//
import Foundation
class CustomContactDupTableViewCell : UITableViewCell {
var model : ContactModel?{
didSet{
self.nameLabel.text = model?.name
if let numbers = model?.phoneNumber {
self.numberLabel.text = getPhoneNumberString(photoNumbers:numbers)
}else {
self.numberLabel.text = ""
}
}
}
lazy var backView : UIView = {
let view = UIView()
view.backgroundColor = UIColor(red: 0.95, green: 0.96, blue: 0.99, alpha: 1)
view.clipsToBounds = true
view.layer.cornerRadius = 12
return view
}()
lazy var nameLabel : UILabel = {
let label = UILabel()
label.textColor = UIColor(red: 0.2, green: 0.2, blue: 0.2, alpha: 1)
label.font = UIFont.systemFont(ofSize: 16, weight: .bold)
return label
}()
lazy var numberLabel : UILabel = {
let label = UILabel()
label.textColor = UIColor(red: 0.4, green: 0.4, blue: 0.4, alpha: 1)
label.font = UIFont.systemFont(ofSize: 14, weight: .regular)
return label
}()
lazy var selectButton : UIButton = {
let button = UIButton(type: .custom)
button.setImage(UIImage(named: "ic_sel_com"), for: .normal)
button.setImage(UIImage(named: "ic_unsel_com_red"), for: .selected)
button.addTarget(self, action: #selector(selectContact(_:)), for: .touchUpInside)
return button
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.selectionStyle = .none
self.contentView.addSubview(self.backView)
self.backView.addSubview(self.nameLabel)
self.backView.addSubview(self.numberLabel)
self.backView.addSubview(self.selectButton)
self.backView.snp.makeConstraints { make in
make.left.right.top.equalToSuperview()
make.height.equalTo(71)
}
self.nameLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(16 * RScreenH())
make.top.equalToSuperview().offset(16)
make.width.equalTo(210)
make.height.equalTo(22)
}
self.numberLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(16 * RScreenH())
make.top.equalTo(self.nameLabel.snp.bottom).offset(0)
make.width.equalTo(210)
make.height.equalTo(20)
}
self.selectButton.snp.makeConstraints { make in
make.width.height.equalTo(24)
make.centerY.equalToSuperview()
make.right.equalToSuperview().offset(-16 * RScreenW())
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension CustomContactDupTableViewCell {
@objc private func selectContact(_ sender: UIButton) {
sender.isSelected = !sender.isSelected
// buttonSelectCallBack(model!,sender.isSelected)
}
func getPhoneNumberString(photoNumbers : [String]) -> String{
var tempNumbers = photoNumbers
let nonEmptyStrings = tempNumbers.filter {!$0.isEmpty}
if nonEmptyStrings.count <= 0 {
return ""
}
return photoNumbers.joined(separator: " / ")
}
}
//
// ContactDupNormalView.swift
// PhoneManager
//
// Created by edy on 2025/5/6.
//
import Foundation
class ContactDupNormalView : UIView {
var dataSourceModel : [[ContactModel]] = []
var selectData: [Int : [ContactModel]] = [:]
/// 选择的联系人
private var selectedContacts: [ContactModel] = []
lazy var titleLabel: UILabel = {
let label = UILabel()
label.text = "All contacts"
label.font = UIFont.systemFont(ofSize: 20, weight: .bold)
label.textColor = UIColor(red: 0.2, green: 0.2, blue: 0.2, alpha: 1)
label.textAlignment = .left
return label
}()
lazy var subTitleLabel: UILabel = {
let label = UILabel()
label.text = "\(self.dataSourceModel.count) Contacts"
label.font = UIFont.systemFont(ofSize: 14, weight: .regular)
label.textColor = UIColor(red: 0.2, green: 0.2, blue: 0.2, alpha: 1)
label.textAlignment = .left
return label
}()
lazy var tableView : UITableView = {
let tableView = UITableView(frame: CGRect(x: 0, y: 0, width: 0, height: 12), style: UITableView.Style.grouped)
tableView.dataSource = self
tableView.delegate = self
tableView.register(CustomContactDupTableViewCell.self, forCellReuseIdentifier: "CustomContactDupTableViewCell")
tableView.separatorStyle = .none
tableView.backgroundColor = .clear
tableView.showsVerticalScrollIndicator = false
if #available(iOS 15.0, *) {
tableView.sectionHeaderTopPadding = 0
}
return tableView
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(self.titleLabel)
self.addSubview(self.subTitleLabel)
self.addSubview(self.tableView)
self.titleLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(15 * RScreenW())
make.top.equalToSuperview().offset(14 * RScreenH())
make.width.equalTo(345 * RScreenW())
make.height.equalTo(32)
}
self.subTitleLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(15 * RScreenW())
make.top.equalTo(self.titleLabel.snp.bottom).offset(2 * RScreenH())
make.width.equalTo(345 * RScreenW())
make.height.equalTo(20)
}
self.tableView.snp.makeConstraints { make in
make.top.equalTo(self.subTitleLabel.snp.bottom).offset(16 * RScreenH())
make.left.equalToSuperview().offset(15 * RScreenW())
make.right.equalToSuperview().offset(-15 * RScreenW())
make.bottom.equalToSuperview().offset(-44 * RScreenH())
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension ContactDupNormalView : UITableViewDelegate,UITableViewDataSource{
func numberOfSections(in tableView: UITableView) -> Int {
return self.dataSourceModel.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.dataSourceModel[section].count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomContactDupTableViewCell", for: indexPath) as! CustomContactDupTableViewCell
cell.model = self.dataSourceModel[indexPath.section][indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 77 + 8 * RScreenH()
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let view = UIView(frame: CGRect(x: 0 , y: 0, width: self.tableView.width, height: 22))
view.backgroundColor = .clear
let label = UILabel(frame: CGRect(x: 0 , y: 0, width: 200, height: 22))
label.text = "\(self.dataSourceModel[section].count) Duplicate Contacts"
label.textAlignment = .left
label.font = UIFont.systemFont(ofSize: 16, weight: .medium)
label.textColor = UIColor(red: 0.4, green: 0.4, blue: 0.4, alpha: 1)
view.addSubview(label)
let selectLabel = UILabel(frame: CGRect(x: 0 , y: 0, width: 100, height: 22))
selectLabel.center = CGPointMake(self.tableView.width - 50, label.centerY)
selectLabel.textAlignment = .right
selectLabel.textColor = UIColor(red: 0, green: 0.51, blue: 1, alpha: 1)
selectLabel.text = "Select All"
let tap = UITapGestureRecognizer()
tap.addTarget(self, action: #selector(cellSelectTap))
selectLabel.isUserInteractionEnabled = true
selectLabel.addGestureRecognizer(tap)
view.addSubview(selectLabel)
return view
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 34
}
// MARK: 响应方法
@objc func cellSelectTap(){
}
// MARK: 辅助方法
}
...@@ -123,7 +123,7 @@ extension ContactModuleView:UITableViewDataSource, UITableViewDelegate { ...@@ -123,7 +123,7 @@ extension ContactModuleView:UITableViewDataSource, UITableViewDelegate {
if indexPath.section == 1 { if indexPath.section == 1 {
// 跳转不完整联系人页面 // 跳转不完整联系人页面
let vc : ContactIncompleteViewController = ContactIncompleteViewController() let vc : ContactIncompleteViewController = ContactIncompleteViewController()
vc.dataSourceModel = self.dataSourceModel!.incompleteContacts vc.dataSourceModel = self.dataSourceModel?.incompleteContacts
self.responderViewController()?.navigationController?.pushViewController(vc, animated: true) self.responderViewController()?.navigationController?.pushViewController(vc, animated: true)
} }
if indexPath.section == 2 { if indexPath.section == 2 {
......
...@@ -53,6 +53,19 @@ class HomeViewController:BaseViewController { ...@@ -53,6 +53,19 @@ class HomeViewController:BaseViewController {
} }
} }
case 2 : case 2 :
DispatchQueue.main.async {[weak self] in
guard let self else {return}
let vc:ContactViewController = ContactViewController()
self.navigationController?.pushViewController(vc, animated: true)
}
break
case 3 :
DispatchQueue.main.async {[weak self] in
guard let self else {return}
}
break
case 4 :
DispatchQueue.main.async {[weak self] in DispatchQueue.main.async {[weak self] in
guard let self else {return} guard let self else {return}
let vc:CompressController = CompressController() let vc:CompressController = CompressController()
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
"heightImage": "tabbar_contacts_hight", "heightImage": "tabbar_contacts_hight",
"text":"Contacts", "text":"Contacts",
}, },
{
"normalImage": "ic_email_home_pre", "normalImage": "ic_email_home_pre",
"heightImage": "tabbar_email_hight", "heightImage": "tabbar_email_hight",
"text":"Email Cleaner", "text":"Email Cleaner",
......
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
<false/> <false/>
</dict> </dict>
<key>GADApplicationIdentifier</key> <key>GADApplicationIdentifier</key>
<string>ca-app-pub-3480207748580737~4236262472</string> <string>ca-app-pub-3940256099942544~1458002511</string>
<key>SKAdNetworkItems</key> <key>SKAdNetworkItems</key>
<array> <array>
<dict> <dict>
......
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