Commit 92524ed8 authored by CZ1004's avatar CZ1004

联系人代码

parent b2ff9d1b
......@@ -560,6 +560,7 @@
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = PhoneManager/Info.plist;
INFOPLIST_KEY_NSCameraUsageDescription = "We need to obtain your camera permission in order to save the photos you take in the private album feature";
INFOPLIST_KEY_NSContactsUsageDescription = "Phone Manager needs access to your contacts to find and merge duplicate contacts";
INFOPLIST_KEY_NSLocalNetworkUsageDescription = "We need to access the network to load content";
INFOPLIST_KEY_NSMicrophoneUsageDescription = "We need to obtain your microphone permission in order to use it when shooting videos in the private album feature";
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "We need to access your album in order to find the photos you want to change and update them";
......@@ -606,6 +607,7 @@
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = PhoneManager/Info.plist;
INFOPLIST_KEY_NSCameraUsageDescription = "We need to obtain your camera permission in order to save the photos you take in the private album feature";
INFOPLIST_KEY_NSContactsUsageDescription = "Phone Manager needs access to your contacts to find and merge duplicate contacts";
INFOPLIST_KEY_NSLocalNetworkUsageDescription = "We need to access the network to load content";
INFOPLIST_KEY_NSMicrophoneUsageDescription = "We need to obtain your microphone permission in order to use it when shooting videos in the private album feature";
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "We need to access your album in order to find the photos you want to change and update them";
......
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
{
"images" : [
{
"filename" : "img_contacts.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "img_contacts@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "img_contacts@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
//
// 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.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)
// 请求联系人权限
requestContactsPermission { permission in
DispatchQueue.main.async {
if permission {
// 有权限
self.updateModuleData()
self.setNormalPage()
// 初始化数据
self.initContatsInfo()
}else {
// 没有权限显示空白页
self.setDefaultPage()
}
}
}
}
}
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
}
/// 初始化联系人数据
func initContatsInfo() {
DispatchQueue.global().async {
let store = CNContactStore()
let keysToFetch = [
CNContactGivenNameKey as CNKeyDescriptor,
CNContactFamilyNameKey as CNKeyDescriptor,
CNContactPhoneNumbersKey as CNKeyDescriptor
]
do {
let request = CNContactFetchRequest(keysToFetch: keysToFetch)
try store.enumerateContacts(with: request) { contact, stop in
let givenName = contact.givenName
let familyName = contact.familyName
let fullName = "\(familyName) \(givenName)"
// 创建数组
var incompleteContacts : [ContactModel] = []
var allContacts : [ContactModel] = []
for phone in contact.phoneNumbers {
let phoneNumber = phone.value.stringValue
let model = ContactModel.init(name: fullName, phoneNumber: phoneNumber)
allContacts.append(model)
if fullName.isEmpty || phoneNumber.isEmpty{
incompleteContacts.append(model)
}
}
self.dataSourceModel = ContactModuleModel.init(duplicates: [], incompleteContacts: incompleteContacts, backups: [], allContacts: allContacts)
DispatchQueue.main.async {
self.updateModuleData()
self.moduleView.tableView.reloadSections(IndexSet(integer: 0), with: .automatic)
}
}
} catch {
DispatchQueue.main.async {
print("获取联系人信息时发生错误: \(error)")
}
}
}
}
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)
}
}
}
//
// ContactModuleModel.swift
// PhoneManager
//
// Created by edy on 2025/4/18.
//
import Foundation
struct ContactModel {
// 联系人名字
var name: String
// 联系人电话
var phoneNumber : String?
init(name: String, phoneNumber: String) {
self.name = name
self.phoneNumber = phoneNumber
}
}
struct BackupsModel {
// 备份时间
var backupsDate : Date
// 备份的联系人
var backupsContacts: [ContactModel]
init(backupsDate: Date, backupsContacts: [ContactModel]) {
self.backupsDate = backupsDate
self.backupsContacts = backupsContacts
}
}
struct ContactModuleModel {
var duplicates: [[ContactModel]]
var incompleteContacts: [ContactModel]
var backups: [BackupsModel]
var allContacts: [ContactModel]
init(duplicates: [[ContactModel]], incompleteContacts: [ContactModel], backups: [BackupsModel], 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
}
}
//
// 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(86 * RScreenW())
make.height.equalTo(28)
}
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 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()
tableView.dataSource = self
tableView.delegate = self
tableView.register(CustomContactModuleTableViewCell.self, forCellReuseIdentifier: "CustomContactModuleTableViewCell")
tableView.separatorStyle = .none
tableView.backgroundColor = .clear
tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: 12))
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(86 * RScreenW())
make.height.equalTo(28)
}
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()
}
}
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.row].titleText
let subTitleText = self.moduleViewData[indexPath.row].subTitleText
cell.selectionStyle = .none
cell.configure(title: titleText, subtitle: subTitleText)
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 74
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 12
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView()
headerView.backgroundColor = .clear
return headerView
}
}
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()
contentView.layer.cornerRadius = 12
contentView.clipsToBounds = true
self.contentView.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() {
contentView.addSubview(titleLabel)
contentView.addSubview(subtitleLabel)
contentView.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
}
}
//
// ContactNavView.swift
// PhoneManager
//
// Created by edy on 2025/4/18.
//
import Foundation
class ContactNavView : UIView {
private 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)
}
}
......@@ -127,13 +127,21 @@ class HomeViewController:BaseViewController {
let vc:SecretViewController = SecretViewController()
self.navigationController?.pushViewController(vc, animated: true)
}
case 2 :
case 2:
DispatchQueue.main.async { [weak self] in
guard let self else {return}
let vc:ContactViewController = ContactViewController()
self.navigationController?.pushViewController(vc, animated: true)
}
case 3 :
DispatchQueue.main.async {[weak self] in
guard let self else {return}
let vc:CompressController = CompressController()
self.navigationController?.pushViewController(vc, animated: true)
}
break
default:
break
}
......
......@@ -9,6 +9,11 @@
"heightImage": "tabbar_secret_hight",
"text":"Secret Space",
},
{
"normalImage": "ic_contacts_home_pre",
"heightImage": "tabbar_contacts_hight",
"text":"Contacts",
},
{
"normalImage": "ic_cmpress_home_pre",
"heightImage": "tabbar_cmpress_high",
......
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