Home > Software engineering >  It get crash when try to fetch the CNContact's Job Title and Profile Images
It get crash when try to fetch the CNContact's Job Title and Profile Images

Time:06-24

I have a lot of troubleshooting these issues a few days ago. Here the complete code,

You need to create 2 Files.

import Foundation
import Contacts

class contactsAspcts  {
    
    var contactOut: CNContact
    
    init(contactOut: CNContact) {
        self.contactOut = contactOut
        
    }
    
}

Then Create new files to create TableView Controller

    import UIKit


    private let cellId = "contactCell"

    class ViewController: UITableViewController, UISearchResultsUpdating {
    

    
   // the contacts array
    
    var allContacts = [contactsAspcts]()
    


    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupNavBar()
        
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellId)
        
        
        fetchData()
        
        searchBarUI()
    
        
    }

    
    
    func setupNavBar()  {
    
        navigationItem.title = "Your Contacts"
        navigationController?.navigationBar.prefersLargeTitles = true
        
    }
    
    
    //MARK: SEARCH VIEW CONTROLLER - START
    
    var searchViewController: UISearchController = UISearchController(searchResultsController: nil)
    
     var searchResults: [contactsAspcts] = []
    
    
    func searchBarUI() {
           
           searchViewController.searchResultsUpdater = self
           
        searchViewController.hidesNavigationBarDuringPresentation = true
        
        searchViewController.obscuresBackgroundDuringPresentation = true
        
        
           searchViewController.searchBar.placeholder = "Search contacts by family name"
           
           searchViewController.searchBar.barTintColor = UIColor.yellow
           
           searchViewController.obscuresBackgroundDuringPresentation = false
           
           definesPresentationContext = true
           
           navigationItem.hidesSearchBarWhenScrolling = false
           
           navigationItem.searchController = searchViewController
           
           
           
       }
    
    func updateSearchResults(for searchController: UISearchController) {
        
        let textToBeLowercased = searchViewController.searchBar.text?.lowercased()
        
        filtercontent(for: textToBeLowercased!)
        
        DispatchQueue.main.async {
            self.tableView.reloadData()
        }
        
        
    }
    
    
    func filtercontent(for searchText: String) {
        
        searchResults = self.allContacts.filter({ (contact) -> Bool in
            
            return contact.contactOut.givenName.lowercased().range(of: searchText) != nil
            
            
        })
        
        
            
        
    }
    
    //MARK: SEARCH VIEW CONTROLLER - END
    
    
    
    

}

extension ViewController {
    
    override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 60
    }
    
    
    override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let headLabel = UILabel()
    
        headLabel.backgroundColor = .black
        
        return headLabel
    }
    
    
    override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        
        return 10
    }
    
    override func numberOfSections(in tableView: UITableView) -> Int {
        
        return 1
        

    }
    
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    

        if searchViewController.isActive {
            
            return searchResults.count
            
        } else {
            
            return allContacts.count
            
        }
        
        
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        
        if searchViewController.isActive {
            
            let cell = ContactShowCell(style: .subtitle, reuseIdentifier: cellId)
             
             cell.textLabel?.text = "\(searchResults[indexPath.row].contactOut.givenName)"   " \( searchResults[indexPath.row].contactOut.familyName)"
             
            
            /// This is crashed when fetching it
            // company
              cell.detailTextLabel?.text = "\(searchResults[indexPath.row].contactOut.jobTitle)" // crashed
           
            // Profile
            cell.imageView?.image = UIImage(data: searchResults[indexPath.row].contactOut.imageData!) // crashed
            
             return cell
            
            
        } else {
            
            let cell = ContactShowCell(style: .subtitle, reuseIdentifier: cellId)
             
             cell.textLabel?.text = "\(allContacts[indexPath.row].contactOut.familyName)"   " "   "\(allContacts[indexPath.row].contactOut.givenName)"
             
             cell.detailTextLabel?.text = (allContacts[indexPath.row].contactOut.phoneNumbers.first?.value.stringValue)
             
            

             return cell
            
        }
        
        
        
    }
    
    
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        
        let nextVC = DetailViewController()
        
        if searchViewController.isActive {
            
            nextVC.familyNamePassedOver = searchResults[indexPath.row].contactOut.familyName
            nextVC.givenNamePassedOver = searchResults[indexPath.row].contactOut.givenName
            
            
            if let phoneToPass = searchResults[indexPath.row].contactOut.phoneNumbers.first?.value.stringValue {
                
                nextVC.phonenumberPassedOver = phoneToPass
                
            }
            
            
        } else {
            
            nextVC.familyNamePassedOver = allContacts[indexPath.row].contactOut.familyName
                   nextVC.givenNamePassedOver = allContacts[indexPath.row].contactOut.givenName
                   
                   
                   if let phoneToPass = allContacts[indexPath.row].contactOut.phoneNumbers.first?.value.stringValue {
                       
                       nextVC.phonenumberPassedOver = phoneToPass
                       
                   }
        }
        
        
        
        
       
        
        navigationController?.pushViewController(nextVC, animated: true)
        
    }
    
   

    
}
    
    
    import Foundation
    import Contacts
    import UIKit
    
    extension ViewController {
        
        func fetchData() {
            
            let contactStore = CNContactStore()
            
            contactStore.requestAccess(for: .contacts) { (accessGrant, error) in
                if let error = error {
                    
                    print("there is an error - \(error)")
                    
                    let alertController = UIAlertController(title: "We need access to your contacts to display them", message: "Go to your settings and grant us permissions", preferredStyle: .alert)
    
                            alertController.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
                            
                           
                    self.present(alertController, animated: true, completion: nil)
                    
                    return
                    
                } else {
                    
                    if accessGrant {
                        
                        print("access is granted")
                        
                        let fetchKeys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey]
                        
                        let request = CNContactFetchRequest(keysToFetch: fetchKeys as [CNKeyDescriptor])
                        
                        do {
                            
                              
                            try  contactStore.enumerateContacts(with: request) { (retrievedContact, stopPointer) in
                                  
                              
                                let contactObject = contactsAspcts(contactOut: retrievedContact)
                                
                                self.allContacts.append(contactObject)
                              
                              }
                        } catch let error {
                            
                            print("falied to enumerate" , error)
                            
                        }
    
                    } else {
                        
                        print("access is denied")
                        
                        
                        
                    }
                    
                }
            }
            
            
        }
        
    }

Note: I split the code of the search by using the if statement

  • search has shown a phone number
  • Not search show job title

It seems it successfully fetched a phone number (when it disable jobTitle and imageData from CNContacts) However, I enabled them and cause the crash app show up in console:

Terminating app due to uncaught exception 'CNPropertyNotFetchedException', reason: 'A property was not requested when contact was fetched.'

I knew it one issues is this code!

         // Company
         cell.detailTextLabel?.text = "\(searchResults[indexPath.row].contactOut.jobTitle)" // crashed
       
        // Profile
        cell.imageView?.image = UIImage(data: searchResults[indexPath.row].contactOut.imageData!) // crashed

I have no idea. I have a lot of research on that issue with that crash, I have used CNContact which access the photo and Company (or else more) from Contacts.

Thanks!

CodePudding user response:

You only reqwuested GivenName, firstName & Phone Number. You should also requests to fetch JobTitle and image. Your fetch keys should be like below,

let fetchKeys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactPhoneNumbersKey, CNContactJobTitleKey, CNContactThumbnailImageDataKey]

You can check here for all list of keys you can fetch for contacts. You can only use the fields from contacts you requesting here in fetchKeys otherwise app will crash with exception CNPropertyNotFetchedException

  • Related