edwardvalentini/EVContactsPicker

View on GitHub
Pod/Classes/EVContactsPickerViewController.swift

Summary

Maintainability
F
3 days
Test Coverage
//
//  EVContactsPickerViewController.swift
//  EVContactsPicker
//
//  Created by Edward Valentini on 9/18/15.
//  Copyright © 2015 CocoaPods. All rights reserved.
//

import UIKit
import Contacts
import ContactsUI


@available(iOS 9.0, *)
@objc open class EVContactsPickerViewController: UIViewController, UITableViewDataSource, UITableViewDelegate,  EVPickedContactsViewDelegate, CNContactViewControllerDelegate {
    
    
    let kKeyboardHeight : CGFloat = 0.0
    
    var contactPickerView : EVPickedContactsView? = nil
    var store : CNContactStore? = nil
    var tableView : UITableView? = nil
    var contacts : [EVContactProtocol]? = nil
    var selectedContacts : [EVContactProtocol]? = nil
    var filteredContacts : [EVContactProtocol]? = nil
    var barButton : UIBarButtonItem? = nil
    var useExternal : Bool = false
    public var maxSelectedContacts : Int = -1 {
        didSet {
            singleSelection = maxSelectedContacts == 1;
        }
    }
    var singleSelection : Bool = false {
        didSet {
            updateTitle()
        }
    }
    var externalDataSource : [EVContactProtocol]? = nil
    
    public var showEmail = true
    public var showPhone = true
    
    public weak var delegate : EVContactsPickerDelegate?
    fileprivate var curBundle : Bundle?

    
    
    var avatarImage : UIImage {
        let image = Bundle.evImage(withName: kAvatarImage, andExtension: "png")!
        return image
    }
    
    var selectedCheckbox : UIImage {
        let image = Bundle.evImage(withName: kSelectedCheckbox, andExtension: "png")!
        return image
    }
    
