Reputation: 3
I have 2 view controllers. 1st is ViewController in which there is a label and select contact button. When I press select contacts button it present MyContactViewController there is a table view in which I fetched contacts from simulator device using import contacts. I can also select multiple row . After selecting multiple rows when i press Done button . This MyContactViewController get Dismiss and brings me back to the ViewController and all the names of the contacts comes in label. Now problem is this When I again click select Contact button then previously selected cell should appear selected. Below is my code of MyContactViewController and viewController.
import UIKit
class ViewController: UIViewController, DataPassProtocol{
@IBOutlet weak var contactNameLabel: UILabel!
var getName = [String]()
var getNameArray = ""
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func selectContactsBtn(_ sender: UIButton) {
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyBoard.instantiateViewController(withIdentifier: "MyContactsViewController")
as! MyContactsViewController
vc.dataPass = self
present(vc, animated: true, completion: nil)
}
func passName(name: [String]) {
getName.append(contentsOf: name)
showData()
}
func showData() {
for index in 0...self.getName.count-1 {
getNameArray = getNameArray + self.getName[index]
}
contactNameLabel.text = getNameArray
getNameArray = ""
}}
code of MyContactViewController
import UIKit
import Contacts
protocol DataPassProtocol{
func passName(name: [String])
}
class MyContactsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate{
@IBOutlet weak var contactSearchBar: UISearchBar!
var contactList = [String]()
var dataPass: DataPassProtocol?
var filterdata = [String]()
var selectedContactName = [String]()
var contactName = [String]()
var searching = false
@IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
tableView.allowsMultipleSelection = true
contactSearchBar.delegate = self
tableView.register(UINib(nibName: "ContactNameTableViewCell", bundle: nil),
forCellReuseIdentifier: "ContactNameTableViewCell")
// Do any additional setup after loading the view.
self.fetchContactData()
}
private func fetchContactData(){
let store = CNContactStore()
store.requestAccess(for: .contacts) { (granted, err) in
if let err = err {
print("failed to fetch Contacts", err)
return
}
if granted{
print("Access Allowed")
let keys = [CNContactGivenNameKey, CNContactFamilyNameKey]
let request = CNContactFetchRequest(keysToFetch: keys as [CNKeyDescriptor])
do {
request.sortOrder = CNContactSortOrder.userDefault
try store.enumerateContacts(with: request, usingBlock:
{(contact,stopPointerIfYouWantToStopEnumerating) in
let full_name = contact.givenName + " " + contact.familyName
let contact_model = full_name
self.contactList.append(contact_model)
})
self.tableView.reloadData()
}
catch let err{
print("Failed to fetch contacts", err)
}
} else {
print("Access Denied")
}
}
}
@IBAction func doneBtn(_ sender: UIButton) {
let dataToBeSent = selectedContactName.joined(separator: ", ")
self.dataPass?.passName(name: [dataToBeSent])
dismiss(animated: true, completion: nil)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searching{
return filterdata.count
}else{
return contactList.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ContactNameTableViewCell", for: indexPath) as! ContactNameTableViewCell
if searching{
cell.nameLabel.text = filterdata[indexPath.row]
}else{
cell.nameLabel.text = contactList[indexPath.row]
}
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 40
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! ContactNameTableViewCell
if searching{
selectedContactName.append(filterdata[indexPath.row])
} else {
selectedContactName.append(contactList[indexPath.row])
}
cell.checkImage.image = UIImage(named: "unchecked")
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! ContactNameTableViewCell
if selectedContactName.contains(contactList[indexPath.row]){
selectedContactName.remove(at: selectedContactName.firstIndex(of:
contactList[indexPath.row])!)
cell.checkImage.image = UIImage(named: "box")
}
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchBar.text == "" {
searching = false
tableView.reloadData()
} else {
searching = true
filterdata = contactList.filter({$0.contains(searchBar.text ?? "")})
tableView.reloadData()
}
}
}
Upvotes: 0
Views: 858
Reputation: 126
You can get previously selected contact in ContactsViewController by making selectedContactName array global as below and checking it in the cellForRow method whether selectedContactName contains contacts or not.
import UIKit
import Contacts
protocol DataPassProtocol{
func passName(name: [String])
}
var selectedContactName = [String]()
class MyContactsViewController: UIViewController ,UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate{
@IBOutlet weak var contactSearchBar: UISearchBar!
var contactList = [String]()
var dataPass: DataPassProtocol?
var filterdata = [String]()
var contactName = [String]()
var searching = false
@IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
tableView.allowsMultipleSelection = true
contactSearchBar.delegate = self
tableView.register(UINib(nibName: "ContactNameTableViewCell", bundle: nil),
forCellReuseIdentifier: "ContactNameTableViewCell")
// Do any additional setup after loading the view.
self.fetchContactData()
// Do any additional setup after loading the view.
}
private func fetchContactData(){
let store = CNContactStore()
store.requestAccess(for: .contacts) { (granted, err) in
if let err = err {
print("failed to fetch Contacts", err)
return
}
if granted{
print("Access Allowed")
let keys = [CNContactGivenNameKey, CNContactFamilyNameKey]
let request = CNContactFetchRequest(keysToFetch: keys as [CNKeyDescriptor])
do {
request.sortOrder = CNContactSortOrder.userDefault
try store.enumerateContacts(with: request, usingBlock:
{(contact,stopPointerIfYouWantToStopEnumerating) in
let full_name = contact.givenName + " " + contact.familyName
let contact_model = full_name
self.contactList.append(contact_model)
})
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
catch let err{
print("Failed to fetch contacts", err)
}
} else {
print("Access Denied")
}
}
}
@IBAction func doneBtn(_ sender: UIButton) {
let dataToBeSent = selectedContactName.joined(separator: ", ")
self.dataPass?.passName(name: [dataToBeSent])
dismiss(animated: true, completion: nil)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searching{
return filterdata.count
}else{
return contactList.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ContactNameTableViewCell", for: indexPath) as! ContactNameTableViewCell
cell.selectionStyle = .none
var contactDict = [String]()
if searching{
contactDict = filterdata
}else{
contactDict = contactList
}
cell.nameLabel.text = contactDict[indexPath.row]
if(selectedContactName.contains(contactDict[indexPath.row]))
{
cell.checkImage.image = UIImage(named: "check")
}
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 40
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath) as! ContactNameTableViewCell
print(selectedContactName)
var contactDict = [String]()
if searching{
contactDict = filterdata
}else{
contactDict = contactList
}
if(selectedContactName.contains(contactDict[indexPath.row]))
{
selectedContactName.remove(at: selectedContactName.firstIndex(of:
contactDict[indexPath.row])!)
cell.checkImage.image = nil
}
else{
selectedContactName.append(contactDict[indexPath.row])
cell.checkImage.image = UIImage(named: "check")
}
tableView.deselectRow(at: indexPath, animated: false)
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchBar.text == "" {
searching = false
tableView.reloadData()
} else {
searching = true
filterdata = contactList.filter({$0.contains(searchBar.text ?? "")})
tableView.reloadData()
}
}
}
and in viewController you should have to blank getName array to remove duplicate contact as below.
func passName(name: [String]) {
getName = []
getName.append(contentsOf: name)
showData()
}
Upvotes: 0
Reputation: 16774
What you need to do is first inject your current selection to your contacts view controller.
Your data source between the two controllers is completely out of sync though. Expected there should be some Contact
structure where each of them seem to support an array of those. But you use contacts as an array of names as strings in one view controller. In the other view controller things are even worse where you seem to have an array of strings where each string represents multiple contacts by showing their names in a coma separated string selectedContactName.joined(separator: ", ")
.
So with just this data it is unsafe to pass the data between the two view controllers. You could add another method to your protocol and add more properties but... Perhaps you should try to cleanup your interface...
I will only write some parts that are relevant for you to be put on the right track. But most of the code that you posted will still need changes to use such data source. Not that those changes are big but there are many of them:
Use a structure to represent your contact. Just the data you actually need for your UI. Don't put CNContact
stuff in it but do still copy the ID from CNContact
to it so that you can match results.
struct Contact {
let id: String
let displayName: String
}
A protocol that used for delegate approach usually looks like this:
protocol MyContactViewControllerDelegate: AnyObject {
func myContactViewController(_ sender: MyContactViewController, didSelectContacts contacts: [Contact])
}
The view controller to select your contacts:
class MyContactViewController: UIViewController {
var allContacts: [Contact] = [] // All contacts that may be shown
var selectedContacts: [Contact] = [] // Contacts that are currently selected
weak var delegate: MyContactViewControllerDelegate?
@IBOutlet private var tableView: UITableView?
override func viewDidLoad() {
super.viewDidLoad()
reload()
}
private func reload() {
Contact.fetchAllContacts { contacts in // This method does all the things with contact list from your address book or whatever...
self.allContacts = contacts
self.tableView?.reloadData()
self.applyTableViewCellSelection()
}
}
// This method needs to be called when data source changes. Either new contacts are received or when filter is applied
private func applyTableViewCellSelection() {
let currentlyShownContacts = allContacts // TODO: decide based on applied filter
let selectedContactIDList: [String] = self.selectedContacts.map { $0.id } // ID list of all selected contacts
// Get indices of all contacts that are selected so that we may convert it to table view index path
let selectedIndices: [Int] = {
// This can all be done in a single line but let's keep it like this for demo
let allContacts = currentlyShownContacts
let indexedContacts = allContacts.enumerated()
let filteredIndexedContacts = indexedContacts.filter { selectedContactIDList.contains($0.element.id) }
let listOfIndices: [Int] = filteredIndexedContacts.map { $0.offset }
return listOfIndices
}()
// Select all indices in table view
selectedIndices.forEach { index in
self.tableView?.selectRow(at: IndexPath(row: index, section: 0), animated: false, scrollPosition: .none)
}
// Deselect all previously selected
self.tableView?.indexPathsForSelectedRows?.forEach { indexPath in
if selectedIndices.contains(indexPath.row) == false {
self.tableView?.deselectRow(at: indexPath, animated: false)
}
}
}
@objc private func onDonePressed() {
delegate?.myContactViewController(self, didSelectContacts: selectedContacts)
}
}
This is how it should be used:
class ViewController: UIViewController, MyContactViewControllerDelegate {
private var selectedContacts: [Contact] = []
@objc private func manageContactsPressed() {
let controller: MyContactViewController = MyContactViewController.fromStoryboard() // The storyboard logic goes here
controller.delegate = self
controller.selectedContacts = selectedContacts
present(controller, animated: true, completion: nil)
}
func myContactViewController(_ sender: MyContactViewController, didSelectContacts contacts: [Contact]) {
selectedContacts = contacts
// TODO: reload relevant UI here
}
}
I hope most of the stuff here is descriptive enough. If you have specific question please do ask.
Upvotes: 0