Commit 28ae9c45 authored by CZ1004's avatar CZ1004

Merge branch 'contact' into Advertisement

* contact:
  联系人不完整联系人
  联系人
  联系人代码

# Conflicts:
#	PhoneManager/Assets.xcassets/Charge/img_bj_battery 1.imageset/Contents.json
#	PhoneManager/Assets.xcassets/Charge/img_bj_battery 1.imageset/img_bj_battery.png
#	PhoneManager/Assets.xcassets/Charge/img_bj_battery 1.imageset/img_bj_battery@2x.png
#	PhoneManager/Assets.xcassets/Charge/img_bj_battery 1.imageset/img_bj_battery@3x.png
#	PhoneManager/Assets.xcassets/Charge/img_bj_battery_default.imageset/Contents.json
#	PhoneManager/Assets.xcassets/Charge/img_bj_battery_default.imageset/img_bj_battery.png
#	PhoneManager/Assets.xcassets/Charge/img_bj_battery_default.imageset/img_bj_battery@2x.png
#	PhoneManager/Assets.xcassets/Charge/img_bj_battery_default.imageset/img_bj_battery@3x.png
#	PhoneManager/Assets.xcassets/Contact/btn_add_contact.imageset/btn_add_contact.png
#	PhoneManager/Assets.xcassets/Contact/btn_add_contact.imageset/btn_add_contact@2x.png
#	PhoneManager/Assets.xcassets/Contact/btn_add_contact.imageset/btn_add_contact@3x.png
#	PhoneManager/Assets.xcassets/img_bj_battery.imageset/Contents.json
#	PhoneManager/Assets.xcassets/img_bj_battery.imageset/img_bj_battery.png
#	PhoneManager/Assets.xcassets/img_bj_battery.imageset/img_bj_battery@2x.png
#	PhoneManager/Assets.xcassets/img_bj_battery.imageset/img_bj_battery@3x.png
#	PhoneManager/Class/Session/Home/Controller/HomeViewController.swift
#	PhoneManager/Class/Session/Home/Model/TabbarImtesData.json
parents c3c18b7a b62e173d
//
// AllContactViewController.swift
// PhoneManager
//
// Created by 赵前 on 2025/4/19.
//
import Foundation
class ContactAllViewController : BaseViewController {
var dataSourceModel : [ContactModel] = []
lazy var navView : ContactNavView = {
let view = ContactNavView()
return view
}()
lazy var emptyView : ContactNoAllView = {
let view = ContactNoAllView()
return view
}()
lazy var normalView : ContactAllView = {
let view = ContactAllView()
return view
}()
// 默认页面
func setDefaultPage(){
self.normalView.removeFromSuperview()
self.view.addSubview(self.emptyView)
self.emptyView.snp.makeConstraints { make in
make.top.equalTo(self.navView.snp.bottom).offset(0)
make.left.right.equalToSuperview()
make.bottom.equalToSuperview()
}
}
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() {
super.viewDidLoad()
self.view.backgroundColor = .white
self.view.addSubview(self.navView)
self.navView.snp.makeConstraints { make in
make.top.left.right.equalToSuperview()
make.height.equalTo(statusBarHeight + 44)
}
}
}
extension ContactAllViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if self.dataSourceModel.count > 0 {
self.setNormalPage()
self.normalView.dataSourceModel = self.dataSourceModel
DispatchQueue.main.async {
self.normalView.subTitleLabel.text = "\(self.dataSourceModel.count) Contacts"
self.normalView.sortContacts()
self.normalView.tableView.reloadData()
self.normalView.setupCustomIndexView()
}
}else{
self.setDefaultPage()
}
}
}
//
// ContactBackupDetailViewController.swift
// PhoneManager
//
// Created by 赵前 on 2025/4/20.
//
import Foundation
class ContactBackupDetailViewController : BaseViewController {
var dataSourceModel : [ContactModel] = []
/// 分组后的联系人
private var sectionedContacts: [String: [ContactModel]] = [:]
/// 联系人首字母数组
private var sectionTitles: [String] = []
/// 选择的联系人
private var selectedContacts: [ContactModel] = []
var customIndexView: UIStackView!
var selectedIndex = 0
lazy var navView : ContactNavView = {
let view = ContactNavView()
return view
}()
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(CustomContactBacDetailTableViewCell.self, forCellReuseIdentifier: "CustomContactBacDetailTableViewCell")
tableView.separatorStyle = .none
tableView.backgroundColor = .clear
tableView.showsVerticalScrollIndicator = false
if #available(iOS 15.0, *) {
tableView.sectionHeaderTopPadding = 0
}
return tableView
}()
lazy var restoreButton : RestoreButtonView = {
let button = RestoreButtonView()
// 设置删除按钮
button.layer.cornerRadius = 23
button.clipsToBounds = true
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(self.navView)
self.view.addSubview(self.titleLabel)
self.view.addSubview(self.subTitleLabel)
self.view.addSubview(self.tableView)
self.view.addSubview(self.restoreButton)
self.navView.snp.makeConstraints { make in
make.top.left.right.equalToSuperview()
make.height.equalTo(statusBarHeight + 44)
}
self.titleLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(15 * RScreenW())
make.top.equalTo(self.navView.snp.bottom)
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(-102 * RScreenH())
}
self.restoreButton.snp.makeConstraints { make in
make.top.equalTo(self.tableView.snp.bottom).offset(16 * RScreenH())
make.width.equalTo(345 * RScreenW())
make.height.equalTo(46)
make.centerX.equalToSuperview()
}
// 排序
self.sortContacts()
self.restoreButton.submitCallBack = {
self.alertRestore()
}
}
}
extension ContactBackupDetailViewController:UITableViewDelegate,UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return sectionTitles.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let sectionTitle = sectionTitles[section]
return sectionedContacts[sectionTitle]?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomContactBacDetailTableViewCell", for: indexPath) as! CustomContactBacDetailTableViewCell
let sectionTitle = sectionTitles[indexPath.section]
let contact = sectionedContacts[sectionTitle]?[indexPath.row]
cell.model = contact
cell.nameLabel.text = contact?.name
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 77 + 8 * RScreenH()
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let label = UILabel(frame: CGRect(x: 0 , y: 0, width: 345, height: 20))
label.text = sectionTitles[section]
label.textAlignment = .left
label.font = UIFont.systemFont(ofSize: 14, weight: .bold)
label.textColor = UIColor(red: 0.7, green: 0.7, blue: 0.7, alpha: 1)
return label
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 20
}
func setupCustomIndexView() {
customIndexView = UIStackView()
customIndexView.axis = .vertical
customIndexView.alignment = .center
customIndexView.distribution = .equalSpacing
customIndexView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(customIndexView)
self.customIndexView.snp.makeConstraints { make in
make.left.equalTo(self.tableView.snp.right).offset(0)
make.top.equalTo(self.subTitleLabel.snp.bottom).offset(77)
make.width.equalTo(15 * RScreenW())
make.height.equalTo(354)
}
for (index, section) in sectionTitles.enumerated() {
let label = UILabel()
label.text = section
label.font = UIFont.systemFont(ofSize: 10, weight: .bold)
label.textColor = index == selectedIndex ? .white : UIColor(red: 0.4, green: 0.4, blue: 0.4, alpha: 1)
label.textAlignment = .center
label.layer.cornerRadius = 7
label.layer.masksToBounds = true
label.backgroundColor = index == selectedIndex ? UIColor(red: 0, green: 0.51, blue: 1, alpha: 1) : .clear
NSLayoutConstraint.activate([
label.widthAnchor.constraint(equalToConstant: 14),
label.heightAnchor.constraint(equalToConstant: 14)
])
label.isUserInteractionEnabled = true
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(indexTapped(_:)))
label.addGestureRecognizer(tapGesture)
label.tag = index
customIndexView.addArrangedSubview(label)
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let visibleRect = CGRect(origin: tableView.contentOffset, size: tableView.bounds.size)
let visiblePoint = CGPoint(x: visibleRect.midX, y: visibleRect.minY)
if let visibleIndexPath = tableView.indexPathForRow(at: visiblePoint) {
let newIndex = visibleIndexPath.section
if newIndex != selectedIndex {
selectedIndex = newIndex
updateIndexStyles()
}
}
}
func updateIndexStyles() {
for (index, view) in customIndexView.arrangedSubviews.enumerated() {
if let label = view as? UILabel {
label.textColor = index == selectedIndex ? .white : UIColor(red: 0.4, green: 0.4, blue: 0.4, alpha: 1)
label.backgroundColor = index == selectedIndex ? UIColor(red: 0, green: 0.51, blue: 1, alpha: 1) : .clear
}
}
}
@objc func indexTapped(_ gesture: UIGestureRecognizer) {
if let label = gesture.view as? UILabel {
let index = label.tag
let indexPath = IndexPath(row: 0, section: index)
tableView.scrollToRow(at: indexPath, at: .top, animated: true)
selectedIndex = index
updateIndexStyles()
}
}
func alertRestore() {
// 是否确实需要恢复
let alertVc = ContactBacRestoreAlertView(frame: self.view.bounds)
self.view.addSubview(alertVc)
alertVc.sureCallBack = {
}
}
/// 给联系人分组排序
func sortContacts() {
sectionedContacts.removeAll()
for contact in self.dataSourceModel {
let firstLetter = pinyinFirstLetter(contact.name).uppercased()
if sectionedContacts[firstLetter] == nil {
sectionedContacts[firstLetter] = []
}
sectionedContacts[firstLetter]?.append(contact)
}
sectionTitles = sectionedContacts.keys.sorted()
for key in sectionTitles {
sectionedContacts[key] = sectionedContacts[key]?.sorted {
return pinyinFirstLetter($0.name).uppercased() < pinyinFirstLetter($1.name).uppercased()
}
}
}
// 获取拼音首字母
private func pinyinFirstLetter(_ string: String) -> String {
let mutableString = NSMutableString(string: string)
CFStringTransform(mutableString, nil, kCFStringTransformToLatin, false)
CFStringTransform(mutableString, nil, kCFStringTransformStripDiacritics, false)
var pinyin = mutableString as String
if pinyin.isEmpty {
return "#"
}
pinyin = pinyin.replacingOccurrences(of: " ", with: "")
return pinyin.isEmpty ? "#" : String(pinyin.first!).uppercased()
}
}
//
// ContactBackupViewController.swift
// PhoneManager
//
// Created by 赵前 on 2025/4/19.
//
import Foundation
class ContactBackupViewController : BaseViewController {
var dataSourceModel : [BackupInfoModel]?
// 总数据
var dataSourceAllModel : ContactModuleModel?
lazy var navView : ContactNavView = {
let view = ContactNavView()
return view
}()
lazy var emptyView : ContactNoBackUpView = {
let view = ContactNoBackUpView()
return view
}()
lazy var normalView : ContactBackUpNormalView = {
let view = ContactBackUpNormalView()
return view
}()
// 默认页面
func setDefaultPage(){
self.view.addSubview(self.emptyView)
self.emptyView.snp.makeConstraints { make in
make.top.equalTo(self.navView.snp.bottom).offset(0)
make.left.right.equalToSuperview()
make.bottom.equalToSuperview()
}
}
// 默认页面
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() {
super.viewDidLoad()
self.view.backgroundColor = .white
self.view.addSubview(self.navView)
self.navView.snp.makeConstraints { make in
make.top.left.right.equalToSuperview()
make.height.equalTo(statusBarHeight + 44)
}
self.setDefaultPage()
self.emptyView.newBackupCallback = {
// 备份数据
let vm = BackupViewModel()
// 备份之前先看看是否有可用的联系人
vm.backupAllContacts(self.dataSourceAllModel!.allContacts) { finished, error in
if let error = error {
Print("添加失败,\(error.localizedDescription)")
}
DispatchQueue.main.async {
// 再次请求数据 重新刷新页面
let buAlertVc = ContactBackUpCompletedAlertView(frame: self.view.bounds)
self.view.addSubview(buAlertVc)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
buAlertVc.removeFromSuperview()
self.updateCurrentPageData()
}
}
}
}
}
}
extension ContactBackupViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// 请求下数据
self.updateCurrentPageData()
}
func updateCurrentPageData(){
let vm = BackupViewModel()
vm.retrieveContactsFromBackup {[weak self] info, error in
guard let self else {return}
if error != nil {
Print("取出备份错误\(String(describing: error?.localizedDescription))")
return
}
// 备份是否有数据
DispatchQueue.main.async {
if info!.count <= 0 {
self.setDefaultPage()
}else{
self.setNormalPage()
self.dataSourceModel = info
self.normalView.dataSourceModel = info ?? []
self.normalView.dataSourceAllModel = self.dataSourceAllModel?.allContacts
self.normalView.tableView.reloadData()
}
}
}
}
}
//
// ContactIncompleteViewController.swift
// PhoneManager
//
// Created by 赵前 on 2025/4/19.
//
import Foundation
import SnapKit
class ContactIncompleteViewController : BaseViewController {
private var widthConstraint: Constraint?
var dataSourceModel : [ContactModel] = []
lazy var navView : ContactNavView = {
let view = ContactNavView()
return view
}()
lazy var selectAllButton : SelectAllButton = {
let view = SelectAllButton()
view.clipsToBounds = true
view.layer.cornerRadius = 16
return view
}()
lazy var emptyView : ContactNoIncomView = {
let view = ContactNoIncomView()
return view
}()
lazy var normalView : ContactNormalIncomView = {
let view = ContactNormalIncomView()
return view
}()
// 默认页面
func setDefaultPage(){
self.view.addSubview(self.emptyView)
self.emptyView.snp.makeConstraints { make in
make.top.equalTo(self.navView.snp.bottom).offset(0)
make.left.right.equalToSuperview()
make.bottom.equalToSuperview()
}
}
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() {
super.viewDidLoad()
self.view.backgroundColor = .white
self.view.addSubview(self.navView)
self.navView.snp.makeConstraints { make in
make.top.left.right.equalToSuperview()
make.height.equalTo(statusBarHeight + 44)
}
self.navView.addSubview(self.selectAllButton)
self.selectAllButton.snp.makeConstraints { make in
make.right.equalTo(-15 * RScreenW())
make.centerY.equalTo(self.navView.backButton.snp.centerY)
widthConstraint = make.width.equalTo(115).constraint
make.height.equalTo(32)
}
self.selectAllButton.tapCallback = { isSelected in
// 选择之后,更新宽度约束的常量值
if isSelected {
self.normalView.selectedContacts = self.normalView.dataSourceModel
self.widthConstraint?.update(offset: 131)
}else {
self.normalView.selectedContacts.removeAll()
self.widthConstraint?.update(offset: 115)
}
self.normalView.updateDeleteButtonStatus()
// 强制重新布局
UIView.animate(withDuration: 0.3) {
self.selectAllButton.setNeedsLayout()
self.selectAllButton.layoutIfNeeded()
}
self.normalView.tableView.reloadData()
}
self.setDefaultPage()
}
}
extension ContactIncompleteViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if self.dataSourceModel.count > 0 {
self.setNormalPage()
self.normalView.dataSourceModel = self.dataSourceModel
DispatchQueue.main.async {
self.normalView.sortContacts()
self.normalView.subTitleLabel.text = "\(self.dataSourceModel.count) Contacts"
self.normalView.tableView.reloadData()
}
}else{
self.setDefaultPage()
}
}
}
//
// ContactViewController.swift
// PhoneManager
//
// Created by edy on 2025/4/18.
//
import Foundation
import Contacts
class ContactViewController : BaseViewController {
var dataSourceModel : ContactModuleModel = ContactModuleModel(duplicates: [], incompleteContacts: [], backups: [], allContacts: [])
lazy var navView : ContactNavView = {
let view = ContactNavView()
return view
}()
lazy var emptyView:ContactDefaultView = {
let view = ContactDefaultView()
return view
}()
lazy var moduleView:ContactModuleView = {
let view = ContactModuleView()
return view
}()
override func viewDidLoad() {
self.view.backgroundColor = .white
self.view.addSubview(self.navView)
self.navView.snp.makeConstraints { make in
make.top.left.right.equalToSuperview()
make.height.equalTo(statusBarHeight + 44)
}
}
func setDefaultPage(){
self.view.addSubview(self.emptyView)
self.emptyView.snp.makeConstraints { make in
make.top.equalTo(self.navView.snp.bottom).offset(0)
make.left.right.equalToSuperview()
make.bottom.equalToSuperview()
}
}
func setNormalPage(){
self.emptyView.removeFromSuperview()
self.view.addSubview(self.moduleView)
self.moduleView.snp.makeConstraints { make in
make.top.equalTo(self.navView.snp.bottom).offset(0)
make.left.right.equalToSuperview()
make.bottom.equalToSuperview()
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// 请求联系人权限
self.setDefaultPage()
requestContactsPermission { permission in
DispatchQueue.main.async {
if permission {
// 有权限
self.updateModuleData()
self.setNormalPage()
// 初始化数据
self.initContatsInfo()
}
}
}
}
}
extension ContactViewController{
/// 更新模块显示的数据
func updateModuleData() {
var moduleViewData : [ModuleDataModel] = []
let model1 = ModuleDataModel.init(titleText: "Duplicates", subTitleText:"\(String(describing: self.dataSourceModel.duplicates.count)) Duplicates")
let model2 = ModuleDataModel.init(titleText: "Incomplete Contacts", subTitleText:"\(String(describing: self.dataSourceModel.incompleteContacts.count)) Incomplete Contacts")
let model3 = ModuleDataModel.init(titleText: "Backups", subTitleText:"\(String(describing: self.dataSourceModel.backups.count)) Backups")
let model4 = ModuleDataModel.init(titleText: "All Contacts", subTitleText:"\(String(describing: self.dataSourceModel.allContacts.count)) Contacts")
moduleViewData.append(model1)
moduleViewData.append(model2)
moduleViewData.append(model3)
moduleViewData.append(model4)
self.moduleView.moduleViewData = moduleViewData
self.moduleView.dataSourceModel = self.dataSourceModel
}
/// 初始化联系人数据
func initContatsInfo() {
let store = CNContactStore()
let keysToFetch = [
CNContactGivenNameKey as CNKeyDescriptor,
CNContactFamilyNameKey as CNKeyDescriptor,
CNContactPhoneNumbersKey as CNKeyDescriptor
]
do {
let request = CNContactFetchRequest(keysToFetch: keysToFetch)
// 创建数组
var incompleteContacts : [ContactModel] = []
var allContacts : [ContactModel] = []
try store.enumerateContacts(with: request) { contact, stop in
let givenName = contact.givenName
let familyName = contact.familyName
let fullName = "\(familyName) \(givenName)"
let phoneNumbers = contact.phoneNumbers.map { $0.value.stringValue }
let model = ContactModel.init(name: fullName, phoneNumber: phoneNumbers,identifier: contact.identifier)
if fullName.isEmpty || phoneNumbers.count <= 0 {
incompleteContacts.append(model)
}
allContacts.append(model)
self.dataSourceModel = ContactModuleModel.init(duplicates: [], incompleteContacts: incompleteContacts, backups: [], allContacts: allContacts)
}
DispatchQueue.main.async {
self.updateModuleData()
self.moduleView.tableView.reloadData()
}
} catch {
DispatchQueue.main.async {
print("获取联系人信息时发生错误: \(error)")
}
}
// 获取备份数据
let vm = BackupViewModel()
vm.retrieveContactsFromBackup { model, error in
if let error = error {
Print("请求备份数据失败,\(error.localizedDescription)")
return
}
DispatchQueue.main.async {
self.dataSourceModel.backups = model!
self.updateModuleData()
self.moduleView.tableView.reloadData()
}
}
// fixme:获取重复数据
}
func requestContactsPermission(completion: @escaping (Bool) -> Void) {
let store = CNContactStore()
switch CNContactStore.authorizationStatus(for: .contacts) {
case .authorized:
// 已经授权
completion(true)
case .denied, .restricted:
// 被拒绝或受限
completion(false)
case .notDetermined:
// 未确定,请求授权
store.requestAccess(for: .contacts) { granted, error in
if let error = error {
print("请求联系人权限时发生错误: \(error)")
}
completion(granted)
}
case .limited:
// 不分联系人权限
completion(true)
@unknown default:
completion(false)
}
}
}
//
// ContactsDupViewController.swift
// PhoneManager
//
// Created by 赵前 on 2025/4/19.
//
import Foundation
class ContactsDupViewController : BaseViewController {
var dataSourceModel : [[ContactModel]]?
lazy var navView : ContactNavView = {
let view = ContactNavView()
return view
}()
lazy var emptyView : ContactNoDupView = {
let view = ContactNoDupView()
return view
}()
// 默认页面
func setDefaultPage(){
self.view.addSubview(self.emptyView)
self.emptyView.snp.makeConstraints { make in
make.top.equalTo(self.navView.snp.bottom).offset(0)
make.left.right.equalToSuperview()
make.bottom.equalToSuperview()
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
self.view.addSubview(self.navView)
self.navView.snp.makeConstraints { make in
make.top.left.right.equalToSuperview()
make.height.equalTo(statusBarHeight + 44)
}
self.setDefaultPage()
}
}
//
// ContactModuleModel.swift
// PhoneManager
//
// Created by edy on 2025/4/18.
//
import Foundation
import Contacts
struct ContactModel : Codable,Equatable {
// 联系人名字
var name: String
// 联系人电话
var phoneNumber : [String]?
// 唯一id
var identifier : String
init(name: String, phoneNumber: [String]? = nil, identifier: String) {
self.name = name
self.phoneNumber = phoneNumber
self.identifier = identifier
}
}
extension ContactModel {
init?(contact: CNContact) {
let fullName = "\(contact.givenName) \(contact.familyName)".trimmingCharacters(in: .whitespaces)
let phoneNumbers = contact.phoneNumbers.map { $0.value.stringValue }
let uniqueID = contact.identifier
if fullName.isEmpty || phoneNumbers.isEmpty || uniqueID.isEmpty {
return nil
}
self.name = fullName
self.phoneNumber = phoneNumbers
self.identifier = uniqueID
}
}
struct ContactModuleModel {
var duplicates: [[ContactModel]]
var incompleteContacts: [ContactModel]
var backups: [BackupInfoModel]
var allContacts: [ContactModel]
init(duplicates: [[ContactModel]], incompleteContacts: [ContactModel], backups: [BackupInfoModel], allContacts: [ContactModel]) {
self.duplicates = duplicates
self.incompleteContacts = incompleteContacts
self.backups = backups
self.allContacts = allContacts
}
}
struct ModuleDataModel {
var titleText : String
var subTitleText : String
init(titleText: String, subTitleText: String) {
self.titleText = titleText
self.subTitleText = subTitleText
}
}
//
// ContactAllView.swift
// PhoneManager
//
// Created by 赵前 on 2025/4/19.
//
import Foundation
class ContactAllView : UIView {
var dataSourceModel : [ContactModel] = []
/// 分组后的联系人
private var sectionedContacts: [String: [ContactModel]] = [:]
/// 联系人首字母数组
private var sectionTitles: [String] = []
/// 选择的联系人
private var selectedContacts: [ContactModel] = []
var customIndexView: UIStackView!
var selectedIndex = 0
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(CustomContactAllViewTableViewCell.self, forCellReuseIdentifier: "CustomContactAllViewTableViewCell")
tableView.separatorStyle = .none
tableView.backgroundColor = .clear
tableView.showsVerticalScrollIndicator = false
if #available(iOS 15.0, *) {
tableView.sectionHeaderTopPadding = 0
}
return tableView
}()
lazy var deleteButton : DeleteButtonView = {
let deleteButton = DeleteButtonView()
// 设置删除按钮
deleteButton.layer.cornerRadius = 23
deleteButton.clipsToBounds = true
deleteButton.isHidden = true
return deleteButton
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(self.titleLabel)
self.addSubview(self.subTitleLabel)
self.addSubview(self.tableView)
self.addSubview(self.deleteButton)
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(-102 * RScreenH())
}
self.deleteButton.snp.makeConstraints { make in
make.top.equalTo(self.tableView.snp.bottom).offset(16 * RScreenH())
make.width.equalTo(345 * RScreenW())
make.height.equalTo(46)
make.centerX.equalToSuperview()
}
// 排序
self.sortContacts()
self.deleteButton.submitCallBack = {
self.alertWhenDeleteSomeOne()
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension ContactAllView : UITableViewDataSource,UITableViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int {
return sectionTitles.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let sectionTitle = sectionTitles[section]
return sectionedContacts[sectionTitle]?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomContactAllViewTableViewCell", for: indexPath) as! CustomContactAllViewTableViewCell
let sectionTitle = sectionTitles[indexPath.section]
let contact = sectionedContacts[sectionTitle]?[indexPath.row]
cell.model = contact
cell.nameLabel.text = contact?.name
if self.selectedContacts.contains(where: { $0.identifier == contact!.identifier }) {
cell.selectButton.isSelected = true
}else {
cell.selectButton.isSelected = false
}
cell.buttonSelectCallBack = {[weak self] model,selected in
guard let self else {return}
if selected {
self.selectedContacts.append(model)
}else{
self.selectedContacts.removeAll(where: { $0.identifier == model.identifier })
}
DispatchQueue.main.async {
// 判断button是否显示
if self.selectedContacts.count > 0 {
// 设置button的title
self.deleteButton.titleLabel.text = "Delete \(self.selectedContacts.count) Contact"
self.deleteButton.isHidden = false
}else{
self.deleteButton.isHidden = true
}
}
}
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 77 + 8 * RScreenH()
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let label = UILabel(frame: CGRect(x: 0 , y: 0, width: 345, height: 20))
label.text = sectionTitles[section]
label.textAlignment = .left
label.font = UIFont.systemFont(ofSize: 14, weight: .bold)
label.textColor = UIColor(red: 0.7, green: 0.7, blue: 0.7, alpha: 1)
return label
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 20
}
func setupCustomIndexView() {
customIndexView = UIStackView()
customIndexView.axis = .vertical
customIndexView.alignment = .center
customIndexView.distribution = .equalSpacing
customIndexView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(customIndexView)
self.customIndexView.snp.makeConstraints { make in
make.left.equalTo(self.tableView.snp.right).offset(0)
make.top.equalTo(self.subTitleLabel.snp.bottom).offset(77)
make.width.equalTo(15 * RScreenW())
make.height.equalTo(354)
}
for (index, section) in sectionTitles.enumerated() {
let label = UILabel()
label.text = section
label.font = UIFont.systemFont(ofSize: 10, weight: .bold)
label.textColor = index == selectedIndex ? .white : UIColor(red: 0.4, green: 0.4, blue: 0.4, alpha: 1)
label.textAlignment = .center
label.layer.cornerRadius = 7
label.layer.masksToBounds = true
label.backgroundColor = index == selectedIndex ? UIColor(red: 0, green: 0.51, blue: 1, alpha: 1) : .clear
NSLayoutConstraint.activate([
label.widthAnchor.constraint(equalToConstant: 14),
label.heightAnchor.constraint(equalToConstant: 14)
])
label.isUserInteractionEnabled = true
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(indexTapped(_:)))
label.addGestureRecognizer(tapGesture)
label.tag = index
customIndexView.addArrangedSubview(label)
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let visibleRect = CGRect(origin: tableView.contentOffset, size: tableView.bounds.size)
let visiblePoint = CGPoint(x: visibleRect.midX, y: visibleRect.minY)
if let visibleIndexPath = tableView.indexPathForRow(at: visiblePoint) {
let newIndex = visibleIndexPath.section
if newIndex != selectedIndex {
selectedIndex = newIndex
updateIndexStyles()
}
}
}
func updateIndexStyles() {
for (index, view) in customIndexView.arrangedSubviews.enumerated() {
if let label = view as? UILabel {
label.textColor = index == selectedIndex ? .white : UIColor(red: 0.4, green: 0.4, blue: 0.4, alpha: 1)
label.backgroundColor = index == selectedIndex ? UIColor(red: 0, green: 0.51, blue: 1, alpha: 1) : .clear
}
}
}
@objc func indexTapped(_ gesture: UIGestureRecognizer) {
if let label = gesture.view as? UILabel {
let index = label.tag
let indexPath = IndexPath(row: 0, section: index)
tableView.scrollToRow(at: indexPath, at: .top, animated: true)
selectedIndex = index
updateIndexStyles()
}
}
func alertWhenDeleteSomeOne() {
// 删除之前弹出是否需要备份
let alertVc = ContactBackupAlertView()
alertVc.frame = (self.responderViewController()?.view.bounds)!
self.responderViewController()?.view.addSubview(alertVc)
alertVc.sureCallBack = {[weak self] isSure in
guard let self else {return}
if isSure {
backupContactsByselect {
// 备份完成后删除
self.deleteContacts()
}
}else{
// 如果不备份,直接删除
self.deleteContacts()
}
}
}
func backupContactsByselect(success:@escaping()->Void){
// 开始备份联系人,备份完成提示
let vm = BackupViewModel()
vm.backupPartialContacts(self.selectedContacts) { finised, error in
if finised {
// 备份成功
success()
DispatchQueue.main.async {
let buAlertVc = ContactBackUpCompletedAlertView(frame: (self.responderViewController()?.view.bounds)!)
self.responderViewController()?.view.addSubview(buAlertVc)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
buAlertVc.removeFromSuperview()
}
}
}else{
Print("备份失败")
}
}
}
func deleteContacts(){
// 删除逻辑
for contact in selectedContacts {
if let index = self.dataSourceModel.firstIndex(of: contact) {
self.dataSourceModel.remove(at: index)
}
for (key, var sectionContacts) in sectionedContacts {
if let index = sectionContacts.firstIndex(of: contact) {
sectionContacts.remove(at: index)
sectionedContacts[key] = sectionContacts
}
}
}
selectedContacts.removeAll()
sortContacts()
self.tableView.reloadData()
// fixme: 需要从联系人列表中删除
}
/// 给联系人分组排序
func sortContacts() {
sectionedContacts.removeAll()
for contact in self.dataSourceModel {
let firstLetter = pinyinFirstLetter(contact.name).uppercased()
if sectionedContacts[firstLetter] == nil {
sectionedContacts[firstLetter] = []
}
sectionedContacts[firstLetter]?.append(contact)
}
sectionTitles = sectionedContacts.keys.sorted()
for key in sectionTitles {
sectionedContacts[key] = sectionedContacts[key]?.sorted {
return pinyinFirstLetter($0.name).uppercased() < pinyinFirstLetter($1.name).uppercased()
}
}
}
// 获取拼音首字母
private func pinyinFirstLetter(_ string: String) -> String {
let mutableString = NSMutableString(string: string)
CFStringTransform(mutableString, nil, kCFStringTransformToLatin, false)
CFStringTransform(mutableString, nil, kCFStringTransformStripDiacritics, false)
var pinyin = mutableString as String
if pinyin.isEmpty {
return "#"
}
pinyin = pinyin.replacingOccurrences(of: " ", with: "")
return pinyin.isEmpty ? "#" : String(pinyin.first!).uppercased()
}
}
//
// ContactNoAll.swift
// PhoneManager
//
// Created by 赵前 on 2025/4/19.
//
import Foundation
class ContactNoAllView : UIView {
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 = "0 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 tipLabel: UILabel = {
let label = UILabel()
label.text = "No Contacts"
label.font = UIFont.systemFont(ofSize: 24, weight: .bold)
label.textColor = UIColor(red: 0.2, green: 0.2, blue: 0.2, alpha: 1)
label.textAlignment = .center
return label
}()
lazy var subTipLabel: UILabel = {
let label = UILabel()
label.text = "Go add a contact!"
label.font = UIFont.systemFont(ofSize: 14, weight: .regular)
label.textColor = UIColor(red: 0.4, green: 0.4, blue: 0.4, alpha: 1)
label.textAlignment = .center
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(self.titleLabel)
self.addSubview(self.subTitleLabel)
self.addSubview(self.tipLabel)
self.addSubview(self.subTipLabel)
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.tipLabel.snp.makeConstraints { make in
make.top.equalTo(self.subTitleLabel.snp.bottom).offset(218 * RScreenH())
make.width.equalTo(315 * RScreenW())
make.height.equalTo(34)
make.centerX.equalToSuperview()
}
self.subTipLabel.snp.makeConstraints { make in
make.top.equalTo(self.tipLabel.snp.bottom).offset(8 * RScreenH())
make.width.equalTo(315 * RScreenW())
make.height.equalTo(20)
make.centerX.equalToSuperview()
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
//
// ContactBackUpView.swift
// PhoneManager
//
// Created by 赵前 on 2025/4/20.
//
import Foundation
class ContactBackUpNormalView : UIView {
var dataSourceModel : [BackupInfoModel] = []
var dataSourceAllModel : [ContactModel]?
lazy var titleLabel: UILabel = {
let label = UILabel()
label.text = "Backups"
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
}()
private lazy var addButon: UIButton = {
let button = UIButton()
button.addTarget(self, action: #selector(addBackupAction), for: .touchUpInside)
button.backgroundColor = .clear
button.setImage(UIImage(named: "btn_add_contact"), for: .normal)
return button
}()
// 懒加载 tableView
lazy var tableView: UITableView = {
let tableView = UITableView()
tableView.delegate = self
tableView.dataSource = self
tableView.register(ContactBackupCell.self, forCellReuseIdentifier: ContactBackupCell.identifier)
tableView.separatorStyle = .none
tableView.showsVerticalScrollIndicator = false
if #available(iOS 15.0, *) {
tableView.sectionHeaderTopPadding = 0
}
return tableView
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = .white
setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupUI() {
self.addSubview(self.titleLabel)
self.addSubview(self.addButon)
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.addButon.snp.makeConstraints { make in
make.right.equalToSuperview().offset(-15 * RScreenW())
make.centerY.equalTo(self.titleLabel.snp.centerY)
make.width.equalTo(28)
make.height.equalTo(28)
}
self.tableView.snp.makeConstraints { make in
make.top.equalTo(self.addButon.snp.bottom).offset(16)
make.left.equalToSuperview().offset(15 * RScreenW())
make.right.equalToSuperview().offset(-15 * RScreenW())
make.bottom.equalToSuperview()
}
}
}
extension ContactBackUpNormalView : UITableViewDelegate,UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func numberOfSections(in tableView: UITableView) -> Int {
return dataSourceModel.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 74
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let view = UIView()
view.backgroundColor = .clear
return view
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 12
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: ContactBackupCell.identifier, for: indexPath) as! ContactBackupCell
let backupInfo = dataSourceModel[indexPath.section]
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd MMM yyyy"
let dateString = dateFormatter.string(from: backupInfo.backupTime)
dateFormatter.dateFormat = "HH:mm"
let timeString = dateFormatter.string(from: backupInfo.backupTime)
cell.configure(title: dateString, subtitle: "\(backupInfo.contactCount) Contacts", time: timeString)
return cell
}
func deleteBackupDataFromMemory(){
let vm = BackupViewModel()
vm.saveModel(model: self.dataSourceModel) { finished, error in
if let error = error {
Print("删除失败,\(error.localizedDescription)")
}
DispatchQueue.main.async {
// 弹框提示成功
let buAlertVc = ContactBackUpDeleteCompletedAlertView(frame: (self.responderViewController()?.view.bounds)!)
self.responderViewController()?.view.addSubview(buAlertVc)
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
buAlertVc.removeFromSuperview()
}
}
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let vc = ContactBackupDetailViewController()
vc.dataSourceModel = self.dataSourceModel[indexPath.section].contacts
vc.subTitleLabel.text = "\(self.dataSourceModel[indexPath.section].contacts.count) Contacts"
vc.sortContacts()
vc.setupCustomIndexView()
self.responderViewController()?.navigationController?.pushViewController(vc, animated: true)
}
@objc(tableView:trailingSwipeActionsConfigurationForRowAtIndexPath:) func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(style: .destructive, title: nil) { [weak self] (action, view, completionHandler) in
let alertVc = ContactDeleteAlertView()
alertVc.frame = (self?.responderViewController()?.view.bounds)!
self?.responderViewController()?.view.addSubview(alertVc)
alertVc.sureCallBack = { isSure in
self?.dataSourceModel.remove(at: indexPath.section)
// 删除整个部分而不是特定行
self?.tableView.deleteSections(IndexSet(integer: indexPath.section), with: .automatic)
// 删除存储的数据
self?.deleteBackupDataFromMemory()
}
completionHandler(true)
}
deleteAction.backgroundColor = UIColor(red: 0.92, green: 0.27, blue: 0.27, alpha: 1)
deleteAction.image = UIImage(named: "ic_delete_email")
let configuration = UISwipeActionsConfiguration(actions: [deleteAction])
return configuration
}
@objc func addBackupAction() {
let vm = BackupViewModel()
vm.backupAllContacts(self.dataSourceAllModel ?? []) { finished, error in
DispatchQueue.main.async {
let buAlertVc = ContactBackUpCompletedAlertView(frame: (self.responderViewController()?.view.bounds)!)
self.responderViewController()?.view.addSubview(buAlertVc)
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
buAlertVc.removeFromSuperview()
}
// 再次请求数据 重新刷新页面
self.updateData()
}
}
}
func updateData(){
let vm = BackupViewModel()
vm.retrieveContactsFromBackup { model, error in
if let error = error {
Print("更新数据失败\(error.localizedDescription)")
}
DispatchQueue.main.async {
self.dataSourceModel = model!
self.tableView.reloadData()
}
}
}
}
class ContactBackupCell: UITableViewCell {
static let identifier = "ContactBackupCell"
// 懒加载 UI 元素
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = UIFont.boldSystemFont(ofSize: 16)
label.textColor = UIColor(red: 0.2, green: 0.2, blue: 0.2, alpha: 1)
return label
}()
private lazy var subtitleLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 14, weight: .regular)
label.textColor = UIColor(red: 0.4, green: 0.4, blue: 0.4, alpha: 1)
return label
}()
private lazy var timeLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 12, weight: .regular)
label.textColor = UIColor(red: 0.7, green: 0.7, blue: 0.7, alpha: 1)
label.textAlignment = .right
return label
}()
lazy var moreImageView: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "icon_left_setting_grey")
imageView.contentMode = .scaleAspectFit
return imageView
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.backgroundColor = UIColor(red: 0.95, green: 0.96, blue: 0.99, alpha: 1)
self.clipsToBounds = true
self.layer.cornerRadius = 12
self.selectionStyle = .none
setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupUI() {
contentView.addSubview(titleLabel)
contentView.addSubview(subtitleLabel)
contentView.addSubview(timeLabel)
contentView.addSubview(moreImageView)
titleLabel.snp.makeConstraints { make in
make.top.left.equalToSuperview().offset(16)
make.width.equalTo(200)
make.height.equalTo(22)
}
subtitleLabel.snp.makeConstraints { make in
make.top.equalTo(self.titleLabel.snp.bottom).offset(0)
make.left.equalToSuperview().offset(16)
make.width.equalTo(200)
make.height.equalTo(22)
}
moreImageView.snp.makeConstraints { make in
make.top.equalToSuperview().offset(16)
make.right.equalToSuperview().offset(-16)
make.width.equalTo(20)
make.height.equalTo(20)
}
timeLabel.snp.makeConstraints { make in
make.right.equalToSuperview().offset(-16)
make.width.equalTo(40)
make.height.equalTo(17)
make.centerY.equalTo(self.subtitleLabel.snp.centerY)
}
}
func configure(title: String, subtitle: String, time: String) {
titleLabel.text = title
subtitleLabel.text = subtitle
timeLabel.text = time
}
}
//
// ContactNoBackUpView.swift
// PhoneManager
//
// Created by 赵前 on 2025/4/19.
//
import Foundation
class ContactNoBackUpView : UIView {
var newBackupCallback : ()->Void = {}
lazy var titleLabel: UILabel = {
let label = UILabel()
label.text = "Backups"
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 imageView: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "btn_add_contact")
imageView.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer()
tap.addTarget(self, action: #selector(newBackup))
imageView.addGestureRecognizer(tap)
return imageView
}()
lazy var tipLabel: UILabel = {
let label = UILabel()
label.text = "No Backup"
label.font = UIFont.systemFont(ofSize: 24, weight: .bold)
label.textColor = UIColor(red: 0.2, green: 0.2, blue: 0.2, alpha: 1)
label.textAlignment = .center
return label
}()
lazy var subTipLabel: UILabel = {
let label = UILabel()
label.text = "You can back up your contacts."
label.font = UIFont.systemFont(ofSize: 14, weight: .regular)
label.textColor = UIColor(red: 0.4, green: 0.4, blue: 0.4, alpha: 1)
label.textAlignment = .center
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(self.titleLabel)
self.addSubview(self.imageView)
self.addSubview(self.tipLabel)
self.addSubview(self.subTipLabel)
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.imageView.snp.makeConstraints { make in
make.top.equalTo(self.titleLabel.snp.bottom).offset(170 * RScreenH())
make.width.height.equalTo(58)
make.centerX.equalToSuperview()
}
self.tipLabel.snp.makeConstraints { make in
make.top.equalTo(self.imageView.snp.bottom).offset(12 * RScreenH())
make.width.equalTo(315 * RScreenW())
make.height.equalTo(34)
make.centerX.equalToSuperview()
}
self.subTipLabel.snp.makeConstraints { make in
make.top.equalTo(self.tipLabel.snp.bottom).offset(8 * RScreenH())
make.width.equalTo(315 * RScreenW())
make.height.equalTo(20)
make.centerX.equalToSuperview()
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension ContactNoBackUpView {
@objc func newBackup() {
self.newBackupCallback()
}
}
//
// BackupViewModel.swift
// PhoneManager
//
// Created by 赵前 on 2025/4/20.
//
import Foundation
import Contacts
struct BackupInfoModel:Codable {
let backupTime: Date
private(set) var contactCount: Int
let contacts: [ContactModel]
// 初始化方法
init(backupTime: Date = Date(), contacts: [ContactModel]) {
self.backupTime = backupTime
self.contactCount = contacts.count
self.contacts = contacts
}
// 手动实现 Decodable 协议的 init(from:) 方法
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ" // 与 ISO8601 格式匹配
if let backupTimeString = try? container.decode(String.self, forKey:.backupTime),
let backupTime = dateFormatter.date(from: backupTimeString) {
self.backupTime = backupTime
} else {
self.backupTime = try container.decode(Date.self, forKey:.backupTime)
}
self.contacts = try container.decode([ContactModel].self, forKey:.contacts)
self.contactCount = contacts.count
}
private enum CodingKeys: String, CodingKey {
case backupTime
case contactCount
case contacts
}
}
class BackupViewModel {
// 备份部分联系人到沙盒
func backupPartialContacts(_ customContacts: [ContactModel], completion: @escaping (Bool, Error?) -> Void) {
let newBackupInfo = BackupInfoModel(contacts: customContacts)
guard let documentsDirectory = FileManager.default.urls(for:.documentDirectory, in:.userDomainMask).first else {
completion(false, NSError(domain: "ContactBackupError", code: 1, userInfo: [NSLocalizedDescriptionKey: "无法获取沙盒目录"]))
return
}
let backupFileURL = documentsDirectory.appendingPathComponent("partial_contacts_backup.json")
var existingBackups: [BackupInfoModel] = []
if let data = try? Data(contentsOf: backupFileURL) {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
do {
existingBackups = try decoder.decode([BackupInfoModel].self, from: data)
} catch {
print("Failed to decode existing backups: \(error)")
}
}
existingBackups.append(newBackupInfo)
do {
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
let jsonData = try encoder.encode(existingBackups)
try jsonData.write(to: backupFileURL)
completion(true, nil)
} catch {
completion(false, error)
}
}
// 备份所有联系人到沙盒
func backupAllContacts(_ customContacts: [ContactModel], completion: @escaping (Bool, Error?) -> Void) {
backupPartialContacts(customContacts) { success, error in
completion(success, error)
}
}
// 从沙盒中取出联系人并转换为自定义模型
func retrieveContactsFromBackup(completion: @escaping ([BackupInfoModel]?, Error?) -> Void) {
guard let documentsDirectory = FileManager.default.urls(for:.documentDirectory, in:.userDomainMask).first else {
completion(nil, NSError(domain: "ContactBackupError", code: 1, userInfo: [NSLocalizedDescriptionKey: "无法获取沙盒目录"]))
return
}
let backupFileURL = documentsDirectory.appendingPathComponent("partial_contacts_backup.json")
do {
let jsonData = try Data(contentsOf: backupFileURL)
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
// 尝试解码为 BackupInfoModel 数组
let backupInfos = try decoder.decode([BackupInfoModel].self, from: jsonData)
completion(backupInfos, nil)
} catch {
Print(error.localizedDescription)
completion(nil, error)
}
}
func saveModel(model:[BackupInfoModel],completion: @escaping (Bool, Error?) -> Void){
guard let documentsDirectory = FileManager.default.urls(for:.documentDirectory, in:.userDomainMask).first else {
completion(false, NSError(domain: "ContactBackupError", code: 1, userInfo: [NSLocalizedDescriptionKey: "无法获取沙盒目录"]))
return
}
let backupFileURL = documentsDirectory.appendingPathComponent("partial_contacts_backup.json")
do {
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
let jsonData = try encoder.encode(model)
try jsonData.write(to: backupFileURL)
completion(true, nil)
} catch {
completion(false, error)
}
}
}
//
// CustomContactAllViewTableViewCell.swift
// PhoneManager
//
// Created by 赵前 on 2025/4/19.
//
import Foundation
class CustomContactAllViewTableViewCell : UITableViewCell {
var model : ContactModel?
var buttonSelectCallBack : (ContactModel,Bool)->Void = {model,selected in}
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 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.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.centerY.equalToSuperview()
make.width.equalTo(210)
make.height.equalTo(22)
}
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 CustomContactAllViewTableViewCell {
@objc private func selectContact(_ sender: UIButton) {
sender.isSelected = !sender.isSelected
buttonSelectCallBack(model!,sender.isSelected)
}
}
//
// CustomContactBacDetailTableViewCell.swift
// PhoneManager
//
// Created by 赵前 on 2025/4/20.
//
import Foundation
class CustomContactBacDetailTableViewCell : UITableViewCell {
var model : ContactModel?
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
}()
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.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.centerY.equalToSuperview()
make.width.equalTo(210)
make.height.equalTo(22)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
//
// ContactBacRestoreAlertView.swift
// PhoneManager
//
// Created by 赵前 on 2025/4/20.
//
import Foundation
class ContactBacRestoreAlertView : UIView {
var sureCallBack: ()->Void = {}
// 懒加载背景视图
private lazy var backgroundView: UIView = {
let view = UIView()
view.backgroundColor = UIColor.black.withAlphaComponent(0.5)
return view
}()
// 懒加载卡片视图
private lazy var cardView: UIView = {
let view = UIView()
view.backgroundColor = .white
view.layer.cornerRadius = 10
view.clipsToBounds = true
return view
}()
// 懒加载标题标签
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.text = "Are you sure?"
label.font = UIFont.systemFont(ofSize: 17, weight: .bold)
label.textColor = UIColor(red: 0, green: 0, blue: 0, alpha: 1)
label.textAlignment = .center
return label
}()
// 懒加载副标题标签
private lazy var subtitleLabel: UILabel = {
let label = UILabel()
label.text = "Your contacts will be removed from your iPhone and iCloud. This process cannot be reversed"
label.font = UIFont.systemFont(ofSize: 13, weight: .regular)
label.textColor = UIColor(red: 0, green: 0, blue: 0, alpha: 1)
label.numberOfLines = 0
label.textAlignment = .center
return label
}()
// 懒加载取消按钮
private lazy var cancelButton: UIButton = {
let button = UIButton()
button.setTitle("Cancel", for: .normal)
button.backgroundColor = .clear
button.setTitleColor(UIColor(red: 0, green: 0.48, blue: 1, alpha: 1), for: .normal)
button.layer.cornerRadius = 5
button.titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: .regular)
button.addTarget(self, action: #selector(dismissAlert), for: .touchUpInside)
return button
}()
// 懒加载确认按钮
private lazy var yesButton: UIButton = {
let button = UIButton()
button.setTitle("Yes", for: .normal)
button.backgroundColor = .clear
button.setTitleColor(UIColor(red: 0, green: 0.48, blue: 1, alpha: 1), for: .normal)
button.layer.cornerRadius = 5
button.titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: .regular)
button.addTarget(self, action: #selector(sureAlert), for: .touchUpInside)
return button
}()
// 懒加载顶部边线
private lazy var topBorder: UIView = {
let view = UIView()
view.backgroundColor = UIColor(red: 0.24, green: 0.24, blue: 0.26, alpha: 0.3600)
return view
}()
// 懒加载中间分割线
private lazy var middleBorder: UIView = {
let view = UIView()
view.backgroundColor = UIColor(red: 0.24, green: 0.24, blue: 0.26, alpha: 0.3600)
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.5000)
setupViews()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupViews() {
self.addSubview(backgroundView)
backgroundView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
self.addSubview(cardView)
cardView.snp.makeConstraints { make in
make.center.equalToSuperview()
make.width.equalTo(270)
make.height.equalTo(154)
}
cardView.addSubview(titleLabel)
titleLabel.snp.makeConstraints { make in
make.top.equalToSuperview().offset(16)
make.left.right.equalToSuperview().inset(16)
make.height.equalTo(22)
}
cardView.addSubview(subtitleLabel)
subtitleLabel.snp.makeConstraints { make in
make.top.equalTo(titleLabel.snp.bottom).offset(2)
make.left.right.equalToSuperview().inset(16)
make.height.equalTo(54)
}
cardView.addSubview(cancelButton)
cancelButton.snp.makeConstraints { make in
make.left.equalToSuperview()
make.bottom.equalToSuperview()
make.width.equalTo(cardView.snp.width).multipliedBy(0.5)
make.height.equalTo(44)
}
cardView.addSubview(yesButton)
yesButton.snp.makeConstraints { make in
make.right.equalToSuperview()
make.bottom.equalToSuperview()
make.width.equalTo(cardView.snp.width).multipliedBy(0.5)
make.height.equalTo(44)
}
cardView.addSubview(topBorder)
topBorder.snp.makeConstraints { make in
make.top.equalTo(cancelButton.snp.top)
make.left.right.equalTo(cardView)
make.height.equalTo(0.5)
}
cardView.addSubview(middleBorder)
middleBorder.snp.makeConstraints { make in
make.centerX.equalTo(cardView)
make.top.equalTo(cancelButton.snp.top)
make.bottom.equalTo(cancelButton.snp.bottom)
make.width.equalTo(0.5)
}
}
@objc private func dismissAlert() {
self.removeFromSuperview()
}
@objc private func sureAlert() {
self.removeFromSuperview()
sureCallBack()
}
}
//
// ContactBackUpCompletedAlertView.swift
// PhoneManager
//
// Created by 赵前 on 2025/4/20.
//
import Foundation
class ContactBackUpCompletedAlertView : UIView {
var sureCallBack: ()->Void = {}
// 懒加载背景视图
private lazy var backgroundView: UIView = {
let view = UIView()
view.backgroundColor = UIColor.black.withAlphaComponent(0.5)
return view
}()
// 懒加载卡片视图
private lazy var cardView: UIView = {
let view = UIView()
view.backgroundColor = .white
view.layer.cornerRadius = 10
view.clipsToBounds = true
return view
}()
private lazy var imageView: UIImageView = {
let view = UIImageView()
view.backgroundColor = .clear
view.image = UIImage(named: "ic_ok_duolicates")
return view
}()
// 懒加载标题标签
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.text = "Backing up is completed"
label.font = UIFont.systemFont(ofSize: 14, weight: .regular)
label.textColor = UIColor(red: 0.4, green: 0.4, blue: 0.4, alpha: 1)
label.textAlignment = .center
label.numberOfLines = 0
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.5000)
setupViews()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupViews() {
self.addSubview(backgroundView)
backgroundView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
self.addSubview(cardView)
cardView.snp.makeConstraints { make in
make.center.equalToSuperview()
make.width.equalTo(175)
make.height.equalTo(115)
}
cardView.addSubview(imageView)
imageView.snp.makeConstraints { make in
make.top.equalToSuperview().offset(20)
make.width.height.equalTo(35)
make.centerX.equalToSuperview()
}
cardView.addSubview(titleLabel)
titleLabel.snp.makeConstraints { make in
make.top.equalTo(self.imageView.snp.bottom).offset(0)
make.left.right.equalToSuperview().inset(20)
make.height.equalTo(40)
}
}
}
//
// ContactBackUpDeleteCompletedAlertView.swift
// PhoneManager
//
// Created by 赵前 on 2025/4/20.
//
import Foundation
class ContactBackUpDeleteCompletedAlertView : UIView {
var sureCallBack: ()->Void = {}
// 懒加载背景视图
private lazy var backgroundView: UIView = {
let view = UIView()
view.backgroundColor = UIColor.black.withAlphaComponent(0.5)
return view
}()
// 懒加载卡片视图
private lazy var cardView: UIView = {
let view = UIView()
view.backgroundColor = .white
view.layer.cornerRadius = 10
view.clipsToBounds = true
return view
}()
private lazy var imageView: UIImageView = {
let view = UIImageView()
view.backgroundColor = .clear
view.image = UIImage(named: "ic_ok_duolicates")
return view
}()
// 懒加载标题标签
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.text = "Done!"
label.font = UIFont.systemFont(ofSize: 14, weight: .regular)
label.textColor = UIColor(red: 0.4, green: 0.4, blue: 0.4, alpha: 1)
label.textAlignment = .center
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.5000)
setupViews()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupViews() {
self.addSubview(backgroundView)
backgroundView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
self.addSubview(cardView)
cardView.snp.makeConstraints { make in
make.center.equalToSuperview()
make.width.equalTo(175)
make.height.equalTo(95)
}
cardView.addSubview(imageView)
imageView.snp.makeConstraints { make in
make.top.equalToSuperview().offset(20)
make.width.height.equalTo(35)
make.centerX.equalToSuperview()
}
cardView.addSubview(titleLabel)
titleLabel.snp.makeConstraints { make in
make.top.equalTo(self.imageView.snp.bottom).offset(0)
make.left.right.equalToSuperview().inset(20)
make.height.equalTo(20)
}
}
}
//
// ContactBackupAlertView.swift
// PhoneManager
//
// Created by 赵前 on 2025/4/20.
//
import Foundation
class ContactBackupAlertView : UIView {
var sureCallBack: (Bool)->Void = {isSure in }
// 懒加载背景视图
private lazy var backgroundView: UIView = {
let view = UIView()
view.backgroundColor = UIColor.black.withAlphaComponent(0.5)
return view
}()
// 懒加载卡片视图
private lazy var cardView: UIView = {
let view = UIView()
view.backgroundColor = .white
view.layer.cornerRadius = 10
view.clipsToBounds = true
return view
}()
// 懒加载标题标签
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.text = "Would you like to back up your contacts before making any changes?"
label.font = UIFont.systemFont(ofSize: 20, weight: .bold)
label.textColor = UIColor(red: 0, green: 0, blue: 0, alpha: 1)
label.textAlignment = .center
label.numberOfLines = 0
return label
}()
// 懒加载副标题标签
private lazy var subtitleLabel: UILabel = {
let label = UILabel()
label.text = "Changes cannot be reversed if you do not back up your contacts"
label.font = UIFont.systemFont(ofSize: 14, weight: .regular)
label.textColor = UIColor(red: 0, green: 0, blue: 0, alpha: 1)
label.numberOfLines = 0
label.textAlignment = .center
return label
}()
// 懒加载取消按钮
private lazy var cancelButton: UIButton = {
let button = UIButton()
button.setTitle("No, Thanks", for: .normal)
button.backgroundColor = .clear
button.setTitleColor(UIColor(red: 0.4, green: 0.4, blue: 0.4, alpha: 1), for: .normal)
button.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .bold)
button.layer.cornerRadius = 23
button.addTarget(self, action: #selector(dismissAlert), for: .touchUpInside)
return button
}()
// 懒加载确认按钮
private lazy var yesButton: UIButton = {
let button = UIButton()
button.setTitle("Back up", for: .normal)
button.backgroundColor = UIColor(red: 0, green: 0.51, blue: 1, alpha: 1)
button.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .bold)
button.setTitleColor(.white, for: .normal)
button.layer.cornerRadius = 23
button.addTarget(self, action: #selector(sureAlert), for: .touchUpInside)
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.5000)
setupViews()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupViews() {
self.addSubview(backgroundView)
backgroundView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
self.addSubview(cardView)
cardView.snp.makeConstraints { make in
make.center.equalToSuperview()
make.width.equalTo(345)
make.height.equalTo(288)
}
cardView.addSubview(titleLabel)
titleLabel.snp.makeConstraints { make in
make.top.equalToSuperview().offset(28)
make.left.right.equalToSuperview().inset(20)
make.height.equalTo(84)
}
cardView.addSubview(subtitleLabel)
subtitleLabel.snp.makeConstraints { make in
make.top.equalTo(titleLabel.snp.bottom).offset(8)
make.left.right.equalToSuperview().inset(20)
make.height.equalTo(40)
}
cardView.addSubview(yesButton)
yesButton.snp.makeConstraints { make in
make.top.equalTo(subtitleLabel.snp.bottom).offset(16)
make.left.right.equalToSuperview().inset(20)
make.height.equalTo(46)
}
cardView.addSubview(cancelButton)
cancelButton.snp.makeConstraints { make in
make.top.equalTo(yesButton.snp.bottom).offset(4)
make.left.right.equalToSuperview().inset(20)
make.height.equalTo(46)
}
}
@objc private func dismissAlert() {
self.removeFromSuperview()
sureCallBack(false)
}
@objc private func sureAlert() {
self.removeFromSuperview()
sureCallBack(true)
}
}
//
// ContactDeleteAlertViewController.swift
// PhoneManager
//
// Created by 赵前 on 2025/4/19.
//
import Foundation
class ContactDeleteAlertView : UIView {
var sureCallBack: (Bool)->Void = {isSure in}
// 懒加载背景视图
private lazy var backgroundView: UIView = {
let view = UIView()
view.backgroundColor = UIColor.black.withAlphaComponent(0.5)
return view
}()
// 懒加载卡片视图
private lazy var cardView: UIView = {
let view = UIView()
view.backgroundColor = .white
view.layer.cornerRadius = 10
view.clipsToBounds = true
return view
}()
// 懒加载标题标签
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.text = "Are you sure?"
label.font = UIFont.systemFont(ofSize: 17, weight: .bold)
label.textColor = UIColor(red: 0, green: 0, blue: 0, alpha: 1)
label.textAlignment = .center
return label
}()
// 懒加载副标题标签
private lazy var subtitleLabel: UILabel = {
let label = UILabel()
label.text = "Your contacts will be removed from your iPhone and iCloud. This process cannot be reversed"
label.font = UIFont.systemFont(ofSize: 13, weight: .regular)
label.textColor = UIColor(red: 0, green: 0, blue: 0, alpha: 1)
label.numberOfLines = 0
label.textAlignment = .center
return label
}()
// 懒加载取消按钮
private lazy var cancelButton: UIButton = {
let button = UIButton()
button.setTitle("Cancel", for: .normal)
button.backgroundColor = .clear
button.setTitleColor(UIColor(red: 0, green: 0.48, blue: 1, alpha: 1), for: .normal)
button.layer.cornerRadius = 5
button.titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: .regular)
button.addTarget(self, action: #selector(dismissAlert), for: .touchUpInside)
return button
}()
// 懒加载确认按钮
private lazy var yesButton: UIButton = {
let button = UIButton()
button.setTitle("Yes", for: .normal)
button.backgroundColor = .clear
button.setTitleColor(UIColor(red: 0, green: 0.48, blue: 1, alpha: 1), for: .normal)
button.layer.cornerRadius = 5
button.titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: .regular)
button.addTarget(self, action: #selector(sureAlert), for: .touchUpInside)
return button
}()
// 懒加载顶部边线
private lazy var topBorder: UIView = {
let view = UIView()
view.backgroundColor = UIColor(red: 0.24, green: 0.24, blue: 0.26, alpha: 0.3600)
return view
}()
// 懒加载中间分割线
private lazy var middleBorder: UIView = {
let view = UIView()
view.backgroundColor = UIColor(red: 0.24, green: 0.24, blue: 0.26, alpha: 0.3600)
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.5000)
setupViews()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupViews() {
self.addSubview(backgroundView)
backgroundView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
self.addSubview(cardView)
cardView.snp.makeConstraints { make in
make.center.equalToSuperview()
make.width.equalTo(270)
make.height.equalTo(154)
}
cardView.addSubview(titleLabel)
titleLabel.snp.makeConstraints { make in
make.top.equalToSuperview().offset(16)
make.left.right.equalToSuperview().inset(16)
make.height.equalTo(22)
}
cardView.addSubview(subtitleLabel)
subtitleLabel.snp.makeConstraints { make in
make.top.equalTo(titleLabel.snp.bottom).offset(2)
make.left.right.equalToSuperview().inset(16)
make.height.equalTo(54)
}
cardView.addSubview(cancelButton)
cancelButton.snp.makeConstraints { make in
make.left.equalToSuperview()
make.bottom.equalToSuperview()
make.width.equalTo(cardView.snp.width).multipliedBy(0.5)
make.height.equalTo(44)
}
cardView.addSubview(yesButton)
yesButton.snp.makeConstraints { make in
make.right.equalToSuperview()
make.bottom.equalToSuperview()
make.width.equalTo(cardView.snp.width).multipliedBy(0.5)
make.height.equalTo(44)
}
cardView.addSubview(topBorder)
topBorder.snp.makeConstraints { make in
make.top.equalTo(cancelButton.snp.top)
make.left.right.equalTo(cardView)
make.height.equalTo(0.5)
}
cardView.addSubview(middleBorder)
middleBorder.snp.makeConstraints { make in
make.centerX.equalTo(cardView)
make.top.equalTo(cancelButton.snp.top)
make.bottom.equalTo(cancelButton.snp.bottom)
make.width.equalTo(0.5)
}
}
@objc private func dismissAlert() {
self.removeFromSuperview()
}
@objc private func sureAlert() {
self.removeFromSuperview()
sureCallBack(true)
}
}
//
// ContactNavView.swift
// PhoneManager
//
// Created by edy on 2025/4/18.
//
import Foundation
class ContactNavView : UIView {
public var backButton:UIButton!
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupUI() {
backgroundColor = .white
backButton = UIButton()
backButton.setImage(UIImage(named: "nav_back"), for: .normal)
backButton.addTarget(self, action: #selector(backBtnClick), for: .touchUpInside)
self.addSubview(backButton)
backButton.snp.makeConstraints { make in
make.centerY.equalTo(navCenterY)
make.left.equalToSuperview().offset(marginLR)
make.width.height.equalTo(iconWH)
}
}
@objc private func backBtnClick() {
self.responderViewController()?.navigationController?.popViewController(animated: true)
}
}
//
// DeleteButtonView.swift
// PhoneManager
//
// Created by 赵前 on 2025/4/19.
//
import Foundation
class DeleteButtonView : UIView {
var submitCallBack : (()->Void) = {}
lazy var imageView : UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "ic_delete_duplicates")
return imageView
}()
lazy var titleLabel : UILabel = {
let label = UILabel()
label.textAlignment = .left
label.textColor = UIColor(red: 1, green: 1, blue: 1, alpha: 1)
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.isUserInteractionEnabled = true
self.backgroundColor = UIColor(red: 0, green: 0.51, blue: 1, alpha: 1)
let tap = UITapGestureRecognizer()
tap.addTarget(self, action: #selector(submitAction))
self.addGestureRecognizer(tap)
self.addSubview(self.imageView)
self.addSubview(self.titleLabel)
self.imageView.snp.makeConstraints { make in
make.width.height.equalTo(20)
make.centerY.equalToSuperview()
make.left.equalToSuperview().offset(94 * RScreenW())
}
self.titleLabel.snp.makeConstraints { make in
make.left.equalTo(self.imageView.snp.right).offset(8 * RScreenW())
make.centerY.equalToSuperview()
make.height.equalTo(22)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension DeleteButtonView {
@objc func submitAction(){
submitCallBack()
}
}
//
// RestoreButtonView.swift
// PhoneManager
//
// Created by 赵前 on 2025/4/20.
//
import Foundation
class RestoreButtonView : UIView {
var submitCallBack : (()->Void) = {}
lazy var imageView : UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "ic_beifen")
return imageView
}()
lazy var titleLabel : UILabel = {
let label = UILabel()
label.textAlignment = .left
label.text = "Restore Backup"
label.textColor = UIColor(red: 1, green: 1, blue: 1, alpha: 1)
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.isUserInteractionEnabled = true
self.backgroundColor = UIColor(red: 0, green: 0.51, blue: 1, alpha: 1)
let tap = UITapGestureRecognizer()
tap.addTarget(self, action: #selector(restoreAction))
self.addGestureRecognizer(tap)
self.addSubview(self.imageView)
self.addSubview(self.titleLabel)
self.imageView.snp.makeConstraints { make in
make.width.height.equalTo(20)
make.centerY.equalToSuperview()
make.left.equalToSuperview().offset(94 * RScreenW())
}
self.titleLabel.snp.makeConstraints { make in
make.left.equalTo(self.imageView.snp.right).offset(8 * RScreenW())
make.centerY.equalToSuperview()
make.height.equalTo(22)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc func restoreAction(){
submitCallBack()
}
}
//
// SelectAllButton.swift
// PhoneManager
//
// Created by edy on 2025/4/21.
//
import Foundation
class SelectAllButton : UIView {
var tapCallback : (Bool)->Void = {selected in }
private let imageView = UIImageView()
private let label = UILabel()
private var isSelectedValue = false
var isSelected: Bool {
get {
return isSelectedValue
}
set {
isSelectedValue = newValue
updateText()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupView()
}
private func setupView() {
// 设置背景颜色
backgroundColor = UIColor(red: 0.95, green: 0.96, blue: 0.99, alpha: 1)
// 添加子视图
addSubview(imageView)
self.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer()
tap.addTarget(self, action: #selector(selectClick))
self.addGestureRecognizer(tap)
addSubview(label)
// 配置图片视图
imageView.contentMode = .scaleAspectFit
// 配置标签
label.font = UIFont.systemFont(ofSize: 14, weight: .bold)
label.textAlignment = .left
label.textColor = UIColor(red: 0.4, green: 0.4, blue: 0.4, alpha: 1)
// 使用 SnapKit 进行布局
imageView.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(6)
make.centerY.equalToSuperview()
make.width.height.equalTo(20)
}
label.snp.makeConstraints { make in
make.leading.equalTo(imageView.snp.trailing).offset(8)
make.centerY.equalToSuperview()
make.trailing.lessThanOrEqualToSuperview().offset(-12)
}
// 初始更新文本
updateText()
}
func setImage(_ image: UIImage?) {
imageView.image = image
}
private func updateText() {
imageView.image = isSelected ? UIImage(named: "ic_close_similar") : UIImage(named: "ic_check_similar")
label.textColor = isSelected ? UIColor(red: 0.4, green: 0.4, blue: 0.4, alpha: 1) : UIColor(red: 0, green: 0.51, blue: 1, alpha: 1)
label.text = isSelected ? "Deselect All" : "Select All"
// 强制布局更新以适应新文本
setNeedsLayout()
layoutIfNeeded()
}
@objc func selectClick(){
self.isSelected = !self.isSelected
tapCallback(self.isSelected)
}
}
//
// ContactNoDupView.swift
// PhoneManager
//
// Created by 赵前 on 2025/4/19.
//
import Foundation
class ContactNoDupView : UIView {
lazy var titleLabel: UILabel = {
let label = UILabel()
label.text = "Duplicates"
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 = "0 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 tipLabel: UILabel = {
let label = UILabel()
label.text = "No duplicate item"
label.font = UIFont.systemFont(ofSize: 24, weight: .bold)
label.textColor = UIColor(red: 0.2, green: 0.2, blue: 0.2, alpha: 1)
label.textAlignment = .center
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(self.titleLabel)
self.addSubview(self.subTitleLabel)
self.addSubview(self.tipLabel)
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.tipLabel.snp.makeConstraints { make in
make.top.equalTo(self.subTitleLabel.snp.bottom).offset(218 * RScreenH())
make.width.equalTo(315 * RScreenW())
make.height.equalTo(34)
make.centerX.equalToSuperview()
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
//
// ContactNoIncomView.swift
// PhoneManager
//
// Created by 赵前 on 2025/4/19.
//
import Foundation
class ContactNoIncomView : UIView {
lazy var titleLabel: UILabel = {
let label = UILabel()
label.text = "Incomplete 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 = "0 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 tipLabel: UILabel = {
let label = UILabel()
label.text = "No Contacts"
label.font = UIFont.systemFont(ofSize: 24, weight: .bold)
label.textColor = UIColor(red: 0.2, green: 0.2, blue: 0.2, alpha: 1)
label.textAlignment = .center
return label
}()
lazy var subTipLabel: UILabel = {
let label = UILabel()
label.text = "You can add new contacts from Contacts app in your phone."
label.font = UIFont.systemFont(ofSize: 14, weight: .regular)
label.textColor = UIColor(red: 0.4, green: 0.4, blue: 0.4, alpha: 1)
label.numberOfLines = 0
label.textAlignment = .center
return label
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(self.titleLabel)
self.addSubview(self.subTitleLabel)
self.addSubview(self.tipLabel)
self.addSubview(self.subTipLabel)
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.tipLabel.snp.makeConstraints { make in
make.top.equalTo(self.subTitleLabel.snp.bottom).offset(218 * RScreenH())
make.width.equalTo(315 * RScreenW())
make.height.equalTo(34)
make.centerX.equalToSuperview()
}
self.subTipLabel.snp.makeConstraints { make in
make.top.equalTo(self.tipLabel.snp.bottom).offset(8 * RScreenH())
make.width.equalTo(315 * RScreenW())
make.height.equalTo(40)
make.centerX.equalToSuperview()
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
//
// ContactNormalIncomView.swift
// PhoneManager
//
// Created by edy on 2025/4/21.
//
import Foundation
class ContactNormalIncomView : UIView {
var dataSourceModel : [ContactModel] = []
/// 分组后的联系人
private var sectionedContacts: [String: [ContactModel]] = [:]
/// 联系人首字母数组
private var sectionTitles: [String] = []
/// 选择的联系人
var selectedContacts: [ContactModel] = []
var selectedIndex = 0
lazy var titleLabel: UILabel = {
let label = UILabel()
label.text = "Incomplete 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(CustomContactAllViewTableViewCell.self, forCellReuseIdentifier: "CustomContactAllViewTableViewCell")
tableView.separatorStyle = .none
tableView.backgroundColor = .clear
tableView.showsVerticalScrollIndicator = false
if #available(iOS 15.0, *) {
tableView.sectionHeaderTopPadding = 0
}
return tableView
}()
lazy var deleteButton : DeleteButtonView = {
let deleteButton = DeleteButtonView()
// 设置删除按钮
deleteButton.layer.cornerRadius = 23
deleteButton.clipsToBounds = true
deleteButton.isHidden = true
return deleteButton
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(self.titleLabel)
self.addSubview(self.subTitleLabel)
self.addSubview(self.tableView)
self.addSubview(self.deleteButton)
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(-102 * RScreenH())
}
self.deleteButton.snp.makeConstraints { make in
make.top.equalTo(self.tableView.snp.bottom).offset(16 * RScreenH())
make.width.equalTo(345 * RScreenW())
make.height.equalTo(46)
make.centerX.equalToSuperview()
}
// 排序
self.sortContacts()
self.deleteButton.submitCallBack = {
self.alertWhenDeleteSomeOne()
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension ContactNormalIncomView : UITableViewDataSource,UITableViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int {
return sectionTitles.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let sectionTitle = sectionTitles[section]
return sectionedContacts[sectionTitle]?.count ?? 0
}
func updateDeleteButtonStatus() {
DispatchQueue.main.async {
// 判断button是否显示
if self.selectedContacts.count > 0 {
// 设置button的title
self.deleteButton.titleLabel.text = "Delete \(self.selectedContacts.count) Contact"
self.deleteButton.isHidden = false
}else{
self.deleteButton.isHidden = true
}
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomContactAllViewTableViewCell", for: indexPath) as! CustomContactAllViewTableViewCell
let sectionTitle = sectionTitles[indexPath.section]
let contact = sectionedContacts[sectionTitle]?[indexPath.row]
cell.model = contact
cell.nameLabel.text = contact?.name
if self.selectedContacts.contains(where: { $0.identifier == contact!.identifier }) {
cell.selectButton.isSelected = true
}else {
cell.selectButton.isSelected = false
}
cell.buttonSelectCallBack = {[weak self] model,selected in
guard let self else {return}
if selected {
self.selectedContacts.append(model)
}else{
self.selectedContacts.removeAll(where: { $0.identifier == model.identifier })
}
updateDeleteButtonStatus()
}
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 77 + 8 * RScreenH()
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let label = UILabel(frame: CGRect(x: 0 , y: 0, width: 345, height: 20))
label.text = sectionTitles[section]
label.textAlignment = .left
label.font = UIFont.systemFont(ofSize: 14, weight: .bold)
label.textColor = UIColor(red: 0.7, green: 0.7, blue: 0.7, alpha: 1)
return label
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 20
}
func alertWhenDeleteSomeOne() {
// 删除之前弹出是否真的需要删除
let alertVc = ContactDeleteAlertView()
alertVc.frame = (self.responderViewController()?.view.bounds)!
self.responderViewController()?.view.addSubview(alertVc)
alertVc.sureCallBack = {[weak self] isSure in
guard let self else {return}
if isSure {
self.deleteContacts()
}
}
}
func deleteContacts(){
// 删除逻辑
for contact in selectedContacts {
if let index = self.dataSourceModel.firstIndex(of: contact) {
self.dataSourceModel.remove(at: index)
}
for (key, var sectionContacts) in sectionedContacts {
if let index = sectionContacts.firstIndex(of: contact) {
sectionContacts.remove(at: index)
sectionedContacts[key] = sectionContacts
}
}
}
selectedContacts.removeAll()
sortContacts()
self.subTitleLabel.text = "\(self.dataSourceModel.count) Contacts"
self.updateDeleteButtonStatus()
self.tableView.reloadData()
// fixme: 需要从联系人列表中删除
// 删除完成 弹窗
let buAlertVc = ContactBackUpDeleteCompletedAlertView(frame: (self.responderViewController()?.view.bounds)!)
self.responderViewController()?.view.addSubview(buAlertVc)
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
buAlertVc.removeFromSuperview()
}
}
/// 给联系人分组排序
func sortContacts() {
sectionedContacts.removeAll()
for contact in self.dataSourceModel {
let firstLetter = pinyinFirstLetter(contact.name).uppercased()
if sectionedContacts[firstLetter] == nil {
sectionedContacts[firstLetter] = []
}
sectionedContacts[firstLetter]?.append(contact)
}
sectionTitles = sectionedContacts.keys.sorted()
for key in sectionTitles {
sectionedContacts[key] = sectionedContacts[key]?.sorted {
return pinyinFirstLetter($0.name).uppercased() < pinyinFirstLetter($1.name).uppercased()
}
}
}
// 获取拼音首字母
private func pinyinFirstLetter(_ string: String) -> String {
let mutableString = NSMutableString(string: string)
CFStringTransform(mutableString, nil, kCFStringTransformToLatin, false)
CFStringTransform(mutableString, nil, kCFStringTransformStripDiacritics, false)
var pinyin = mutableString as String
if pinyin.isEmpty {
return "#"
}
pinyin = pinyin.replacingOccurrences(of: " ", with: "")
return pinyin.isEmpty ? "#" : String(pinyin.first!).uppercased()
}
}
//
// ContactDefaultView.swift
// PhoneManager
//
// Created by edy on 2025/4/18.
//
import Foundation
class ContactDefaultView : UIView {
lazy var titleLabel: UILabel = {
let label = UILabel()
label.text = "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 imageView: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "img_contacts")
return imageView
}()
// 懒加载第一个提示标签
lazy var firstLabel: UILabel = {
let label = UILabel()
label.text = "Need Access to Start Scanning"
label.numberOfLines = 0
label.font = UIFont.systemFont(ofSize: 24, weight: .bold)
label.textColor = UIColor(red: 0.2, green: 0.2, blue: 0.2, alpha: 1)
label.textAlignment = .center
return label
}()
// 懒加载第二个提示标签
lazy var secondLabel: UILabel = {
let label = UILabel()
label.text = "Phone Manager needs access to your contacts"
label.font = UIFont.systemFont(ofSize: 14, weight: .regular)
label.textColor = UIColor(red: 0.4, green: 0.4, blue: 0.4, alpha: 1)
label.textAlignment = .center
return label
}()
// 懒加载设置按钮
lazy var settingsButton: UIButton = {
let button = UIButton()
button.setTitle("Go to Settings", for: .normal)
button.backgroundColor = UIColor(red: 0, green: 0.51, blue: 1, alpha: 1)
button.layer.cornerRadius = 25
button.clipsToBounds = true
button.setTitleColor(.white, for: .normal)
button.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .bold)
button.addTarget(self, action: #selector(goToSettings), for: .touchUpInside)
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.setupUI()
self.setupConstraints()
}
private func setupUI() {
self.addSubview(self.titleLabel)
self.addSubview(self.imageView)
self.addSubview(self.firstLabel)
self.addSubview(self.secondLabel)
self.addSubview(self.settingsButton)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension ContactDefaultView {
private func setupConstraints() {
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.imageView.snp.makeConstraints { make in
make.top.equalTo(self.titleLabel.snp.bottom).offset(22 * RScreenH())
make.width.height.equalTo(242 * RScreenW())
make.centerX.equalToSuperview()
}
self.firstLabel.snp.makeConstraints { make in
make.top.equalTo(self.imageView.snp.bottom).offset(0)
make.width.equalTo(315 * RScreenW())
make.height.equalTo(68)
make.centerX.equalToSuperview()
}
self.secondLabel.snp.makeConstraints { make in
make.top.equalTo(self.firstLabel.snp.bottom).offset(8 * RScreenH())
make.width.equalTo(315 * RScreenW())
make.height.equalTo(20)
make.centerX.equalToSuperview()
}
self.settingsButton.snp.makeConstraints { make in
make.top.equalTo(self.secondLabel.snp.bottom).offset(32 * RScreenH())
make.width.equalTo(315 * RScreenW())
make.height.equalTo(50)
make.centerX.equalToSuperview()
}
}
@objc private func goToSettings() {
if let settingsURL = URL(string: UIApplication.openSettingsURLString) {
UIApplication.shared.open(settingsURL, options: [:], completionHandler: nil)
}
}
}
//
// ContactModuleView.swift
// PhoneManager
//
// Created by edy on 2025/4/18.
//
import Foundation
class ContactModuleView : UIView {
// 总数据
var dataSourceModel : ContactModuleModel?
// 当前页面显示数据
var moduleViewData : [ModuleDataModel] = []
lazy var titleLabel: UILabel = {
let label = UILabel()
label.text = "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 tableView: UITableView = {
let tableView = UITableView(frame: CGRect(x: 0, y: 0, width: 0, height: 12), style: UITableView.Style.plain)
tableView.dataSource = self
tableView.delegate = self
tableView.register(CustomContactModuleTableViewCell.self, forCellReuseIdentifier: "CustomContactModuleTableViewCell")
tableView.separatorStyle = .none
tableView.backgroundColor = .clear
tableView.isScrollEnabled = false
// 设置分隔线样式
tableView.separatorStyle = .none
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.tableView)
setupTableViewConstraints()
}
private func setupTableViewConstraints() {
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.tableView.snp.makeConstraints { make in
make.top.equalTo(self.titleLabel.snp.bottom).offset(18 * RScreenH())
make.width.equalTo(345 * RScreenW())
make.centerX.equalToSuperview()
make.bottom.equalToSuperview().offset(-34)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension ContactModuleView:UITableViewDataSource, UITableViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int {
return self.moduleViewData.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomContactModuleTableViewCell", for: indexPath) as! CustomContactModuleTableViewCell
let titleText = self.moduleViewData[indexPath.section].titleText
let subTitleText = self.moduleViewData[indexPath.section].subTitleText
cell.configure(title: titleText, subtitle: subTitleText)
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 74
}
// 实现 UITableViewDelegate 协议方法,设置 header 的高度
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return section == 0 ? 0 : 12
}
// 实现 UITableViewDelegate 协议方法,返回 header 视图
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView()
headerView.backgroundColor = .clear
return headerView
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// 跳转新页面
if indexPath.section == 0 {
// 跳转重复联系人页面
let vc : ContactsDupViewController = ContactsDupViewController()
vc.dataSourceModel = self.dataSourceModel?.duplicates
self.responderViewController()?.navigationController?.pushViewController(vc, animated: true)
}
if indexPath.section == 1 {
// 跳转不完整联系人页面
let vc : ContactIncompleteViewController = ContactIncompleteViewController()
vc.dataSourceModel = self.dataSourceModel!.incompleteContacts
self.responderViewController()?.navigationController?.pushViewController(vc, animated: true)
}
if indexPath.section == 2 {
// 跳转备份页面
let vc : ContactBackupViewController = ContactBackupViewController()
vc.dataSourceModel = self.dataSourceModel?.backups
vc.dataSourceAllModel = self.dataSourceModel
self.responderViewController()?.navigationController?.pushViewController(vc, animated: true)
}
if indexPath.section == 3 {
// 跳转所有联系人页面
let vc : ContactAllViewController = ContactAllViewController()
vc.dataSourceModel = self.dataSourceModel!.allContacts
self.responderViewController()?.navigationController?.pushViewController(vc, animated: true)
}
}
}
class CustomContactModuleTableViewCell: UITableViewCell {
// 主标题标签
lazy var titleLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 16, weight: .bold)
label.textColor = UIColor(red: 0.2, green: 0.2, blue: 0.2, alpha: 1)
return label
}()
// 副标题标签
lazy var subtitleLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 14, weight: .regular)
label.textColor = UIColor(red: 0.4, green: 0.4, blue: 0.4, alpha: 1)
return label
}()
// 箭头图片视图
lazy var arrowImageView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "icon_left_setting_grey"))
return imageView
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupUI()
setupConstraints()
self.layer.cornerRadius = 12
self.clipsToBounds = true
self.selectionStyle = .none
self.backgroundColor = UIColor(red: 0.95, green: 0.96, blue: 0.99, alpha: 1)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupUI() {
self.addSubview(titleLabel)
self.addSubview(subtitleLabel)
self.addSubview(arrowImageView)
}
private func setupConstraints() {
titleLabel.snp.makeConstraints { make in
make.top.equalToSuperview().inset(16)
make.left.equalToSuperview().offset(16)
make.height.equalTo(22)
make.width.equalTo(238)
}
subtitleLabel.snp.makeConstraints { make in
make.top.equalTo(titleLabel.snp.bottom).offset(0)
make.left.equalToSuperview().offset(16)
make.height.equalTo(20)
make.width.equalTo(238)
}
arrowImageView.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.width.height.equalTo(20)
make.right.equalToSuperview().offset(-16)
}
}
func configure(title: String, subtitle: String) {
titleLabel.text = title
subtitleLabel.text = subtitle
}
}
...@@ -14,7 +14,6 @@ ...@@ -14,7 +14,6 @@
"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",
......
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