skip to Main Content

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()
        }
    }
}

2

Answers


  1. 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.

    Login or Signup to reply.
  2. 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()
        }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search