    var unselectedCheckbox : UIImage {
        let image = Bundle.evImage(withName: kUnselectedCheckbox, andExtension: "png")!
        return image
    }
    
    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
        self.setup()
    }
    
    public init(externalDataSource: [EVContactProtocol]!) {
        self.init()
        self.useExternal = true
        self.externalDataSource = externalDataSource
        self.setup()
    }

    required public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.setup()

    }
    
    func setup() -> Void {
        updateTitle()
        self.curBundle = Bundle(for: type(of: self))
        if( self.useExternal == false ) {
            self.store = CNContactStore()
        }

    }
    
    func updateTitle() -> Void {
        var contactsPresented = false
        if let theContacts = self.selectedContacts {
            contactsPresented = theContacts.isEmpty
        }
        if contactsPresented == false {
            if singleSelection {
                self.title  = Bundle.evLocalizedStringForKey("Selected Contacts")
            } else {
                self.title  = Bundle.evLocalizedStringForKey("Selected Contacts")! + "(0)"
            }
        } else {
            if singleSelection {
                self.title  = Bundle.evLocalizedStringForKey("Add Contacts")
            } else {
                self.title = String(Bundle.evLocalizedStringForKey("Add Contacts")! + "(\(self.selectedContacts!.count))")
            }
        }
    }

    override open func viewDidLoad() {
        super.viewDidLoad()
        
        barButton = UIBarButtonItem(title: Bundle.evLocalizedStringForKey("Done"), style: .done, target: self, action: #selector(EVContactsPickerViewController.done(_:)))
        barButton?.isEnabled = false
        self.navigationItem.rightBarButtonItem = barButton
        
        if self.navigationController?.viewControllers.first == self {
            self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.cancel,
                                                                    target: self,
                                                                    action: #selector(cancelTapped))
        }
        
        self.contactPickerView = EVPickedContactsView(frame: CGRect(origin: CGPoint.zero, size: CGSize(width:self.view.frame.size.width, height: 100)))
        self.contactPickerView?.delegate = self
        self.contactPickerView?.setPlaceHolderString(Bundle.evLocalizedStringForKey("Type Contact Name")!)
        self.view.addSubview(self.contactPickerView!)
        
        
        self.tableView = UITableView(frame: CGRect(x: 0, y: self.contactPickerView!.frame.size.height, width: self.view.frame.size.width, height: self.view.frame.size.height - self.contactPickerView!.frame.size.height - kKeyboardHeight), style: .plain)
        
        self.tableView?.delegate  = self
        self.tableView?.dataSource = self
        

        
        self.tableView?.register(UINib(nibName: "EVContactsPickerTableViewCell", bundle: self.curBundle), forCellReuseIdentifier: "contactCell")
        self.view.insertSubview(self.tableView!, belowSubview: self.contactPickerView!)
        
        if( self.useExternal == false ) {
            self.store?.requestAccess(for: .contacts, completionHandler: { (granted : Bool, error: Error?) -> Void in
                if(granted) {
                    DispatchQueue.main.async(execute: { () -> Void in
                        self.getContactsFromAddressBook()
                    })
                } else {
                    DispatchQueue.main.async(execute: { () -> Void in
                        print("show UIalert for problem")
                    })
                }
            })
        } else {
            self.contacts = self.externalDataSource
            self.selectedContacts = []
            self.filteredContacts = self.contacts
            self.tableView?.reloadData()
        }
    }
    
    override open func viewWillAppear(_ animated: Bool) -> Void {
        super.viewWillAppear(animated)
        DispatchQueue.main.async { () -> Void in
            self.refreshContacts()
        }
    }
    
    override open func viewDidLayoutSubviews() -> Void {
        super.viewDidLayoutSubviews()
        var topOffset : CGFloat = 0.0
        if( self.responds(to: #selector(getter: UIViewController.topLayoutGuide))) {
            topOffset = self.topLayoutGuide.length
        }
        self.contactPickerView?.frame.origin.y = topOffset
        self.adjustTableViewFrame(false)
    }
    
    @objc func cancelTapped() {
        self.dismiss(animated: true, completion: nil)
    }
    
    func adjustTableViewFrame(_ animated: Bool) -> Void {
        var frame = self.tableView?.frame
        frame?.origin.y = (self.contactPickerView?.frame.size.height)!
        frame?.size.height = self.view.frame.size.height - (self.contactPickerView?.frame.size.height)! - kKeyboardHeight
        if( animated ) {
            UIView.beginAnimations(nil, context: nil)
            UIView.setAnimationDuration(0.3)
            UIView.setAnimationDelay(0.1)
            UIView.setAnimationCurve(.easeOut)
            self.tableView?.frame = frame!
            UIView.commitAnimations()
        } else {
            self.tableView?.frame = frame!
        }
    }

    override open func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    // MARK: - Addressbook
    
    func getContactsFromAddressBook() -> Void {
        
        self.contacts = []
        var mutableContacts : [EVContact] = []
        
        let req : CNContactFetchRequest = CNContactFetchRequest(keysToFetch: [CNContactEmailAddressesKey as CNKeyDescriptor,CNContactGivenNameKey as CNKeyDescriptor,CNContactFamilyNameKey as CNKeyDescriptor,CNContactImageDataAvailableKey as CNKeyDescriptor,CNContactThumbnailImageDataKey as CNKeyDescriptor,CNContactImageDataKey as CNKeyDescriptor,CNContactPhoneNumbersKey as CNKeyDescriptor])
        
        do {
            try self.store?.enumerateContacts(with: req, usingBlock: { (contact: CNContact, boolprop : UnsafeMutablePointer<ObjCBool> ) -> Void in
                
                var tmpContact = EVContact(identifier: contact.identifier)
                tmpContact.firstName = contact.givenName
                tmpContact.lastName = contact.familyName
                if (contact.phoneNumbers.count > 0) {
                    tmpContact.phone = (contact.phoneNumbers[0].value ).stringValue

                }
                if (contact.emailAddresses.count > 0) {
                    tmpContact.email = (contact.emailAddresses[0].value as String)
                }
                
                if(contact.imageDataAvailable) {
                    if let imgData = contact.imageData {
                        let img = UIImage(data: imgData)
                        tmpContact.image = img
                    } else {
                        tmpContact.image = self.avatarImage
                    }
                } else {
                    tmpContact.image = self.avatarImage
                }
                
                let showContact = self.delegate?.shouldShowContact(tmpContact)
                
                if showContact == nil || showContact == true {
                    mutableContacts.append(tmpContact)
                }
                
                self.contacts = mutableContacts
                self.selectedContacts = []
                self.filteredContacts = self.contacts
                self.tableView?.reloadData()
                
            })
        } catch {
            print("error occured")
        }
  
    }
    
    func refreshContacts() -> Void {
        if let contacts = self.contacts {
            for contact in contacts {
                self.refreshContact(contact)
            }
        }
        self.tableView?.reloadData()
    }

    func refreshContact(_ contact: EVContactProtocol) {
        if( self.useExternal == false ) {
            do {
                if let tmpContact = try self.store?.unifiedContact(withIdentifier: contact.identifier, keysToFetch: [CNContactEmailAddressesKey as CNKeyDescriptor,CNContactGivenNameKey as CNKeyDescriptor,CNContactFamilyNameKey as CNKeyDescriptor,CNContactImageDataAvailableKey as CNKeyDescriptor,CNContactThumbnailImageDataKey as CNKeyDescriptor,CNContactImageDataKey as CNKeyDescriptor,CNContactPhoneNumbersKey as CNKeyDescriptor]) {
                    var mutableContact = contact
                    mutableContact.identifier = tmpContact.identifier
                    mutableContact.firstName = tmpContact.givenName
                    mutableContact.lastName = tmpContact.familyName
                    if (tmpContact.phoneNumbers.count > 0) {
                        mutableContact.phone = (tmpContact.phoneNumbers[0].value ).stringValue
                        
                    }
                    if (tmpContact.emailAddresses.count > 0) {
                        mutableContact.email = (tmpContact.emailAddresses[0].value as String)
                    }

                    
                    if(tmpContact.imageDataAvailable) {
                        let imgData = tmpContact.imageData
                        let img = UIImage(data: imgData!)
                        mutableContact.image = img
                    } else {
                        mutableContact.image = self.avatarImage
                    }
                }
            } catch {
                print("error")
            }
        } else {
            var mutableContact = contact
            if(mutableContact.image == nil) {
                mutableContact.image = self.avatarImage
            }
        }
    }
    
    open func contactViewController(_ viewController: CNContactViewController, shouldPerformDefaultActionFor property: CNContactProperty) -> Bool {
        return true
    }
    
    // MARK: - TableView
    
    open func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    open func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if( self.filteredContacts == nil ) {
            return 0
        } else {
            return self.filteredContacts!.count
        }
    }
    
    open func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 70
    }
    
    open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cellIdentifier = "contactCell"
        let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) as! EVContactsPickerTableViewCell
        
        guard let selectedContacts = self.selectedContacts, let filteredContacts = self.filteredContacts else {
            cell.checkImage?.image = self.unselectedCheckbox
            return cell
        }
        
        var contact = filteredContacts[indexPath.row]
        
        cell.fullName?.text = contact.fullname()
        
        cell.email?.isHidden = (contact.email == nil || contact.email?.isEmpty == true || showEmail == false)
        cell.phone?.isHidden = (contact.phone == nil || contact.phone?.isEmpty == true || showPhone == false)
        
        cell.phone?.text = contact.phone
        cell.email?.text = contact.email
        
        if let cImage = contact.image {
            cell.contactImage?.image = cImage
        }
        
        if let cImageURL = contact.imageURL {
            cell.imageURL = cImageURL
        }
        
        cell.contactImage?.layer.masksToBounds = true
        cell.contactImage?.layer.cornerRadius = 20
        
 
            
        if selectedContacts.contains(where: { (evcontact) -> Bool in
            return evcontact.identifier == contact.identifier
        }) {
            cell.checkImage?.image = self.selectedCheckbox
        } else {
            cell.checkImage?.image = self.unselectedCheckbox
        }

        if !self.useExternal {
            cell.accessoryView = UIButton(type: .detailDisclosure)
            let but = cell.accessoryView as! UIButton
            but.addTarget(self, action: #selector(EVContactsPickerViewController.viewContactDetail(_:)), for: UIControlEvents.touchUpInside)
        } else {
            cell.accessoryType = .none
            cell.accessoryView?.isHidden = true
        }
        
        return cell
    }
    
    open func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {

        guard let filteredContacts = self.filteredContacts else {
            return nil
        }
        
        guard let selectedContacts = self.selectedContacts else {
            return nil
        }
        
        self.contactPickerView?.resignKeyboard()
        
        
        let cell = tableView.cellForRow(at: indexPath) as! EVContactsPickerTableViewCell
        
        let user = filteredContacts[indexPath.row]
        
            if selectedContacts.contains(where: { (evcontact) -> Bool in
                return evcontact.identifier == user.identifier
            }) {
                if let ind = selectedContacts.index(where: { $0.identifier == user.identifier }) {
                    self.selectedContacts?.remove(at: ind)
                    self.contactPickerView?.removeContact(user)
                    cell.checkImage?.image = self.unselectedCheckbox
                }
            } else if (canAddMoreContacts() || singleSelection) {
                if singleSelection {
                    self.selectedContacts?.removeAll();
                    self.contactPickerView?.removeAllContacts();
                }
                self.selectedContacts?.append(user)
                if let fullname = user.fullname() {
                    self.contactPickerView?.addContact(user, name: fullname)
                    cell.checkImage?.image = self.selectedCheckbox
                }
            }
        
        if((self.selectedContacts?.count)! > 0) {
            self.barButton?.isEnabled = true
        } else {
            self.barButton?.isEnabled = false
        }
        
        self.title = String(Bundle.evLocalizedStringForKey("Add Contacts")! + "(\(self.selectedContacts!.count))")
        self.filteredContacts = self.contacts
        self.tableView?.reloadData()
        
        return indexPath
    }
    
    private func canAddMoreContacts() -> Bool {
        return maxSelectedContacts <= 0 || maxSelectedContacts > 0 && maxSelectedContacts > (self.selectedContacts?.count)!;
    }
    
    // MARK: - EVPickedContactsViewDelegate
    private func doesContain(str1: String, str2: String) -> Bool {
        var options = NSString.CompareOptions()
        options.insert(NSString.CompareOptions.caseInsensitive)
        options.insert(NSString.CompareOptions.diacriticInsensitive)
        return (str1.range(of: str2, options: options, range: nil, locale: nil) != nil)
    }
    
    func contactPickerTextViewDidChange(_ textViewText: String) -> Void {
        if(textViewText == "") {
            self.filteredContacts = self.contacts
        } else {
            guard let contacts = self.contacts else { return }
            
            self.filteredContacts = contacts.filter({ (contact) -> Bool in
                var filterResultFirst : Bool = true
                var filterResultLast : Bool = true

                if let firstName = contact.firstName {
                    filterResultFirst = filterResultFirst && doesContain(str1: firstName, str2: textViewText)
                }
                
                if let lastName = contact.lastName {
                    filterResultLast = filterResultLast && doesContain(str1: lastName, str2: textViewText)
                }
                
                return filterResultFirst || filterResultLast
            })
        }
        
        self.tableView?.reloadData()
        
     }
    
    func contactPickerDidRemoveContact(_ contact: EVContactProtocol) -> Void {
        guard var selectedContacts = self.selectedContacts else {
            return
        }
        
        if let ind = selectedContacts.index(where: { (evcontact) -> Bool in
            return  evcontact.identifier == contact.identifier
        }) {
            selectedContacts.remove(at: ind)
            let indexPath = IndexPath(row: ind, section: 0)
            let cell = self.tableView?.cellForRow(at: indexPath) as! EVContactsPickerTableViewCell
            if((self.selectedContacts?.count)! > 0) {
                self.barButton?.isEnabled = true
            } else {
                self.barButton?.isEnabled = false
            }
//            let imPath = self.curBundle?.path(forResource: kUnselectedCheckbox, ofType: "png", inDirectory: "EVContactsPicker.bundle")
//            let im = UIImage(contentsOfFile: imPath!)
//            
            let im = Bundle.evImage(withName: kUnselectedCheckbox, andExtension: "png")!
            cell.checkImage?.image = im
            
            self.title = String(Bundle.evLocalizedStringForKey("Add Contacts")! + "(\(self.selectedContacts!.count))")
        }
    }
    
    func contactPickerDidResize(_ pickedContactView: EVPickedContactsView) -> Void {
        self.adjustTableViewFrame(true)
    }
    
    // MARK: - Miscellaneous

    @objc open func done(_ sender: AnyObject) -> Void {
        let delayTime = DispatchTime.now() + Double(Int64(0.01 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)

        DispatchQueue.main.asyncAfter(deadline: delayTime, execute: { () -> Void in
            if let del = self.delegate {
                if let selcontacts = self.selectedContacts {
                    del.didChooseContacts(selcontacts)
                } else {
                    del.didChooseContacts(nil)
                }
            }
        });
    }
    
    @IBAction func viewContactDetail(_ sender: UIButton) -> Void {
       // print("clicked discloser")
        
        if( self.useExternal == false ) {
            let indexp = IndexPath(row: 0, section: 0)
            
            let c =    self.filteredContacts?[(indexp as NSIndexPath).row]
            do {
                let appconact = try self.store?.unifiedContact(withIdentifier: (c?.identifier)!, keysToFetch: [CNContactViewController.descriptorForRequiredKeys()] )
                let vc = CNContactViewController(for: appconact!)
                CNContactViewController.descriptorForRequiredKeys()
                self.navigationController?.pushViewController(vc, animated: true)
            } catch {
                print("error")
            }
        }
        
        
    }

}