Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Sign in / Register
Toggle navigation
P
PhoneManager
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Packages
Packages
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Yang
PhoneManager
Commits
c19fadbf
Commit
c19fadbf
authored
May 08, 2025
by
CZ1004
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
【新增】联系人主要功能逻辑代码
parent
6e41a98b
Hide whitespace changes
Inline
Side-by-side
Showing
25 changed files
with
1539 additions
and
188 deletions
+1539
-188
AppDelegate.swift
PhoneManager/AppDelegate.swift
+1
-1
ContactBackupDetailViewController.swift
...ontact/Controller/ContactBackupDetailViewController.swift
+15
-1
ContactBackupViewController.swift
...sion/Contact/Controller/ContactBackupViewController.swift
+1
-0
ContactIncompleteViewController.swift
.../Contact/Controller/ContactIncompleteViewController.swift
+6
-1
ContactViewController.swift
...ss/Session/Contact/Controller/ContactViewController.swift
+1
-0
ContactsDupPreViewController.swift
...ion/Contact/Controller/ContactsDupPreViewController.swift
+102
-0
ContactsDupViewController.swift
...ession/Contact/Controller/ContactsDupViewController.swift
+67
-14
CustomContactViewController.swift
...sion/Contact/Controller/CustomContactViewController.swift
+56
-0
ContactManager.swift
PhoneManager/Class/Session/Contact/Tool/ContactManager.swift
+337
-0
ContactAllView.swift
...nager/Class/Session/Contact/View/All/ContactAllView.swift
+25
-15
ContactBackUpNormalView.swift
...ss/Session/Contact/View/Bac/ContactBackUpNormalView.swift
+5
-5
CustomContactAllViewTableViewCell.swift
...Contact/View/Cell/CustomContactAllViewTableViewCell.swift
+1
-1
CustomContactDupPreTableViewCell.swift
.../Contact/View/Cell/CustomContactDupPreTableViewCell.swift
+122
-0
CustomContactDupTableViewCell.swift
...ion/Contact/View/Cell/CustomContactDupTableViewCell.swift
+2
-2
CustomDupPreHeaderView.swift
...ss/Session/Contact/View/Cell/CustomDupPreHeaderView.swift
+77
-0
ContactBackUpCompletedAlertView.swift
...ew/Common/AlertView/ContactBackUpCompletedAlertView.swift
+0
-1
ContactRestoreSuccessView.swift
...act/View/Common/AlertView/ContactRestoreSuccessView.swift
+82
-0
DeleteButtonView.swift
.../Class/Session/Contact/View/Common/DeleteButtonView.swift
+30
-33
ContactDupNormalView.swift
...Class/Session/Contact/View/Dup/ContactDupNormalView.swift
+75
-5
ContactDupPreNormalView.swift
...ss/Session/Contact/View/Dup/ContactDupPreNormalView.swift
+189
-0
ContactNoDupPreView.swift
.../Class/Session/Contact/View/Dup/ContactNoDupPreView.swift
+88
-0
MergeButtonView.swift
...ager/Class/Session/Contact/View/Dup/MergeButtonView.swift
+129
-0
MergePreButtonView.swift
...r/Class/Session/Contact/View/Dup/MergePreButtonView.swift
+62
-0
ContactNormalIncomView.swift
...ass/Session/Contact/View/Inc/ContactNormalIncomView.swift
+65
-109
Singleton.swift
PhoneManager/Class/Tool/Singleton/Singleton.swift
+1
-0
No files found.
PhoneManager/AppDelegate.swift
View file @
c19fadbf
...
@@ -21,7 +21,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
...
@@ -21,7 +21,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
window
=
UIWindow
(
frame
:
UIScreen
.
main
.
bounds
)
window
=
UIWindow
(
frame
:
UIScreen
.
main
.
bounds
)
window
?
.
backgroundColor
=
UIColor
.
colorWithHex
(
hexStr
:
launchColor
)
window
?
.
backgroundColor
=
.
white
window
?
.
overrideUserInterfaceStyle
=
.
light
window
?
.
overrideUserInterfaceStyle
=
.
light
let
Ssoryboard
=
UIStoryboard
(
name
:
"LauchVC"
,
bundle
:
nil
)
let
Ssoryboard
=
UIStoryboard
(
name
:
"LauchVC"
,
bundle
:
nil
)
...
...
PhoneManager/Class/Session/Contact/Controller/ContactBackupDetailViewController.swift
View file @
c19fadbf
...
@@ -227,7 +227,21 @@ extension ContactBackupDetailViewController:UITableViewDelegate,UITableViewDataS
...
@@ -227,7 +227,21 @@ extension ContactBackupDetailViewController:UITableViewDelegate,UITableViewDataS
let
alertVc
=
ContactBacRestoreAlertView
(
frame
:
self
.
view
.
bounds
)
let
alertVc
=
ContactBacRestoreAlertView
(
frame
:
self
.
view
.
bounds
)
self
.
view
.
addSubview
(
alertVc
)
self
.
view
.
addSubview
(
alertVc
)
alertVc
.
sureCallBack
=
{
alertVc
.
sureCallBack
=
{
ContactManager
.
performAtomicRestore
(
self
.
dataSourceModel
)
{
success
,
error
in
if
success
{
print
(
"恢复成功!"
)
DispatchQueue
.
main
.
async
{
// 再次请求数据 重新刷新页面
let
buAlertVc
=
ContactRestoreSuccessView
(
frame
:
self
.
view
.
bounds
)
cWindow
?
.
addSubview
(
buAlertVc
)
DispatchQueue
.
main
.
asyncAfter
(
deadline
:
.
now
()
+
1
)
{
buAlertVc
.
removeFromSuperview
()
}
}
}
else
{
print
(
"失败原因:
\(
error
?
.
localizedDescription
??
"未知错误"
)
"
)
}
}
}
}
}
}
...
...
PhoneManager/Class/Session/Contact/Controller/ContactBackupViewController.swift
View file @
c19fadbf
...
@@ -33,6 +33,7 @@ class ContactBackupViewController : BaseViewController {
...
@@ -33,6 +33,7 @@ class ContactBackupViewController : BaseViewController {
// 默认页面
// 默认页面
func
setDefaultPage
(){
func
setDefaultPage
(){
self
.
normalView
.
removeFromSuperview
()
self
.
view
.
addSubview
(
self
.
emptyView
)
self
.
view
.
addSubview
(
self
.
emptyView
)
self
.
emptyView
.
snp
.
makeConstraints
{
make
in
self
.
emptyView
.
snp
.
makeConstraints
{
make
in
...
...
PhoneManager/Class/Session/Contact/Controller/ContactIncompleteViewController.swift
View file @
c19fadbf
...
@@ -39,6 +39,8 @@ class ContactIncompleteViewController : BaseViewController {
...
@@ -39,6 +39,8 @@ class ContactIncompleteViewController : BaseViewController {
// 默认页面
// 默认页面
func
setDefaultPage
(){
func
setDefaultPage
(){
self
.
normalView
.
removeFromSuperview
()
self
.
selectAllButton
.
isHidden
=
true
self
.
view
.
addSubview
(
self
.
emptyView
)
self
.
view
.
addSubview
(
self
.
emptyView
)
self
.
emptyView
.
snp
.
makeConstraints
{
make
in
self
.
emptyView
.
snp
.
makeConstraints
{
make
in
...
@@ -49,6 +51,7 @@ class ContactIncompleteViewController : BaseViewController {
...
@@ -49,6 +51,7 @@ class ContactIncompleteViewController : BaseViewController {
}
}
func
setNormalPage
(){
func
setNormalPage
(){
self
.
emptyView
.
removeFromSuperview
()
self
.
emptyView
.
removeFromSuperview
()
self
.
selectAllButton
.
isHidden
=
false
self
.
view
.
addSubview
(
self
.
normalView
)
self
.
view
.
addSubview
(
self
.
normalView
)
self
.
normalView
.
snp
.
makeConstraints
{
make
in
self
.
normalView
.
snp
.
makeConstraints
{
make
in
...
@@ -94,6 +97,9 @@ class ContactIncompleteViewController : BaseViewController {
...
@@ -94,6 +97,9 @@ class ContactIncompleteViewController : BaseViewController {
}
}
self
.
normalView
.
tableView
.
reloadData
()
self
.
normalView
.
tableView
.
reloadData
()
}
}
self
.
normalView
.
dataClearCallBack
=
{
self
.
setDefaultPage
()
}
self
.
setDefaultPage
()
self
.
setDefaultPage
()
}
}
}
}
...
@@ -106,7 +112,6 @@ extension ContactIncompleteViewController {
...
@@ -106,7 +112,6 @@ extension ContactIncompleteViewController {
self
.
setNormalPage
()
self
.
setNormalPage
()
self
.
normalView
.
dataSourceModel
=
data
self
.
normalView
.
dataSourceModel
=
data
DispatchQueue
.
main
.
async
{
DispatchQueue
.
main
.
async
{
self
.
normalView
.
sortContacts
()
self
.
normalView
.
subTitleLabel
.
text
=
"
\(
data
.
count
)
Contacts"
self
.
normalView
.
subTitleLabel
.
text
=
"
\(
data
.
count
)
Contacts"
self
.
normalView
.
tableView
.
reloadData
()
self
.
normalView
.
tableView
.
reloadData
()
}
}
...
...
PhoneManager/Class/Session/Contact/Controller/ContactViewController.swift
View file @
c19fadbf
...
@@ -41,6 +41,7 @@ class ContactViewController : BaseViewController {
...
@@ -41,6 +41,7 @@ class ContactViewController : BaseViewController {
}
}
func
setDefaultPage
(){
func
setDefaultPage
(){
self
.
moduleView
.
removeFromSuperview
()
self
.
view
.
addSubview
(
self
.
emptyView
)
self
.
view
.
addSubview
(
self
.
emptyView
)
self
.
emptyView
.
snp
.
makeConstraints
{
make
in
self
.
emptyView
.
snp
.
makeConstraints
{
make
in
make
.
top
.
equalTo
(
self
.
navView
.
snp
.
bottom
)
.
offset
(
0
)
make
.
top
.
equalTo
(
self
.
navView
.
snp
.
bottom
)
.
offset
(
0
)
...
...
PhoneManager/Class/Session/Contact/Controller/ContactsDupPreViewController.swift
0 → 100644
View file @
c19fadbf
//
// ContactsDupPreViewController.swift
// PhoneManager
//
// Created by edy on 2025/5/8.
//
import
Foundation
class
ContactsDupPreViewController
:
BaseViewController
{
var
dataSourceModel
:
[
String
:[
ContactModel
]]
=
[:]
lazy
var
navView
:
ContactNavView
=
{
let
view
=
ContactNavView
()
return
view
}()
lazy
var
emptyView
:
ContactNoDupPreView
=
{
let
view
=
ContactNoDupPreView
()
return
view
}()
lazy
var
normalView
:
ContactDupPreNormalView
=
{
let
view
=
ContactDupPreNormalView
()
return
view
}()
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
.
normalView
.
dataChangeCallBack
=
{[
weak
self
]
isClear
in
guard
let
self
else
{
return
}
if
isClear
{
DispatchQueue
.
main
.
async
{
self
.
setDefaultPage
()
}
}
}
}
// 默认页面
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
()
}
}
}
extension
ContactsDupPreViewController
{
override
func
viewWillAppear
(
_
animated
:
Bool
)
{
super
.
viewWillAppear
(
animated
)
if
self
.
dataSourceModel
.
count
>
0
{
self
.
setNormalPage
()
var
finallyData
:
[[
ContactModel
]]
=
[]
for
(
_
,
value
)
in
self
.
dataSourceModel
{
finallyData
.
append
(
value
)
}
self
.
normalView
.
dataSourceModel
=
finallyData
DispatchQueue
.
main
.
async
{
self
.
normalView
.
subTitleLabel
.
text
=
"
\(
self
.
dataSourceModel
.
count
)
Contacts"
var
count
:
Int
=
0
for
item
in
finallyData
{
count
+=
item
.
count
}
self
.
normalView
.
mergeButtonView
.
mergeButton
.
setTitle
(
"Merge
\(
count
)
Contacts"
,
for
:
.
normal
)
self
.
normalView
.
tableView
.
reloadData
()
}
}
else
{
self
.
setDefaultPage
()
}
}
}
PhoneManager/Class/Session/Contact/Controller/ContactsDupViewController.swift
View file @
c19fadbf
...
@@ -7,6 +7,7 @@
...
@@ -7,6 +7,7 @@
import
Foundation
import
Foundation
import
SnapKit
import
SnapKit
import
Contacts
class
ContactsDupViewController
:
BaseViewController
{
class
ContactsDupViewController
:
BaseViewController
{
var
dataSourceModel
:
[[
ContactModel
]]?
var
dataSourceModel
:
[[
ContactModel
]]?
...
@@ -38,6 +39,7 @@ class ContactsDupViewController : BaseViewController {
...
@@ -38,6 +39,7 @@ class ContactsDupViewController : BaseViewController {
// 默认页面
// 默认页面
func
setDefaultPage
(){
func
setDefaultPage
(){
self
.
normalView
.
removeFromSuperview
()
self
.
normalView
.
removeFromSuperview
()
self
.
selectAllButton
.
isHidden
=
true
self
.
view
.
addSubview
(
self
.
emptyView
)
self
.
view
.
addSubview
(
self
.
emptyView
)
self
.
emptyView
.
snp
.
makeConstraints
{
make
in
self
.
emptyView
.
snp
.
makeConstraints
{
make
in
...
@@ -45,20 +47,12 @@ class ContactsDupViewController : BaseViewController {
...
@@ -45,20 +47,12 @@ class ContactsDupViewController : BaseViewController {
make
.
left
.
right
.
equalToSuperview
()
make
.
left
.
right
.
equalToSuperview
()
make
.
bottom
.
equalToSuperview
()
make
.
bottom
.
equalToSuperview
()
}
}
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
)
}
}
}
func
setNormalPage
(){
func
setNormalPage
(){
self
.
emptyView
.
removeFromSuperview
()
self
.
emptyView
.
removeFromSuperview
()
self
.
view
.
addSubview
(
self
.
normalView
)
self
.
view
.
addSubview
(
self
.
normalView
)
self
.
selectAllButton
.
isHidden
=
false
self
.
normalView
.
snp
.
makeConstraints
{
make
in
self
.
normalView
.
snp
.
makeConstraints
{
make
in
make
.
top
.
equalTo
(
self
.
navView
.
snp
.
bottom
)
.
offset
(
0
)
make
.
top
.
equalTo
(
self
.
navView
.
snp
.
bottom
)
.
offset
(
0
)
make
.
left
.
right
.
equalToSuperview
()
make
.
left
.
right
.
equalToSuperview
()
...
@@ -77,6 +71,14 @@ class ContactsDupViewController : BaseViewController {
...
@@ -77,6 +71,14 @@ class ContactsDupViewController : BaseViewController {
make
.
height
.
equalTo
(
statusBarHeight
+
44
)
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
.
setDefaultPage
()
self
.
setDefaultPage
()
...
@@ -116,16 +118,17 @@ class ContactsDupViewController : BaseViewController {
...
@@ -116,16 +118,17 @@ class ContactsDupViewController : BaseViewController {
}
}
}
}
}
}
let
dataUpdated
=
Notification
.
Name
(
ContactDupPreNormalView
.
CONTACT_MERGED
)
NotificationCenter
.
default
.
addObserver
(
self
,
selector
:
#selector(
handleDataUpdated(_:)
)
,
name
:
dataUpdated
,
object
:
nil
)
}
}
}
}
extension
ContactsDupViewController
{
extension
ContactsDupViewController
{
override
func
viewWillAppear
(
_
animated
:
Bool
)
{
override
func
viewWillAppear
(
_
animated
:
Bool
)
{
// 重新获取下重复项的数据
super
.
viewWillAppear
(
animated
)
super
.
viewWillAppear
(
animated
)
if
let
data
=
self
.
dataSourceModel
{
if
let
data
=
self
.
dataSourceModel
{
if
data
.
count
>
0
{
if
data
.
count
>
0
{
...
@@ -154,5 +157,55 @@ extension ContactsDupViewController {
...
@@ -154,5 +157,55 @@ extension ContactsDupViewController {
}
}
return
totalElementCount
return
totalElementCount
}
}
@objc
func
handleDataUpdated
(
_
notification
:
Notification
)
{
// 重新获取下重复项的数据
let
store
=
CNContactStore
()
let
keysToFetch
=
[
CNContactGivenNameKey
as
CNKeyDescriptor
,
CNContactFamilyNameKey
as
CNKeyDescriptor
,
CNContactPhoneNumbersKey
as
CNKeyDescriptor
]
do
{
let
request
=
CNContactFetchRequest
(
keysToFetch
:
keysToFetch
)
// 创建数组
var
duplicates
:
[[
ContactModel
]]
=
[]
var
contactsByName
:
[
String
:
[
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
{
if
contactsByName
[
fullName
]
==
nil
{
contactsByName
[
fullName
]
=
[
model
]
}
else
{
contactsByName
[
fullName
]?
.
append
(
model
)
}
}
duplicates
=
contactsByName
.
values
.
filter
{
$0
.
count
>
1
}
}
DispatchQueue
.
main
.
async
{
self
.
dataSourceModel
=
self
.
sortDupDataSource
(
orgData
:
duplicates
)
self
.
normalView
.
tableView
.
reloadData
()
}
}
catch
{
DispatchQueue
.
main
.
async
{
print
(
"获取联系人信息时发生错误:
\(
error
)
"
)
}
}
}
/// 重复项排序-做一个反序操作,让最新添加的联系人显示在最前面
/// - Parameter orgData: 原始数据
/// - Returns: 排序后的数据
func
sortDupDataSource
(
orgData
:[[
ContactModel
]])
->
[[
ContactModel
]]{
return
orgData
.
map
{
$0
.
reversed
()
}
}
}
}
PhoneManager/Class/Session/Contact/Controller/CustomContactViewController.swift
0 → 100644
View file @
c19fadbf
//
// CustomContactViewController.swift
// PhoneManager
//
// Created by edy on 2025/5/8.
//
import
ContactsUI
// 步骤1: 创建子类继承 CNContactViewController
class
CustomContactViewController
:
CNContactViewController
{
override
func
viewDidLoad
()
{
super
.
viewDidLoad
()
setupCancelButton
()
}
// 自定义取消按钮
private
func
setupCancelButton
()
{
navigationItem
.
leftBarButtonItem
=
UIBarButtonItem
(
title
:
"Cancel"
,
style
:
.
plain
,
target
:
self
,
action
:
#selector(
dismissEditor
)
)
}
}
extension
CustomContactViewController
{
@objc
private
func
dismissEditor
()
{
dismiss
(
animated
:
true
,
completion
:
nil
)
}
// 使用自定义视图控制器
func
presentCustomContactEditor
(
with
identifier
:
String
,
viewController
:
UIViewController
)
{
let
contactStore
=
CNContactStore
()
let
keys
=
[
CNContactViewController
.
descriptorForRequiredKeys
()]
do
{
let
contact
=
try
contactStore
.
unifiedContact
(
withIdentifier
:
identifier
,
keysToFetch
:
keys
)
let
mutableContact
=
contact
.
mutableCopy
()
as!
CNMutableContact
// 步骤4: 使用自定义子类初始化
let
editVC
=
CustomContactViewController
(
for
:
mutableContact
)
editVC
.
contactStore
=
contactStore
let
navController
=
UINavigationController
(
rootViewController
:
editVC
)
viewController
.
present
(
navController
,
animated
:
true
)
}
catch
{
print
(
"获取联系人失败:
\(
error
)
"
)
}
}
}
PhoneManager/Class/Session/Contact/Tool/ContactManager.swift
0 → 100644
View file @
c19fadbf
//
// ContactManager.swift
// PhoneManager
//
// Created by edy on 2025/5/8.
//
import
Foundation
import
Contacts
import
ContactsUI
enum
ContactError
:
Error
{
case
unauthorized
case
contactNotFound
(
ContactModel
)
case
executionFailed
(
Error
)
}
enum
MergePolicy
{
case
union
// 合并所有非空字段
case
priority
(
ContactModel
)
// 指定优先级联系人
}
enum
ContactMergeError
:
Error
{
case
unauthorized
case
emptyGroup
case
contactNotFound
(
identifier
:
String
)
case
mergeConflict
(
field
:
String
)
case
systemError
(
Error
)
}
class
ContactManager
{
// MARK: 删除联系人
static
func
batchDeleteContacts
(
_
contacts
:
[
ContactModel
],
completion
:
@escaping
(
Result
<
[
ContactModel
],
ContactError
>
)
->
Void
)
{
let
store
=
CNContactStore
()
let
queue
=
DispatchQueue
(
label
:
"com.contacts.deleteQueue"
,
qos
:
.
userInitiated
)
queue
.
async
{
// 前置权限检查
guard
CNContactStore
.
authorizationStatus
(
for
:
.
contacts
)
==
.
authorized
else
{
DispatchQueue
.
main
.
async
{
completion
(
.
failure
(
.
unauthorized
))
}
return
}
do
{
let
result
=
try
performAtomicDeletion
(
store
:
store
,
contacts
:
contacts
)
DispatchQueue
.
main
.
async
{
completion
(
.
success
(
result
))
}
}
catch
let
error
as
ContactError
{
DispatchQueue
.
main
.
async
{
completion
(
.
failure
(
error
))
}
}
catch
{
DispatchQueue
.
main
.
async
{
completion
(
.
failure
(
.
executionFailed
(
error
)))
}
}
}
}
private
static
func
performAtomicDeletion
(
store
:
CNContactStore
,
contacts
:
[
ContactModel
])
throws
->
[
ContactModel
]
{
let
batchSize
=
200
var
deletedContacts
=
[
ContactModel
]()
for
batch
in
contacts
.
chunked
(
into
:
batchSize
)
{
let
identifiers
=
batch
.
map
{
$0
.
identifier
}
// 批量获取联系人
let
predicate
=
CNContact
.
predicateForContacts
(
withIdentifiers
:
identifiers
)
let
matchedContacts
=
try
store
.
unifiedContacts
(
matching
:
predicate
,
keysToFetch
:
[
CNContactIdentifierKey
as
CNKeyDescriptor
]
)
// 验证完整性
let
matchedIDs
=
Set
(
matchedContacts
.
map
{
$0
.
identifier
})
if
let
missing
=
batch
.
first
(
where
:
{
!
matchedIDs
.
contains
(
$0
.
identifier
)
})
{
throw
ContactError
.
contactNotFound
(
missing
)
}
// 执行删除
let
saveRequest
=
CNSaveRequest
()
matchedContacts
.
forEach
{
saveRequest
.
delete
(
$0
.
mutableCopy
()
as!
CNMutableContact
)
}
try
store
.
execute
(
saveRequest
)
deletedContacts
.
append
(
contentsOf
:
batch
)
}
return
deletedContacts
}
// MARK: 合并联系人
static
func
mergeContacts
(
groups
:
[[
ContactModel
]],
mergePolicy
:
MergePolicy
=
.
union
,
completion
:
@escaping
(
Result
<
[
String
],
ContactMergeError
>
)
->
Void
)
{
let
store
=
CNContactStore
()
let
queue
=
DispatchQueue
(
label
:
"com.contacts.mergeQueue"
,
qos
:
.
userInitiated
)
queue
.
async
{
// 权限检查
guard
CNContactStore
.
authorizationStatus
(
for
:
.
contacts
)
==
.
authorized
else
{
DispatchQueue
.
main
.
async
{
completion
(
.
failure
(
.
unauthorized
))
}
return
}
do
{
var
mergedIDs
=
[
String
]()
let
saveRequest
=
CNSaveRequest
()
try
groups
.
forEach
{
group
in
guard
!
group
.
isEmpty
else
{
throw
ContactMergeError
.
emptyGroup
}
// 获取原始联系人
let
contacts
=
try
fetchContacts
(
for
:
group
,
store
:
store
)
// 创建合并后的联系人
let
mergedContact
=
try
mergeContacts
(
contacts
,
policy
:
mergePolicy
)
// 删除出去第一个的联系人(因为第一个的identifier就是mergedContact的identifier,直接更新这个主联系人就可以了)
for
(
index
,
item
)
in
contacts
.
enumerated
(){
if
(
index
!=
0
){
saveRequest
.
delete
(
item
.
mutableCopy
()
as!
CNMutableContact
)
}
}
// 更新主(传入的第一个)联系人
saveRequest
.
update
(
mergedContact
)
mergedIDs
.
append
(
mergedContact
.
identifier
)
}
try
store
.
execute
(
saveRequest
)
DispatchQueue
.
main
.
async
{
completion
(
.
success
(
mergedIDs
))
}
}
catch
let
error
as
ContactMergeError
{
DispatchQueue
.
main
.
async
{
completion
(
.
failure
(
error
))
}
}
catch
{
DispatchQueue
.
main
.
async
{
completion
(
.
failure
(
.
systemError
(
error
)))
}
}
}
}
private
static
func
fetchContacts
(
for
group
:
[
ContactModel
],
store
:
CNContactStore
)
throws
->
[
CNContact
]
{
let
identifiers
=
group
.
map
{
$0
.
identifier
}
let
predicate
=
CNContact
.
predicateForContacts
(
withIdentifiers
:
identifiers
)
let
keysToFetch
:
[
CNKeyDescriptor
]
=
[
CNContactGivenNameKey
,
CNContactFamilyNameKey
,
CNContactPhoneNumbersKey
,
CNContactEmailAddressesKey
,
CNContactPostalAddressesKey
,
CNContactIdentifierKey
]
.
map
{
$0
as
CNKeyDescriptor
}
let
contacts
=
try
store
.
unifiedContacts
(
matching
:
predicate
,
keysToFetch
:
keysToFetch
)
// 验证所有联系人存在
let
foundIDs
=
Set
(
contacts
.
map
{
$0
.
identifier
})
guard
let
missing
=
identifiers
.
first
(
where
:
{
!
foundIDs
.
contains
(
$0
)
})
else
{
return
contacts
}
throw
ContactMergeError
.
contactNotFound
(
identifier
:
missing
)
}
private
static
func
mergeContacts
(
_
contacts
:
[
CNContact
],
policy
:
MergePolicy
)
throws
->
CNMutableContact
{
guard
let
baseContact
=
contacts
.
first
else
{
throw
ContactMergeError
.
emptyGroup
}
let
merged
=
baseContact
.
mutableCopy
()
as!
CNMutableContact
switch
policy
{
case
.
union
:
for
contact
in
contacts
{
mergePhoneNumbers
(
from
:
contact
,
to
:
merged
)
mergeEmails
(
from
:
contact
,
to
:
merged
)
mergeAddresses
(
from
:
contact
,
to
:
merged
)
mergeNames
(
from
:
contact
,
to
:
merged
)
}
case
.
priority
(
let
model
):
guard
let
priorityContact
=
contacts
.
first
(
where
:
{
$0
.
identifier
==
model
.
identifier
})
else
{
throw
ContactMergeError
.
contactNotFound
(
identifier
:
model
.
identifier
)
}
copyContactProperties
(
from
:
priorityContact
,
to
:
merged
)
}
return
merged
}
private
static
func
mergePhoneNumbers
(
from
source
:
CNContact
,
to
target
:
CNMutableContact
)
{
let
existingNumbers
=
Set
(
target
.
phoneNumbers
.
map
{
$0
.
value
.
stringValue
})
let
newNumbers
=
source
.
phoneNumbers
.
filter
{
!
existingNumbers
.
contains
(
$0
.
value
.
stringValue
)
}
target
.
phoneNumbers
.
append
(
contentsOf
:
newNumbers
)
}
private
static
func
mergeEmails
(
from
source
:
CNContact
,
to
target
:
CNMutableContact
)
{
let
existingEmails
=
Set
(
target
.
emailAddresses
.
map
{
$0
.
value
as
String
})
let
newEmails
=
source
.
emailAddresses
.
filter
{
!
existingEmails
.
contains
(
$0
.
value
as
String
)
}
target
.
emailAddresses
.
append
(
contentsOf
:
newEmails
)
}
private
static
func
mergeAddresses
(
from
source
:
CNContact
,
to
target
:
CNMutableContact
)
{
let
existingAddresses
=
Set
(
target
.
postalAddresses
.
map
{
$0
.
value
})
let
newAddresses
=
source
.
postalAddresses
.
filter
{
!
existingAddresses
.
contains
(
$0
.
value
)
}
target
.
postalAddresses
.
append
(
contentsOf
:
newAddresses
)
}
private
static
func
mergeNames
(
from
source
:
CNContact
,
to
target
:
CNMutableContact
)
{
// 保留非空字段
if
target
.
givenName
.
isEmpty
&&
!
source
.
givenName
.
isEmpty
{
target
.
givenName
=
source
.
givenName
}
if
target
.
familyName
.
isEmpty
&&
!
source
.
familyName
.
isEmpty
{
target
.
familyName
=
source
.
familyName
}
}
private
static
func
copyContactProperties
(
from
source
:
CNContact
,
to
target
:
CNMutableContact
)
{
// 基础信息
target
.
givenName
=
source
.
givenName
target
.
familyName
=
source
.
familyName
target
.
middleName
=
source
.
middleName
target
.
namePrefix
=
source
.
namePrefix
target
.
nameSuffix
=
source
.
nameSuffix
target
.
nickname
=
source
.
nickname
// 组织信息
target
.
organizationName
=
source
.
organizationName
target
.
departmentName
=
source
.
departmentName
target
.
jobTitle
=
source
.
jobTitle
// 联系方式
target
.
phoneNumbers
=
source
.
phoneNumbers
.
map
{
CNLabeledValue
(
label
:
$0
.
label
,
value
:
$0
.
value
)
}
target
.
emailAddresses
=
source
.
emailAddresses
.
map
{
CNLabeledValue
(
label
:
$0
.
label
,
value
:
$0
.
value
)
}
target
.
postalAddresses
=
source
.
postalAddresses
.
map
{
CNLabeledValue
(
label
:
$0
.
label
,
value
:
$0
.
value
)
}
// 其他信息
target
.
urlAddresses
=
source
.
urlAddresses
.
map
{
CNLabeledValue
(
label
:
$0
.
label
,
value
:
$0
.
value
)
}
target
.
contactRelations
=
source
.
contactRelations
.
map
{
CNLabeledValue
(
label
:
$0
.
label
,
value
:
$0
.
value
)
}
target
.
socialProfiles
=
source
.
socialProfiles
.
map
{
CNLabeledValue
(
label
:
$0
.
label
,
value
:
$0
.
value
)
}
// 日期信息
target
.
birthday
=
source
.
birthday
target
.
dates
=
source
.
dates
.
map
{
CNLabeledValue
(
label
:
$0
.
label
,
value
:
$0
.
value
)
}
// 备注
target
.
note
=
source
.
note
}
// MARK: 导入联系人
// 核心原子操作
static
func
performAtomicRestore
(
_
contacts
:
[
ContactModel
],
completion
:
@escaping
(
Bool
,
Error
?)
->
Void
)
{
do
{
let
contactStore
=
CNContactStore
()
// 1. 获取所有现有联系人ID
let
existingContactIDs
=
try
ContactManager
.
getAllContactIDs
()
// 2. 创建统一事务请求
let
saveRequest
=
CNSaveRequest
()
// 3. 添加删除旧联系人的操作
existingContactIDs
.
forEach
{
id
in
if
let
contact
=
try
?
contactStore
.
unifiedContact
(
withIdentifier
:
id
,
keysToFetch
:
[
CNContactIdentifierKey
as
CNKeyDescriptor
])
{
let
mutableContact
=
contact
.
mutableCopy
()
as!
CNMutableContact
saveRequest
.
delete
(
mutableContact
)
}
}
// 4. 添加新联系人
for
model
in
contacts
{
let
newContact
=
ContactManager
.
createCNContact
(
from
:
model
)
saveRequest
.
add
(
newContact
,
toContainerWithIdentifier
:
nil
)
}
// 5. 执行事务(原子性)
try
contactStore
.
execute
(
saveRequest
)
completion
(
true
,
nil
)
}
catch
{
// 事务失败,所有操作自动回滚
completion
(
false
,
error
)
}
}
private
static
func
getAllContactIDs
()
throws
->
[
String
]
{
let
contactStore
=
CNContactStore
()
let
request
=
CNContactFetchRequest
(
keysToFetch
:
[
CNContactIdentifierKey
as
CNKeyDescriptor
])
var
contactIDs
=
[
String
]()
try
contactStore
.
enumerateContacts
(
with
:
request
)
{
contact
,
_
in
contactIDs
.
append
(
contact
.
identifier
)
}
return
contactIDs
}
private
static
func
createCNContact
(
from
model
:
ContactModel
)
->
CNMutableContact
{
let
contact
=
CNMutableContact
()
contact
.
givenName
=
model
.
name
if
let
numbers
=
model
.
phoneNumber
{
contact
.
phoneNumbers
=
numbers
.
map
{
number
in
CNLabeledValue
(
label
:
CNLabelPhoneNumberMain
,
value
:
CNPhoneNumber
(
stringValue
:
number
))
}
}
return
contact
}
// MARK: 其他方法
static
func
callContactWithIdentifier
(
_
model
:
ContactModel
,
viewController
:
UIViewController
)
{
let
editViewController
=
CustomContactViewController
()
editViewController
.
presentCustomContactEditor
(
with
:
model
.
identifier
,
viewController
:
viewController
)
}
}
PhoneManager/Class/Session/Contact/View/All/ContactAllView.swift
View file @
c19fadbf
...
@@ -6,7 +6,10 @@
...
@@ -6,7 +6,10 @@
//
//
import
Foundation
import
Foundation
import
SnapKit
class
ContactAllView
:
UIView
{
class
ContactAllView
:
UIView
{
private
var
bottomConstraint
:
Constraint
?
var
dataSourceModel
:
[
ContactModel
]
=
[]
var
dataSourceModel
:
[
ContactModel
]
=
[]
...
@@ -59,10 +62,6 @@ class ContactAllView : UIView {
...
@@ -59,10 +62,6 @@ class ContactAllView : UIView {
lazy
var
deleteButton
:
DeleteButtonView
=
{
lazy
var
deleteButton
:
DeleteButtonView
=
{
let
deleteButton
=
DeleteButtonView
()
let
deleteButton
=
DeleteButtonView
()
// 设置删除按钮
deleteButton
.
layer
.
cornerRadius
=
23
deleteButton
.
clipsToBounds
=
true
deleteButton
.
isHidden
=
true
return
deleteButton
return
deleteButton
}()
}()
...
@@ -91,13 +90,12 @@ class ContactAllView : UIView {
...
@@ -91,13 +90,12 @@ class ContactAllView : UIView {
make
.
top
.
equalTo
(
self
.
subTitleLabel
.
snp
.
bottom
)
.
offset
(
16
*
RScreenH
())
make
.
top
.
equalTo
(
self
.
subTitleLabel
.
snp
.
bottom
)
.
offset
(
16
*
RScreenH
())
make
.
left
.
equalToSuperview
()
.
offset
(
15
*
RScreenW
())
make
.
left
.
equalToSuperview
()
.
offset
(
15
*
RScreenW
())
make
.
right
.
equalToSuperview
()
.
offset
(
-
15
*
RScreenW
())
make
.
right
.
equalToSuperview
()
.
offset
(
-
15
*
RScreenW
())
make
.
bottom
.
equalToSuperview
()
.
offset
(
-
102
*
RScreenH
()
)
make
.
bottom
.
equalToSuperview
()
.
offset
(
-
safeHeight
)
}
}
self
.
deleteButton
.
snp
.
makeConstraints
{
make
in
self
.
deleteButton
.
snp
.
makeConstraints
{
make
in
make
.
top
.
equalTo
(
self
.
tableView
.
snp
.
bottom
)
.
offset
(
16
*
RScreenH
())
make
.
left
.
right
.
equalToSuperview
()
make
.
width
.
equalTo
(
345
*
RScreenW
())
self
.
bottomConstraint
=
make
.
bottom
.
equalToSuperview
()
.
offset
(
68
)
.
constraint
make
.
height
.
equalTo
(
46
)
make
.
height
.
equalTo
(
68
)
make
.
centerX
.
equalToSuperview
()
}
}
// 排序
// 排序
...
@@ -125,6 +123,11 @@ extension ContactAllView : UITableViewDataSource,UITableViewDelegate {
...
@@ -125,6 +123,11 @@ extension ContactAllView : UITableViewDataSource,UITableViewDelegate {
return
sectionedContacts
[
sectionTitle
]?
.
count
??
0
return
sectionedContacts
[
sectionTitle
]?
.
count
??
0
}
}
func
tableView
(
_
tableView
:
UITableView
,
didSelectRowAt
indexPath
:
IndexPath
)
{
let
cell
=
tableView
.
cellForRow
(
at
:
indexPath
)
as!
CustomContactAllViewTableViewCell
cell
.
selectContact
(
cell
.
selectButton
)
}
func
tableView
(
_
tableView
:
UITableView
,
cellForRowAt
indexPath
:
IndexPath
)
->
UITableViewCell
{
func
tableView
(
_
tableView
:
UITableView
,
cellForRowAt
indexPath
:
IndexPath
)
->
UITableViewCell
{
let
cell
=
tableView
.
dequeueReusableCell
(
withIdentifier
:
"CustomContactAllViewTableViewCell"
,
for
:
indexPath
)
as!
CustomContactAllViewTableViewCell
let
cell
=
tableView
.
dequeueReusableCell
(
withIdentifier
:
"CustomContactAllViewTableViewCell"
,
for
:
indexPath
)
as!
CustomContactAllViewTableViewCell
let
sectionTitle
=
sectionTitles
[
indexPath
.
section
]
let
sectionTitle
=
sectionTitles
[
indexPath
.
section
]
...
@@ -148,11 +151,18 @@ extension ContactAllView : UITableViewDataSource,UITableViewDelegate {
...
@@ -148,11 +151,18 @@ extension ContactAllView : UITableViewDataSource,UITableViewDelegate {
// 判断button是否显示
// 判断button是否显示
if
self
.
selectedContacts
.
count
>
0
{
if
self
.
selectedContacts
.
count
>
0
{
// 设置button的title
// 设置button的title
self
.
deleteButton
.
titleLabel
.
text
=
"Delete
\(
self
.
selectedContacts
.
count
)
Contact"
self
.
deleteButton
.
deleteButton
.
setTitle
(
"Delete
\(
self
.
selectedContacts
.
count
)
Contact"
,
for
:
.
normal
)
self
.
deleteButton
.
isHidden
=
false
UIView
.
animate
(
withDuration
:
0.1
)
{
// 更新约束
self
.
bottomConstraint
?
.
update
(
offset
:
-
safeHeight
)
self
.
layoutIfNeeded
()
}
}
else
{
}
else
{
UIView
.
animate
(
withDuration
:
0.1
)
{
self
.
deleteButton
.
isHidden
=
true
// 更新约束
self
.
bottomConstraint
?
.
update
(
offset
:
68
)
self
.
layoutIfNeeded
()
}
}
}
}
}
...
@@ -269,8 +279,8 @@ extension ContactAllView : UITableViewDataSource,UITableViewDelegate {
...
@@ -269,8 +279,8 @@ extension ContactAllView : UITableViewDataSource,UITableViewDelegate {
// 备份成功
// 备份成功
success
()
success
()
DispatchQueue
.
main
.
async
{
DispatchQueue
.
main
.
async
{
let
buAlertVc
=
ContactBackUpCompletedAlertView
(
frame
:
(
self
.
responderViewController
()?
.
view
.
bounds
)
!
)
let
buAlertVc
=
ContactBackUpCompletedAlertView
(
frame
:
(
cWindow
?
.
bounds
)
!
)
self
.
responderViewController
()?
.
view
.
addSubview
(
buAlertVc
)
cWindow
?
.
addSubview
(
buAlertVc
)
DispatchQueue
.
main
.
asyncAfter
(
deadline
:
.
now
()
+
1
)
{
DispatchQueue
.
main
.
asyncAfter
(
deadline
:
.
now
()
+
1
)
{
buAlertVc
.
removeFromSuperview
()
buAlertVc
.
removeFromSuperview
()
}
}
...
...
PhoneManager/Class/Session/Contact/View/Bac/ContactBackUpNormalView.swift
View file @
c19fadbf
...
@@ -76,7 +76,7 @@ class ContactBackUpNormalView : UIView {
...
@@ -76,7 +76,7 @@ class ContactBackUpNormalView : UIView {
make
.
top
.
equalTo
(
self
.
addButon
.
snp
.
bottom
)
.
offset
(
16
)
make
.
top
.
equalTo
(
self
.
addButon
.
snp
.
bottom
)
.
offset
(
16
)
make
.
left
.
equalToSuperview
()
.
offset
(
15
*
RScreenW
())
make
.
left
.
equalToSuperview
()
.
offset
(
15
*
RScreenW
())
make
.
right
.
equalToSuperview
()
.
offset
(
-
15
*
RScreenW
())
make
.
right
.
equalToSuperview
()
.
offset
(
-
15
*
RScreenW
())
make
.
bottom
.
equalToSuperview
()
make
.
bottom
.
equalToSuperview
()
.
offset
(
-
safeHeight
)
}
}
}
}
}
}
...
@@ -125,8 +125,8 @@ extension ContactBackUpNormalView : UITableViewDelegate,UITableViewDataSource{
...
@@ -125,8 +125,8 @@ extension ContactBackUpNormalView : UITableViewDelegate,UITableViewDataSource{
}
}
DispatchQueue
.
main
.
async
{
DispatchQueue
.
main
.
async
{
// 弹框提示成功
// 弹框提示成功
let
buAlertVc
=
ContactBackUpDeleteCompletedAlertView
(
frame
:
(
self
.
responderViewController
()?
.
view
.
bounds
)
!
)
let
buAlertVc
=
ContactBackUpDeleteCompletedAlertView
(
frame
:
(
cWindow
?
.
bounds
)
!
)
self
.
responderViewController
()?
.
view
.
addSubview
(
buAlertVc
)
cWindow
?
.
addSubview
(
buAlertVc
)
DispatchQueue
.
main
.
asyncAfter
(
deadline
:
.
now
()
+
2
)
{
DispatchQueue
.
main
.
asyncAfter
(
deadline
:
.
now
()
+
2
)
{
buAlertVc
.
removeFromSuperview
()
buAlertVc
.
removeFromSuperview
()
}
}
...
@@ -168,8 +168,8 @@ extension ContactBackUpNormalView : UITableViewDelegate,UITableViewDataSource{
...
@@ -168,8 +168,8 @@ extension ContactBackUpNormalView : UITableViewDelegate,UITableViewDataSource{
let
vm
=
BackupViewModel
()
let
vm
=
BackupViewModel
()
vm
.
backupAllContacts
(
self
.
dataSourceAllModel
??
[])
{
finished
,
error
in
vm
.
backupAllContacts
(
self
.
dataSourceAllModel
??
[])
{
finished
,
error
in
DispatchQueue
.
main
.
async
{
DispatchQueue
.
main
.
async
{
let
buAlertVc
=
ContactBackUpCompletedAlertView
(
frame
:
(
self
.
responderViewController
()?
.
view
.
bounds
)
!
)
let
buAlertVc
=
ContactBackUpCompletedAlertView
(
frame
:
(
cWindow
?
.
bounds
)
!
)
self
.
responderViewController
()?
.
view
.
addSubview
(
buAlertVc
)
cWindow
?
.
addSubview
(
buAlertVc
)
DispatchQueue
.
main
.
asyncAfter
(
deadline
:
.
now
()
+
1
)
{
DispatchQueue
.
main
.
asyncAfter
(
deadline
:
.
now
()
+
1
)
{
buAlertVc
.
removeFromSuperview
()
buAlertVc
.
removeFromSuperview
()
}
}
...
...
PhoneManager/Class/Session/Contact/View/Cell/CustomContactAllViewTableViewCell.swift
View file @
c19fadbf
...
@@ -72,7 +72,7 @@ class CustomContactAllViewTableViewCell : UITableViewCell {
...
@@ -72,7 +72,7 @@ class CustomContactAllViewTableViewCell : UITableViewCell {
}
}
extension
CustomContactAllViewTableViewCell
{
extension
CustomContactAllViewTableViewCell
{
@objc
private
func
selectContact
(
_
sender
:
UIButton
)
{
@objc
func
selectContact
(
_
sender
:
UIButton
)
{
sender
.
isSelected
=
!
sender
.
isSelected
sender
.
isSelected
=
!
sender
.
isSelected
buttonSelectCallBack
(
model
!
,
sender
.
isSelected
)
buttonSelectCallBack
(
model
!
,
sender
.
isSelected
)
}
}
...
...
PhoneManager/Class/Session/Contact/View/Cell/CustomContactDupPreTableViewCell.swift
0 → 100644
View file @
c19fadbf
//
// CustomContactDupTableViewCell.swift
// PhoneManager
//
// Created by edy on 2025/5/6.
//
import
Foundation
class
CustomContactDupPreTableViewCell
:
UITableViewCell
{
var
indexPath
:
IndexPath
?
var
cellSelectCallCack
:
(
IndexPath
,
Bool
)
->
Void
=
{
index
,
choose
in
}
var
model
:
[
ContactModel
]
=
[]
{
didSet
{
DispatchQueue
.
main
.
async
{
if
self
.
model
.
count
>
0
{
self
.
nameLabel
.
text
=
self
.
model
.
first
?
.
name
self
.
numberLabel
.
text
=
self
.
getPhoneNumberString
(
contacts
:
self
.
model
)
}
else
{
self
.
nameLabel
.
text
=
""
self
.
numberLabel
.
text
=
""
}
}
}
}
lazy
var
backView
:
UIView
=
{
let
view
=
UIView
()
view
.
backgroundColor
=
UIColor
(
red
:
0.95
,
green
:
0.96
,
blue
:
0.99
,
alpha
:
1
)
view
.
clipsToBounds
=
true
view
.
layer
.
cornerRadius
=
12
return
view
}()
lazy
var
nameLabel
:
UILabel
=
{
let
label
=
UILabel
()
label
.
textColor
=
UIColor
(
red
:
0.2
,
green
:
0.2
,
blue
:
0.2
,
alpha
:
1
)
label
.
font
=
UIFont
.
systemFont
(
ofSize
:
16
,
weight
:
.
bold
)
return
label
}()
lazy
var
numberLabel
:
UILabel
=
{
let
label
=
UILabel
()
label
.
textColor
=
UIColor
(
red
:
0.4
,
green
:
0.4
,
blue
:
0.4
,
alpha
:
1
)
label
.
font
=
UIFont
.
systemFont
(
ofSize
:
14
,
weight
:
.
regular
)
return
label
}()
lazy
var
moreButton
:
UIButton
=
{
let
button
=
UIButton
(
type
:
.
custom
)
button
.
setImage
(
UIImage
(
named
:
"icon_left_setting_grey"
),
for
:
.
normal
)
return
button
}()
override
init
(
style
:
UITableViewCell
.
CellStyle
,
reuseIdentifier
:
String
?)
{
super
.
init
(
style
:
style
,
reuseIdentifier
:
reuseIdentifier
)
self
.
selectionStyle
=
.
none
self
.
contentView
.
addSubview
(
self
.
backView
)
self
.
backView
.
addSubview
(
self
.
nameLabel
)
self
.
backView
.
addSubview
(
self
.
numberLabel
)
self
.
backView
.
addSubview
(
self
.
moreButton
)
self
.
backView
.
snp
.
makeConstraints
{
make
in
make
.
left
.
right
.
top
.
equalToSuperview
()
make
.
height
.
equalTo
(
71
)
}
self
.
nameLabel
.
snp
.
makeConstraints
{
make
in
make
.
left
.
equalToSuperview
()
.
offset
(
16
*
RScreenH
())
make
.
top
.
equalToSuperview
()
.
offset
(
16
)
make
.
width
.
equalTo
(
210
)
make
.
height
.
equalTo
(
22
)
}
self
.
numberLabel
.
snp
.
makeConstraints
{
make
in
make
.
left
.
equalToSuperview
()
.
offset
(
16
*
RScreenH
())
make
.
top
.
equalTo
(
self
.
nameLabel
.
snp
.
bottom
)
.
offset
(
0
)
make
.
right
.
equalTo
(
self
.
moreButton
.
snp
.
left
)
.
offset
(
0
)
make
.
height
.
equalTo
(
20
)
}
self
.
moreButton
.
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
CustomContactDupPreTableViewCell
{
func
getPhoneNumberString
(
contacts
:
[
ContactModel
])
->
String
{
var
photoNumbers
:
[
String
]
=
[]
for
item
in
contacts
{
if
let
tempNumbers
=
item
.
phoneNumber
{
photoNumbers
=
photoNumbers
+
tempNumbers
}
}
let
nonEmptyStrings
=
photoNumbers
.
filter
{
!
$0
.
isEmpty
}
if
nonEmptyStrings
.
count
<=
0
{
return
""
}
return
photoNumbers
.
joined
(
separator
:
" / "
)
}
}
PhoneManager/Class/Session/Contact/View/Cell/CustomContactDupTableViewCell.swift
View file @
c19fadbf
...
@@ -81,7 +81,7 @@ class CustomContactDupTableViewCell : UITableViewCell {
...
@@ -81,7 +81,7 @@ class CustomContactDupTableViewCell : UITableViewCell {
self
.
numberLabel
.
snp
.
makeConstraints
{
make
in
self
.
numberLabel
.
snp
.
makeConstraints
{
make
in
make
.
left
.
equalToSuperview
()
.
offset
(
16
*
RScreenH
())
make
.
left
.
equalToSuperview
()
.
offset
(
16
*
RScreenH
())
make
.
top
.
equalTo
(
self
.
nameLabel
.
snp
.
bottom
)
.
offset
(
0
)
make
.
top
.
equalTo
(
self
.
nameLabel
.
snp
.
bottom
)
.
offset
(
0
)
make
.
width
.
equalTo
(
21
0
)
make
.
right
.
equalTo
(
self
.
selectButton
.
snp
.
left
)
.
offset
(
0
)
make
.
height
.
equalTo
(
20
)
make
.
height
.
equalTo
(
20
)
}
}
...
@@ -100,7 +100,7 @@ class CustomContactDupTableViewCell : UITableViewCell {
...
@@ -100,7 +100,7 @@ class CustomContactDupTableViewCell : UITableViewCell {
}
}
extension
CustomContactDupTableViewCell
{
extension
CustomContactDupTableViewCell
{
@objc
private
func
selectContact
(
_
sender
:
UIButton
)
{
@objc
func
selectContact
(
_
sender
:
UIButton
)
{
sender
.
isSelected
=
!
sender
.
isSelected
sender
.
isSelected
=
!
sender
.
isSelected
if
let
indexPath
=
self
.
indexPath
{
if
let
indexPath
=
self
.
indexPath
{
self
.
cellSelectCallCack
(
indexPath
,
sender
.
isSelected
)
self
.
cellSelectCallCack
(
indexPath
,
sender
.
isSelected
)
...
...
PhoneManager/Class/Session/Contact/View/Cell/CustomDupPreHeaderView.swift
0 → 100644
View file @
c19fadbf
//
// CustomDupHeaderView.swift
// PhoneManager
//
// Created by edy on 2025/5/7.
//
import
Foundation
class
CustomDupPreHeaderView
:
UITableViewHeaderFooterView
{
var
model
:
[
ContactModel
]
=
[]
{
didSet
{
DispatchQueue
.
main
.
async
{
self
.
tiplabel
.
text
=
"
\(
self
.
model
.
count
)
Contacts Merged"
}
}
}
// 当前的section
var
currentSection
:
Int
=
0
var
headerCallback
:
([
ContactModel
],
Int
)
->
Void
=
{
array
,
currentSection
in
}
var
selectStatus
:
Bool
=
false
lazy
var
tiplabel
:
UILabel
=
{
let
label
=
UILabel
()
label
.
text
=
"
\(
self
.
model
.
count
)
Contacts Merged"
label
.
textAlignment
=
.
left
label
.
font
=
UIFont
.
systemFont
(
ofSize
:
16
,
weight
:
.
medium
)
label
.
textColor
=
UIColor
(
red
:
0.4
,
green
:
0.4
,
blue
:
0.4
,
alpha
:
1
)
return
label
}()
lazy
var
deSelectButton
:
UIButton
=
{
let
button
=
UIButton
(
type
:
.
custom
)
button
.
setImage
(
UIImage
(
named
:
"ic_close_similar"
),
for
:
.
normal
)
button
.
addTarget
(
self
,
action
:
#selector(
removeCurrentData
)
,
for
:
.
touchUpInside
)
return
button
}()
override
init
(
reuseIdentifier
:
String
?)
{
super
.
init
(
reuseIdentifier
:
reuseIdentifier
)
self
.
backgroundColor
=
.
clear
self
.
addSubview
(
self
.
tiplabel
)
self
.
tiplabel
.
snp
.
makeConstraints
{
make
in
make
.
left
.
top
.
equalToSuperview
()
make
.
width
.
equalTo
(
200
)
make
.
height
.
equalTo
(
22
)
}
self
.
addSubview
(
self
.
deSelectButton
)
self
.
deSelectButton
.
snp
.
makeConstraints
{
make
in
make
.
width
.
equalTo
(
20
)
make
.
height
.
equalTo
(
20
)
make
.
right
.
equalToSuperview
()
make
.
centerY
.
equalTo
(
self
.
tiplabel
.
snp
.
centerY
)
}
}
required
init
?(
coder
:
NSCoder
)
{
fatalError
(
"init(coder:) has not been implemented"
)
}
}
extension
CustomDupPreHeaderView
{
@objc
func
removeCurrentData
(){
self
.
headerCallback
(
self
.
model
,
currentSection
)
}
}
PhoneManager/Class/Session/Contact/View/Common/AlertView/ContactBackUpCompletedAlertView.swift
View file @
c19fadbf
...
@@ -8,7 +8,6 @@
...
@@ -8,7 +8,6 @@
import
Foundation
import
Foundation
class
ContactBackUpCompletedAlertView
:
UIView
{
class
ContactBackUpCompletedAlertView
:
UIView
{
var
sureCallBack
:
()
->
Void
=
{}
// 懒加载背景视图
// 懒加载背景视图
private
lazy
var
backgroundView
:
UIView
=
{
private
lazy
var
backgroundView
:
UIView
=
{
...
...
PhoneManager/Class/Session/Contact/View/Common/AlertView/ContactRestoreSuccessView.swift
0 → 100644
View file @
c19fadbf
//
// ContactRestoreSuccessView.swift
// PhoneManager
//
// Created by edy on 2025/5/8.
//
import
Foundation
class
ContactRestoreSuccessView
:
UIView
{
// 懒加载背景视图
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
=
"Backup restored"
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
)
}
}
}
PhoneManager/Class/Session/Contact/View/Common/DeleteButtonView.swift
View file @
c19fadbf
...
@@ -10,43 +10,40 @@ class DeleteButtonView : UIView {
...
@@ -10,43 +10,40 @@ class DeleteButtonView : UIView {
var
submitCallBack
:
(()
->
Void
)
=
{}
var
submitCallBack
:
(()
->
Void
)
=
{}
lazy
var
imageView
:
UIImageView
=
{
lazy
var
deleteButton
:
UIButton
=
{
let
imageView
=
UIImageView
()
let
view
=
UIButton
(
type
:
.
custom
)
imageView
.
image
=
UIImage
(
named
:
"ic_delete_duplicates"
)
view
.
backgroundColor
=
UIColor
(
red
:
0
,
green
:
0.51
,
blue
:
1
,
alpha
:
1
)
return
imageView
view
.
setTitleColor
(
.
white
,
for
:
.
normal
)
}()
view
.
clipsToBounds
=
true
view
.
layer
.
cornerRadius
=
23
lazy
var
titleLabel
:
UILabel
=
{
view
.
titleLabel
?
.
font
=
UIFont
.
systemFont
(
ofSize
:
16
,
weight
:
.
bold
)
let
label
=
UILabel
()
view
.
setTitle
(
"See Merge Preview"
,
for
:
.
normal
)
label
.
textAlignment
=
.
left
view
.
setImage
(
UIImage
(
named
:
"ic_hebing"
),
for
:
.
normal
)
label
.
textColor
=
UIColor
(
red
:
1
,
green
:
1
,
blue
:
1
,
alpha
:
1
)
return
label
// 设置间距为 8
let
spacing
:
CGFloat
=
8
// 获取图片和文字的大小
let
imageSize
=
view
.
imageView
?
.
image
?
.
size
??
.
zero
let
titleSize
=
view
.
titleLabel
?
.
intrinsicContentSize
??
.
zero
// 计算 imageEdgeInsets 和 titleEdgeInsets
view
.
imageEdgeInsets
=
UIEdgeInsets
(
top
:
0
,
left
:
-
spacing
/
2
,
bottom
:
0
,
right
:
spacing
/
2
)
view
.
titleEdgeInsets
=
UIEdgeInsets
(
top
:
0
,
left
:
spacing
/
2
,
bottom
:
0
,
right
:
-
spacing
/
2
)
view
.
addTarget
(
self
,
action
:
#selector(
deleteButtonAction
)
,
for
:
.
touchUpInside
)
return
view
}()
}()
override
init
(
frame
:
CGRect
)
{
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
)
super
.
init
(
frame
:
frame
)
self
.
backgroundColor
=
.
white
self
.
addSubview
(
self
.
deleteButton
)
self
.
imageView
.
snp
.
makeConstraints
{
make
in
self
.
deleteButton
.
snp
.
makeConstraints
{
make
in
make
.
width
.
height
.
equalTo
(
20
)
make
.
left
.
equalToSuperview
()
.
offset
(
15
)
make
.
centerY
.
equalToSuperview
()
make
.
right
.
equalToSuperview
()
.
offset
(
-
15
)
make
.
left
.
equalToSuperview
()
.
offset
(
94
*
RScreenW
())
make
.
height
.
equalTo
(
46
)
}
make
.
top
.
equalToSuperview
()
.
offset
(
16
)
self
.
titleLabel
.
snp
.
makeConstraints
{
make
in
make
.
left
.
equalTo
(
self
.
imageView
.
snp
.
right
)
.
offset
(
8
*
RScreenW
())
make
.
centerY
.
equalToSuperview
()
make
.
height
.
equalTo
(
22
)
}
}
}
}
...
@@ -60,7 +57,7 @@ class DeleteButtonView : UIView {
...
@@ -60,7 +57,7 @@ class DeleteButtonView : UIView {
extension
DeleteButtonView
{
extension
DeleteButtonView
{
@objc
func
submit
Action
(){
@objc
func
deleteButton
Action
(){
submitCallBack
()
submitCallBack
()
}
}
}
}
PhoneManager/Class/Session/Contact/View/Dup/ContactDupNormalView.swift
View file @
c19fadbf
...
@@ -6,21 +6,28 @@
...
@@ -6,21 +6,28 @@
//
//
import
Foundation
import
Foundation
import
SnapKit
class
ContactDupNormalView
:
UIView
{
class
ContactDupNormalView
:
UIView
{
var
dataSourceModel
:
[[
ContactModel
]]
=
[]
var
dataSourceModel
:
[[
ContactModel
]]
=
[]
var
selectData
:
[
String
:
[
ContactModel
]]
=
[:]
var
preButtonShowStatus
:
Bool
=
false
var
selectData
:
[
String
:
[
ContactModel
]]
=
[:]
{
didSet
{
// 是否更新底部合并预览按钮
showPreMergeButton
()
}
}
var
dataChangeCallBack
:(
Bool
)
->
Void
=
{
changed
in
}
var
dataChangeCallBack
:(
Bool
)
->
Void
=
{
changed
in
}
/// 选择的联系人
private
var
bottomConstraint
:
Constraint
?
private
var
selectedContacts
:
[
ContactModel
]
=
[]
lazy
var
titleLabel
:
UILabel
=
{
lazy
var
titleLabel
:
UILabel
=
{
let
label
=
UILabel
()
let
label
=
UILabel
()
label
.
text
=
"
All contact
s"
label
.
text
=
"
Duplicate
s"
label
.
font
=
UIFont
.
systemFont
(
ofSize
:
20
,
weight
:
.
bold
)
label
.
font
=
UIFont
.
systemFont
(
ofSize
:
20
,
weight
:
.
bold
)
label
.
textColor
=
UIColor
(
red
:
0.2
,
green
:
0.2
,
blue
:
0.2
,
alpha
:
1
)
label
.
textColor
=
UIColor
(
red
:
0.2
,
green
:
0.2
,
blue
:
0.2
,
alpha
:
1
)
label
.
textAlignment
=
.
left
label
.
textAlignment
=
.
left
...
@@ -50,11 +57,18 @@ class ContactDupNormalView : UIView {
...
@@ -50,11 +57,18 @@ class ContactDupNormalView : UIView {
return
tableView
return
tableView
}()
}()
lazy
var
preButtonView
:
MergePreButtonView
=
{
let
view
=
MergePreButtonView
()
return
view
}()
override
init
(
frame
:
CGRect
)
{
override
init
(
frame
:
CGRect
)
{
super
.
init
(
frame
:
frame
)
super
.
init
(
frame
:
frame
)
self
.
addSubview
(
self
.
titleLabel
)
self
.
addSubview
(
self
.
titleLabel
)
self
.
addSubview
(
self
.
subTitleLabel
)
self
.
addSubview
(
self
.
subTitleLabel
)
self
.
addSubview
(
self
.
tableView
)
self
.
addSubview
(
self
.
tableView
)
self
.
addSubview
(
self
.
preButtonView
)
self
.
titleLabel
.
snp
.
makeConstraints
{
make
in
self
.
titleLabel
.
snp
.
makeConstraints
{
make
in
make
.
left
.
equalToSuperview
()
.
offset
(
15
*
RScreenW
())
make
.
left
.
equalToSuperview
()
.
offset
(
15
*
RScreenW
())
...
@@ -74,7 +88,19 @@ class ContactDupNormalView : UIView {
...
@@ -74,7 +88,19 @@ class ContactDupNormalView : UIView {
make
.
top
.
equalTo
(
self
.
subTitleLabel
.
snp
.
bottom
)
.
offset
(
16
*
RScreenH
())
make
.
top
.
equalTo
(
self
.
subTitleLabel
.
snp
.
bottom
)
.
offset
(
16
*
RScreenH
())
make
.
left
.
equalToSuperview
()
.
offset
(
15
*
RScreenW
())
make
.
left
.
equalToSuperview
()
.
offset
(
15
*
RScreenW
())
make
.
right
.
equalToSuperview
()
.
offset
(
-
15
*
RScreenW
())
make
.
right
.
equalToSuperview
()
.
offset
(
-
15
*
RScreenW
())
make
.
bottom
.
equalToSuperview
()
.
offset
(
-
44
*
RScreenH
())
make
.
bottom
.
equalToSuperview
()
.
offset
(
-
safeHeight
)
}
self
.
preButtonView
.
snp
.
makeConstraints
{
make
in
make
.
left
.
right
.
equalToSuperview
()
self
.
bottomConstraint
=
make
.
bottom
.
equalToSuperview
()
.
offset
(
68
)
.
constraint
make
.
height
.
equalTo
(
68
)
}
self
.
preButtonView
.
mergePreCallBack
=
{
let
vc
=
ContactsDupPreViewController
()
vc
.
dataSourceModel
=
self
.
selectData
self
.
responderViewController
()?
.
navigationController
?
.
pushViewController
(
vc
,
animated
:
true
)
}
}
}
}
...
@@ -155,6 +181,16 @@ extension ContactDupNormalView : UITableViewDelegate,UITableViewDataSource{
...
@@ -155,6 +181,16 @@ extension ContactDupNormalView : UITableViewDelegate,UITableViewDataSource{
return
34
return
34
}
}
func
tableView
(
_
tableView
:
UITableView
,
didSelectRowAt
indexPath
:
IndexPath
)
{
// 调起系统弹窗
let
cell
=
tableView
.
cellForRow
(
at
:
indexPath
)
as!
CustomContactDupTableViewCell
if
let
model
=
cell
.
model
{
if
let
vc
=
self
.
responderViewController
()
{
ContactManager
.
callContactWithIdentifier
(
model
,
viewController
:
vc
)
}
}
}
// MARK: 辅助方法
// MARK: 辅助方法
...
@@ -215,6 +251,40 @@ extension ContactDupNormalView : UITableViewDelegate,UITableViewDataSource{
...
@@ -215,6 +251,40 @@ extension ContactDupNormalView : UITableViewDelegate,UITableViewDataSource{
}
}
return
false
return
false
}
}
// 是否显示预览按钮
func
showPreMergeButton
(){
var
show
:
Bool
=
false
for
(
_
,
value
)
in
self
.
selectData
{
// 判断至少有一组且大于2的开始合并
if
value
.
count
>=
2
{
show
=
true
}
}
DispatchQueue
.
main
.
asyncAfter
(
deadline
:
.
now
()
+
0.1
)
{
// 如果有且当前是显示的
if
show
{
if
self
.
preButtonShowStatus
==
false
{
self
.
preButtonShowStatus
=
true
UIView
.
animate
(
withDuration
:
0.1
)
{
// 更新约束
self
.
bottomConstraint
?
.
update
(
offset
:
-
safeHeight
)
self
.
layoutIfNeeded
()
}
}
}
else
{
if
self
.
preButtonShowStatus
==
true
{
self
.
preButtonShowStatus
=
false
UIView
.
animate
(
withDuration
:
0.1
)
{
// 更新约束
self
.
bottomConstraint
?
.
update
(
offset
:
68
)
self
.
layoutIfNeeded
()
}
}
}
}
}
}
}
PhoneManager/Class/Session/Contact/View/Dup/ContactDupPreNormalView.swift
0 → 100644
View file @
c19fadbf
//
// ContactDupPreNormalView.swift
// PhoneManager
//
// Created by edy on 2025/5/8.
//
import
Foundation
class
ContactDupPreNormalView
:
UIView
{
static
let
CONTACT_MERGED
=
"contact_merged"
var
dataSourceModel
:
[[
ContactModel
]]
=
[]
var
preButtonShowStatus
:
Bool
=
false
// 如果将不想合并的重复联系人删除完了,就显示没有预览可用页面
var
dataChangeCallBack
:
(
Bool
)
->
Void
=
{
isClear
in
}
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
=
"
\(
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
(
CustomContactDupPreTableViewCell
.
self
,
forCellReuseIdentifier
:
"CustomContactDupPreTableViewCell"
)
tableView
.
separatorStyle
=
.
none
tableView
.
backgroundColor
=
.
clear
tableView
.
showsVerticalScrollIndicator
=
false
if
#available(iOS 15.0, *)
{
tableView
.
sectionHeaderTopPadding
=
0
}
return
tableView
}()
lazy
var
mergeButtonView
:
MergeButtonView
=
{
let
view
=
MergeButtonView
()
return
view
}()
override
init
(
frame
:
CGRect
)
{
super
.
init
(
frame
:
frame
)
self
.
addSubview
(
self
.
titleLabel
)
self
.
addSubview
(
self
.
subTitleLabel
)
self
.
addSubview
(
self
.
tableView
)
self
.
addSubview
(
self
.
mergeButtonView
)
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
(
-
safeHeight
-
68
)
}
self
.
mergeButtonView
.
snp
.
makeConstraints
{
make
in
make
.
left
.
right
.
equalToSuperview
()
make
.
bottom
.
equalToSuperview
()
.
offset
(
-
safeHeight
)
make
.
height
.
equalTo
(
68
)
}
self
.
mergeButtonView
.
mergeCallBack
=
{
ContactManager
.
mergeContacts
(
groups
:
self
.
dataSourceModel
)
{
result
in
switch
result
{
case
.
success
(
let
newIDs
):
print
(
"新联系人ID:
\(
newIDs
)
"
)
DispatchQueue
.
main
.
async
{
self
.
removeData
()
// 弹框提示成功
let
buAlertVc
=
ContactBackUpDeleteCompletedAlertView
(
frame
:
(
cWindow
?
.
bounds
)
!
)
cWindow
?
.
addSubview
(
buAlertVc
)
DispatchQueue
.
main
.
asyncAfter
(
deadline
:
.
now
()
+
2
)
{
buAlertVc
.
removeFromSuperview
()
}
}
break
case
.
failure
(
let
error
):
print
(
"合并失败:
\(
error
.
localizedDescription
)
"
)
}
}
}
}
required
init
?(
coder
:
NSCoder
)
{
fatalError
(
"init(coder:) has not been implemented"
)
}
/// 合并成功后移除数据源
func
removeData
(){
self
.
dataSourceModel
.
removeAll
()
self
.
tableView
.
reloadData
()
// 更新下头部数据
self
.
subTitleLabel
.
text
=
"
\(
self
.
dataSourceModel
.
count
)
Contacts"
self
.
dataChangeCallBack
(
true
)
// 发起通知
let
dataUpdated
=
Notification
.
Name
(
ContactDupPreNormalView
.
CONTACT_MERGED
)
NotificationCenter
.
default
.
post
(
name
:
dataUpdated
,
object
:
nil
,
userInfo
:
nil
)
}
}
extension
ContactDupPreNormalView
:
UITableViewDelegate
,
UITableViewDataSource
{
func
numberOfSections
(
in
tableView
:
UITableView
)
->
Int
{
return
self
.
dataSourceModel
.
count
}
func
tableView
(
_
tableView
:
UITableView
,
numberOfRowsInSection
section
:
Int
)
->
Int
{
return
1
}
func
tableView
(
_
tableView
:
UITableView
,
didSelectRowAt
indexPath
:
IndexPath
)
{
// 调起系统弹窗
let
cell
=
tableView
.
cellForRow
(
at
:
indexPath
)
as!
CustomContactDupPreTableViewCell
if
let
model
=
cell
.
model
.
first
{
if
let
vc
=
self
.
responderViewController
()
{
ContactManager
.
callContactWithIdentifier
(
model
,
viewController
:
vc
)
}
}
}
func
tableView
(
_
tableView
:
UITableView
,
cellForRowAt
indexPath
:
IndexPath
)
->
UITableViewCell
{
let
cell
=
tableView
.
dequeueReusableCell
(
withIdentifier
:
"CustomContactDupPreTableViewCell"
,
for
:
indexPath
)
as!
CustomContactDupPreTableViewCell
cell
.
indexPath
=
indexPath
cell
.
model
=
self
.
dataSourceModel
[
indexPath
.
section
]
return
cell
}
func
tableView
(
_
tableView
:
UITableView
,
heightForRowAt
indexPath
:
IndexPath
)
->
CGFloat
{
return
77
+
8
*
RScreenH
()
}
func
tableView
(
_
tableView
:
UITableView
,
viewForHeaderInSection
section
:
Int
)
->
UIView
?
{
let
view
=
CustomDupPreHeaderView
(
frame
:
CGRect
(
x
:
0
,
y
:
0
,
width
:
self
.
tableView
.
width
,
height
:
22
))
view
.
model
=
self
.
dataSourceModel
[
section
]
view
.
currentSection
=
section
view
.
headerCallback
=
{
array
,
currentSection
in
self
.
dataSourceModel
.
remove
(
at
:
currentSection
)
DispatchQueue
.
main
.
async
{
self
.
tableView
.
reloadData
()
self
.
subTitleLabel
.
text
=
"
\(
self
.
dataSourceModel
.
count
)
Contacts"
if
self
.
dataSourceModel
.
count
==
0
{
self
.
dataChangeCallBack
(
true
)
}
}
}
return
view
}
func
tableView
(
_
tableView
:
UITableView
,
heightForHeaderInSection
section
:
Int
)
->
CGFloat
{
return
34
}
}
PhoneManager/Class/Session/Contact/View/Dup/ContactNoDupPreView.swift
0 → 100644
View file @
c19fadbf
//
// ContactNoDupPreView.swift
// PhoneManager
//
// Created by edy on 2025/5/8.
//
import
Foundation
class
ContactNoDupPreView
:
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 Preview Available"
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 back and select duplicate contacts to see a preview"
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"
)
}
}
PhoneManager/Class/Session/Contact/View/Dup/MergeButtonView.swift
0 → 100644
View file @
c19fadbf
//
// MergeButtonView.swift
// PhoneManager
//
// Created by edy on 2025/5/8.
//
import
Foundation
import
Contacts
class
MergeButtonView
:
UIView
{
var
mergeCallBack
:
()
->
Void
=
{}
lazy
var
mergeButton
:
UIButton
=
{
let
view
=
UIButton
(
type
:
.
custom
)
view
.
backgroundColor
=
UIColor
(
red
:
0
,
green
:
0.51
,
blue
:
1
,
alpha
:
1
)
view
.
setTitleColor
(
.
white
,
for
:
.
normal
)
view
.
clipsToBounds
=
true
view
.
layer
.
cornerRadius
=
23
view
.
titleLabel
?
.
font
=
UIFont
.
systemFont
(
ofSize
:
16
,
weight
:
.
bold
)
view
.
setTitle
(
"Merge 0 Contacts"
,
for
:
.
normal
)
view
.
setImage
(
UIImage
(
named
:
"ic_hebing"
),
for
:
.
normal
)
// 设置间距为 8
let
spacing
:
CGFloat
=
8
// 获取图片和文字的大小
let
imageSize
=
view
.
imageView
?
.
image
?
.
size
??
.
zero
let
titleSize
=
view
.
titleLabel
?
.
intrinsicContentSize
??
.
zero
// 计算 imageEdgeInsets 和 titleEdgeInsets
view
.
imageEdgeInsets
=
UIEdgeInsets
(
top
:
0
,
left
:
-
spacing
/
2
,
bottom
:
0
,
right
:
spacing
/
2
)
view
.
titleEdgeInsets
=
UIEdgeInsets
(
top
:
0
,
left
:
spacing
/
2
,
bottom
:
0
,
right
:
-
spacing
/
2
)
view
.
addTarget
(
self
,
action
:
#selector(
merge
)
,
for
:
.
touchUpInside
)
return
view
}()
override
init
(
frame
:
CGRect
)
{
super
.
init
(
frame
:
frame
)
self
.
backgroundColor
=
.
white
self
.
addSubview
(
self
.
mergeButton
)
self
.
mergeButton
.
snp
.
makeConstraints
{
make
in
make
.
left
.
equalToSuperview
()
.
offset
(
15
)
make
.
right
.
equalToSuperview
()
.
offset
(
-
15
)
make
.
height
.
equalTo
(
46
)
make
.
top
.
equalToSuperview
()
.
offset
(
16
)
}
}
required
init
?(
coder
:
NSCoder
)
{
fatalError
(
"init(coder:) has not been implemented"
)
}
}
extension
MergeButtonView
{
@objc
func
merge
(){
// 弹出提示框
alertWhenMergeContact
()
}
func
alertWhenMergeContact
()
{
// 删除之前弹出是否需要备份
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
.
mergeCallBack
()
}
}
else
{
// 直接合并
self
.
mergeCallBack
()
}
}
}
func
backupContactsByselect
(
success
:
@escaping
()
->
Void
){
// 直接备份所有联系人
let
vm
=
BackupViewModel
()
// 获取所有联系人
let
store
=
CNContactStore
()
let
keysToFetch
=
[
CNContactGivenNameKey
as
CNKeyDescriptor
,
CNContactFamilyNameKey
as
CNKeyDescriptor
,
CNContactPhoneNumbersKey
as
CNKeyDescriptor
]
do
{
let
request
=
CNContactFetchRequest
(
keysToFetch
:
keysToFetch
)
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
)
allContacts
.
append
(
model
)
}
vm
.
backupPartialContacts
(
allContacts
)
{
finised
,
error
in
if
finised
{
// 备份成功
success
()
DispatchQueue
.
main
.
async
{
let
buAlertVc
=
ContactBackUpCompletedAlertView
(
frame
:
(
cWindow
?
.
bounds
)
!
)
cWindow
?
.
addSubview
(
buAlertVc
)
DispatchQueue
.
main
.
asyncAfter
(
deadline
:
.
now
()
+
1
)
{
buAlertVc
.
removeFromSuperview
()
}
}
}
else
{
Print
(
"备份失败"
)
}
}
}
catch
{
DispatchQueue
.
main
.
async
{
print
(
"获取联系人信息时发生错误:
\(
error
)
"
)
}
}
}
}
PhoneManager/Class/Session/Contact/View/Dup/MergePreButtonView.swift
0 → 100644
View file @
c19fadbf
//
// MergePreButtonView.swift
// PhoneManager
//
// Created by edy on 2025/5/8.
//
import
Foundation
class
MergePreButtonView
:
UIView
{
var
mergePreCallBack
:
()
->
Void
=
{}
lazy
var
mergePreButton
:
UIButton
=
{
let
view
=
UIButton
(
type
:
.
custom
)
view
.
backgroundColor
=
UIColor
(
red
:
0
,
green
:
0.51
,
blue
:
1
,
alpha
:
1
)
view
.
setTitleColor
(
.
white
,
for
:
.
normal
)
view
.
clipsToBounds
=
true
view
.
layer
.
cornerRadius
=
23
view
.
titleLabel
?
.
font
=
UIFont
.
systemFont
(
ofSize
:
16
,
weight
:
.
bold
)
view
.
setTitle
(
"See Merge Preview"
,
for
:
.
normal
)
view
.
setImage
(
UIImage
(
named
:
"ic_hebing"
),
for
:
.
normal
)
// 设置间距为 8
let
spacing
:
CGFloat
=
8
// 获取图片和文字的大小
let
imageSize
=
view
.
imageView
?
.
image
?
.
size
??
.
zero
let
titleSize
=
view
.
titleLabel
?
.
intrinsicContentSize
??
.
zero
// 计算 imageEdgeInsets 和 titleEdgeInsets
view
.
imageEdgeInsets
=
UIEdgeInsets
(
top
:
0
,
left
:
-
spacing
/
2
,
bottom
:
0
,
right
:
spacing
/
2
)
view
.
titleEdgeInsets
=
UIEdgeInsets
(
top
:
0
,
left
:
spacing
/
2
,
bottom
:
0
,
right
:
-
spacing
/
2
)
view
.
addTarget
(
self
,
action
:
#selector(
mergePre
)
,
for
:
.
touchUpInside
)
return
view
}()
override
init
(
frame
:
CGRect
)
{
super
.
init
(
frame
:
frame
)
self
.
backgroundColor
=
.
white
self
.
addSubview
(
self
.
mergePreButton
)
self
.
mergePreButton
.
snp
.
makeConstraints
{
make
in
make
.
left
.
equalToSuperview
()
.
offset
(
15
)
make
.
right
.
equalToSuperview
()
.
offset
(
-
15
)
make
.
height
.
equalTo
(
46
)
make
.
top
.
equalToSuperview
()
.
offset
(
16
)
}
}
required
init
?(
coder
:
NSCoder
)
{
fatalError
(
"init(coder:) has not been implemented"
)
}
}
extension
MergePreButtonView
{
@objc
func
mergePre
(){
self
.
mergePreCallBack
()
}
}
PhoneManager/Class/Session/Contact/View/Inc/ContactNormalIncomView.swift
View file @
c19fadbf
...
@@ -6,17 +6,17 @@
...
@@ -6,17 +6,17 @@
//
//
import
Foundation
import
Foundation
import
SnapKit
class
ContactNormalIncomView
:
UIView
{
class
ContactNormalIncomView
:
UIView
{
var
dataSourceModel
:
[
ContactModel
]
=
[]
/// 分组后的联系人
private
var
bottomConstraint
:
Constraint
?
private
var
sectionedContacts
:
[
String
:
[
ContactModel
]]
=
[:]
var
dataSourceModel
:
[
ContactModel
]
=
[]
var
dataClearCallBack
:
()
->
Void
=
{}
/// 联系人首字母数组
private
var
sectionTitles
:
[
String
]
=
[]
/// 选择的联系人
/// 选择的联系人
var
selectedContacts
:
[
ContactModel
]
=
[]
var
selectedContacts
:
[
ContactModel
]
=
[]
...
@@ -42,7 +42,7 @@ class ContactNormalIncomView : UIView {
...
@@ -42,7 +42,7 @@ class ContactNormalIncomView : UIView {
}()
}()
lazy
var
tableView
:
UITableView
=
{
lazy
var
tableView
:
UITableView
=
{
let
tableView
=
UITableView
(
frame
:
CGRect
(
x
:
0
,
y
:
0
,
width
:
0
,
height
:
12
),
style
:
UITableView
.
Style
.
grouped
)
let
tableView
=
UITableView
(
frame
:
CGRect
(
x
:
0
,
y
:
0
,
width
:
0
,
height
:
12
),
style
:
UITableView
.
Style
.
plain
)
tableView
.
dataSource
=
self
tableView
.
dataSource
=
self
tableView
.
delegate
=
self
tableView
.
delegate
=
self
tableView
.
register
(
CustomContactAllViewTableViewCell
.
self
,
forCellReuseIdentifier
:
"CustomContactAllViewTableViewCell"
)
tableView
.
register
(
CustomContactAllViewTableViewCell
.
self
,
forCellReuseIdentifier
:
"CustomContactAllViewTableViewCell"
)
...
@@ -57,10 +57,6 @@ class ContactNormalIncomView : UIView {
...
@@ -57,10 +57,6 @@ class ContactNormalIncomView : UIView {
lazy
var
deleteButton
:
DeleteButtonView
=
{
lazy
var
deleteButton
:
DeleteButtonView
=
{
let
deleteButton
=
DeleteButtonView
()
let
deleteButton
=
DeleteButtonView
()
// 设置删除按钮
deleteButton
.
layer
.
cornerRadius
=
23
deleteButton
.
clipsToBounds
=
true
deleteButton
.
isHidden
=
true
return
deleteButton
return
deleteButton
}()
}()
...
@@ -89,18 +85,13 @@ class ContactNormalIncomView : UIView {
...
@@ -89,18 +85,13 @@ class ContactNormalIncomView : UIView {
make
.
top
.
equalTo
(
self
.
subTitleLabel
.
snp
.
bottom
)
.
offset
(
16
*
RScreenH
())
make
.
top
.
equalTo
(
self
.
subTitleLabel
.
snp
.
bottom
)
.
offset
(
16
*
RScreenH
())
make
.
left
.
equalToSuperview
()
.
offset
(
15
*
RScreenW
())
make
.
left
.
equalToSuperview
()
.
offset
(
15
*
RScreenW
())
make
.
right
.
equalToSuperview
()
.
offset
(
-
15
*
RScreenW
())
make
.
right
.
equalToSuperview
()
.
offset
(
-
15
*
RScreenW
())
make
.
bottom
.
equalToSuperview
()
.
offset
(
-
102
*
RScreenH
()
)
make
.
bottom
.
equalToSuperview
()
.
offset
(
-
safeHeight
)
}
}
self
.
deleteButton
.
snp
.
makeConstraints
{
make
in
self
.
deleteButton
.
snp
.
makeConstraints
{
make
in
make
.
top
.
equalTo
(
self
.
tableView
.
snp
.
bottom
)
.
offset
(
16
*
RScreenH
())
make
.
left
.
right
.
equalToSuperview
()
make
.
width
.
equalTo
(
345
*
RScreenW
())
self
.
bottomConstraint
=
make
.
bottom
.
equalToSuperview
()
.
offset
(
68
)
.
constraint
make
.
height
.
equalTo
(
46
)
make
.
height
.
equalTo
(
68
)
make
.
centerX
.
equalToSuperview
()
}
}
// 排序
self
.
sortContacts
()
self
.
deleteButton
.
submitCallBack
=
{
self
.
deleteButton
.
submitCallBack
=
{
self
.
alertWhenDeleteSomeOne
()
self
.
alertWhenDeleteSomeOne
()
}
}
...
@@ -112,14 +103,21 @@ class ContactNormalIncomView : UIView {
...
@@ -112,14 +103,21 @@ class ContactNormalIncomView : UIView {
}
}
extension
ContactNormalIncomView
:
UITableViewDataSource
,
UITableViewDelegate
{
extension
ContactNormalIncomView
:
UITableViewDataSource
,
UITableViewDelegate
{
func
numberOfSections
(
in
tableView
:
UITableView
)
->
Int
{
func
tableView
(
_
tableView
:
UITableView
,
numberOfRowsInSection
section
:
Int
)
->
Int
{
return
sectionTitles
.
count
return
self
.
dataSourceModel
.
count
}
}
func
tableView
(
_
tableView
:
UITableView
,
numberOfRowsInSection
section
:
Int
)
->
Int
{
func
tableView
(
_
tableView
:
UITableView
,
didSelectRowAt
indexPath
:
IndexPath
)
{
let
sectionTitle
=
sectionTitles
[
section
]
// 调起系统弹窗
return
sectionedContacts
[
sectionTitle
]?
.
count
??
0
let
cell
=
tableView
.
cellForRow
(
at
:
indexPath
)
as!
CustomContactAllViewTableViewCell
if
let
model
=
cell
.
model
{
if
let
vc
=
self
.
responderViewController
()
{
ContactManager
.
callContactWithIdentifier
(
model
,
viewController
:
vc
)
}
}
}
}
func
updateDeleteButtonStatus
()
{
func
updateDeleteButtonStatus
()
{
...
@@ -127,22 +125,28 @@ extension ContactNormalIncomView : UITableViewDataSource,UITableViewDelegate {
...
@@ -127,22 +125,28 @@ extension ContactNormalIncomView : UITableViewDataSource,UITableViewDelegate {
// 判断button是否显示
// 判断button是否显示
if
self
.
selectedContacts
.
count
>
0
{
if
self
.
selectedContacts
.
count
>
0
{
// 设置button的title
// 设置button的title
self
.
deleteButton
.
titleLabel
.
text
=
"Delete
\(
self
.
selectedContacts
.
count
)
Contact"
self
.
deleteButton
.
deleteButton
.
setTitle
(
"Delete
\(
self
.
selectedContacts
.
count
)
Contact"
,
for
:
.
normal
)
self
.
deleteButton
.
isHidden
=
false
UIView
.
animate
(
withDuration
:
0.1
)
{
// 更新约束
self
.
bottomConstraint
?
.
update
(
offset
:
-
safeHeight
)
self
.
layoutIfNeeded
()
}
}
else
{
}
else
{
UIView
.
animate
(
withDuration
:
0.1
)
{
self
.
deleteButton
.
isHidden
=
true
// 更新约束
self
.
bottomConstraint
?
.
update
(
offset
:
68
)
self
.
layoutIfNeeded
()
}
}
}
}
}
}
}
func
tableView
(
_
tableView
:
UITableView
,
cellForRowAt
indexPath
:
IndexPath
)
->
UITableViewCell
{
func
tableView
(
_
tableView
:
UITableView
,
cellForRowAt
indexPath
:
IndexPath
)
->
UITableViewCell
{
let
cell
=
tableView
.
dequeueReusableCell
(
withIdentifier
:
"CustomContactAllViewTableViewCell"
,
for
:
indexPath
)
as!
CustomContactAllViewTableViewCell
let
cell
=
tableView
.
dequeueReusableCell
(
withIdentifier
:
"CustomContactAllViewTableViewCell"
,
for
:
indexPath
)
as!
CustomContactAllViewTableViewCell
let
sectionTitle
=
sectionTitles
[
indexPath
.
section
]
let
contact
=
self
.
dataSourceModel
[
indexPath
.
row
]
let
contact
=
sectionedContacts
[
sectionTitle
]?[
indexPath
.
row
]
cell
.
model
=
contact
cell
.
model
=
contact
cell
.
nameLabel
.
text
=
contact
?
.
name
cell
.
nameLabel
.
text
=
contact
.
name
if
self
.
selectedContacts
.
contains
(
where
:
{
$0
.
identifier
==
contact
!
.
identifier
})
{
if
self
.
selectedContacts
.
contains
(
where
:
{
$0
.
identifier
==
contact
.
identifier
})
{
cell
.
selectButton
.
isSelected
=
true
cell
.
selectButton
.
isSelected
=
true
}
else
{
}
else
{
cell
.
selectButton
.
isSelected
=
false
cell
.
selectButton
.
isSelected
=
false
...
@@ -154,9 +158,7 @@ extension ContactNormalIncomView : UITableViewDataSource,UITableViewDelegate {
...
@@ -154,9 +158,7 @@ extension ContactNormalIncomView : UITableViewDataSource,UITableViewDelegate {
}
else
{
}
else
{
self
.
selectedContacts
.
removeAll
(
where
:
{
$0
.
identifier
==
model
.
identifier
})
self
.
selectedContacts
.
removeAll
(
where
:
{
$0
.
identifier
==
model
.
identifier
})
}
}
updateDeleteButtonStatus
()
updateDeleteButtonStatus
()
}
}
return
cell
return
cell
...
@@ -165,25 +167,12 @@ extension ContactNormalIncomView : UITableViewDataSource,UITableViewDelegate {
...
@@ -165,25 +167,12 @@ extension ContactNormalIncomView : UITableViewDataSource,UITableViewDelegate {
func
tableView
(
_
tableView
:
UITableView
,
heightForRowAt
indexPath
:
IndexPath
)
->
CGFloat
{
func
tableView
(
_
tableView
:
UITableView
,
heightForRowAt
indexPath
:
IndexPath
)
->
CGFloat
{
return
77
+
8
*
RScreenH
()
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
()
{
func
alertWhenDeleteSomeOne
()
{
// 删除之前弹出是否真的需要删除
// 删除之前弹出是否真的需要删除
let
alertVc
=
ContactDeleteAlertView
()
let
alertVc
=
ContactDeleteAlertView
()
alertVc
.
frame
=
(
self
.
responderViewController
()?
.
view
.
bounds
)
!
alertVc
.
frame
=
(
self
.
responderViewController
()?
.
view
.
bounds
)
!
self
.
responderViewController
()?
.
view
.
addSubview
(
alertVc
)
cWindow
?
.
addSubview
(
alertVc
)
alertVc
.
sureCallBack
=
{[
weak
self
]
isSure
in
alertVc
.
sureCallBack
=
{[
weak
self
]
isSure
in
guard
let
self
else
{
return
}
guard
let
self
else
{
return
}
...
@@ -193,74 +182,41 @@ extension ContactNormalIncomView : UITableViewDataSource,UITableViewDelegate {
...
@@ -193,74 +182,41 @@ extension ContactNormalIncomView : UITableViewDataSource,UITableViewDelegate {
}
}
}
}
func
deleteContacts
(){
func
deleteContacts
(){
// 删除逻辑
// 删除逻辑
for
contact
in
selectedContacts
{
for
contact
in
selectedContacts
{
if
let
index
=
self
.
dataSourceModel
.
firstIndex
(
of
:
contact
)
{
if
let
index
=
self
.
dataSourceModel
.
firstIndex
(
of
:
contact
)
{
self
.
dataSourceModel
.
remove
(
at
:
index
)
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
()
}
}
ContactManager
.
batchDeleteContacts
(
self
.
selectedContacts
)
{
result
in
switch
result
{
case
.
success
(
let
deletedContacts
):
}
print
(
"成功删除
\(
deletedContacts
.
count
)
个联系人"
)
self
.
selectedContacts
.
removeAll
()
self
.
subTitleLabel
.
text
=
"
\(
self
.
dataSourceModel
.
count
)
Contacts"
/// 给联系人分组排序
self
.
updateDeleteButtonStatus
()
func
sortContacts
()
{
self
.
tableView
.
reloadData
()
sectionedContacts
.
removeAll
()
for
contact
in
self
.
dataSourceModel
{
if
self
.
dataSourceModel
.
count
<=
0
{
let
firstLetter
=
pinyinFirstLetter
(
contact
.
name
)
.
uppercased
()
self
.
dataClearCallBack
()
if
sectionedContacts
[
firstLetter
]
==
nil
{
}
sectionedContacts
[
firstLetter
]
=
[]
// 删除完成 弹窗
let
buAlertVc
=
ContactBackUpDeleteCompletedAlertView
(
frame
:
(
cWindow
?
.
bounds
)
!
)
cWindow
?
.
addSubview
(
buAlertVc
)
DispatchQueue
.
main
.
asyncAfter
(
deadline
:
.
now
()
+
2
)
{
buAlertVc
.
removeFromSuperview
()
}
break
case
.
failure
(
.
contactNotFound
(
let
missing
)):
print
(
"操作终止:未找到联系人
\(
missing
.
identifier
)
"
)
case
.
failure
(
.
unauthorized
):
print
(
"无通讯录访问权限"
)
case
.
failure
(
.
executionFailed
(
let
error
)):
print
(
"操作失败:
\(
error
.
localizedDescription
)
"
)
}
}
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
()
}
}
}
}
PhoneManager/Class/Tool/Singleton/Singleton.swift
View file @
c19fadbf
...
@@ -7,6 +7,7 @@
...
@@ -7,6 +7,7 @@
import
Foundation
import
Foundation
import
GoogleMobileAds
import
GoogleMobileAds
import
ContactsUI
class
Singleton
{
class
Singleton
{
// 使用静态常量来保存单例实例
// 使用静态常量来保存单例实例
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment