├── .swift-version ├── EPContactsPickerLogo.jpg ├── Screenshots ├── Screen1.png ├── Screen2.png └── Screen3.png ├── .travis.yml ├── .xctool-args ├── Contacts Picker.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── prabaharan.e.xcuserdatad │ │ └── UserInterfaceState.xcuserstate ├── xcuserdata │ └── prabaharan.e.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ └── xcschememanagement.plist ├── xcshareddata │ └── xcschemes │ │ └── Contacts Picker.xcscheme └── project.pbxproj ├── .gitignore ├── LICENSE ├── Pods ├── EPExtensions.swift ├── EPConstants.swift ├── EPContact.swift ├── EPContactCell.swift ├── EPContactCell.xib └── EPContactsPicker.swift ├── EPContactsPicker.podspec ├── Contacts Picker ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Info.plist ├── ViewController.swift ├── AppDelegate.swift └── Base.lproj │ ├── Main.storyboard │ └── LaunchScreen.storyboard └── README.md /.swift-version: -------------------------------------------------------------------------------- 1 | 3.0 2 | -------------------------------------------------------------------------------- /EPContactsPickerLogo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipraba/EPContactsPicker/HEAD/EPContactsPickerLogo.jpg -------------------------------------------------------------------------------- /Screenshots/Screen1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipraba/EPContactsPicker/HEAD/Screenshots/Screen1.png -------------------------------------------------------------------------------- /Screenshots/Screen2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipraba/EPContactsPicker/HEAD/Screenshots/Screen2.png -------------------------------------------------------------------------------- /Screenshots/Screen3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipraba/EPContactsPicker/HEAD/Screenshots/Screen3.png -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode8.3.2 3 | before_install: gem install xcpretty 4 | script: xcodebuild -project Contacts\ Picker.xcodeproj -sdk iphonesimulator build | xcpretty -c 5 | -------------------------------------------------------------------------------- /.xctool-args: -------------------------------------------------------------------------------- 1 | [ 2 | "-project", "Contacts Picker.xcodeproj", 3 | "-scheme", "Contacts Picker", 4 | "-sdk", "iphonesimulator", 5 | "-destination", "platform=iOS Simulator,name=iPhone 6s,OS=latest", 6 | ] 7 | -------------------------------------------------------------------------------- /Contacts Picker.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Contacts Picker.xcodeproj/project.xcworkspace/xcuserdata/prabaharan.e.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ipraba/EPContactsPicker/HEAD/Contacts Picker.xcodeproj/project.xcworkspace/xcuserdata/prabaharan.e.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Contacts Picker.xcodeproj/xcuserdata/prabaharan.e.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Contacts Picker.xcodeproj/xcuserdata/prabaharan.e.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Contacts Picker.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | F4C1C93D1BDF8AB7001AA643 16 | 17 | primary 18 | 19 | 20 | F4C1C9511BDF8AB7001AA643 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xcuserstate 23 | 24 | ## Obj-C/Swift specific 25 | *.hmap 26 | *.ipa 27 | *.dSYM.zip 28 | *.dSYM 29 | *.DS_Store 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | .build/ 40 | 41 | # CocoaPods 42 | # 43 | # We recommend against adding the Pods directory to your .gitignore. However 44 | # you should judge for yourself, the pros and cons are mentioned at: 45 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 46 | # 47 | Pods/ 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Prabaharan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Pods/EPExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EPExtensions.swift 3 | // EPContactPicker 4 | // 5 | // Created by Prabaharan Elangovan on 14/10/15. 6 | // Copyright © 2015 Prabaharan Elangovan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Foundation 11 | 12 | extension String { 13 | subscript(r: Range) -> String? { 14 | get { 15 | let stringCount = self.characters.count as Int 16 | if (stringCount < r.upperBound) || (stringCount < r.lowerBound) { 17 | return nil 18 | } 19 | let startIndex = self.characters.index(self.startIndex, offsetBy: r.lowerBound) 20 | let endIndex = self.characters.index(self.startIndex, offsetBy: r.upperBound - r.lowerBound) 21 | return self[(startIndex ..< endIndex)] 22 | } 23 | } 24 | 25 | func containsAlphabets() -> Bool { 26 | //Checks if all the characters inside the string are alphabets 27 | let set = CharacterSet.letters 28 | return self.utf16.contains( where: { 29 | guard let unicode = UnicodeScalar($0) else { return false } 30 | return set.contains(unicode) 31 | }) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /EPContactsPicker.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "EPContactsPicker" 3 | s.version = "2.0.2" 4 | s.summary = "A contacts picker component for iOS written in swift using new contacts framwork" 5 | s.description = <<-DESC 6 | Features 7 | 1. Single selection and multiselection option 8 | 2. Making the secondary data to show as requested(Phonenumbers, Emails, Birthday and Organisation) 9 | 3. Section Indexes to easily navigate throught the contacts 10 | 4. Showing initials when image is not available 11 | 5. EPContact object to get the properties of the contacts 12 | DESC 13 | 14 | s.homepage = "https://github.com/ipraba/EPContactsPicker" 15 | s.license = 'MIT' 16 | s.author = { "Prabaharan" => "mailprabaharan.e@gmail.com" } 17 | s.source = { :git => "https://github.com/ipraba/EPContactsPicker.git", :tag => s.version.to_s } 18 | s.platform = :ios, '9.0' 19 | s.requires_arc = true 20 | s.source_files = 'Pods' 21 | s.frameworks = 'Contacts', 'ContactsUI' 22 | s.resources = ["Pods/EPContactCell.xib"] 23 | s.resource_bundles = { 24 | 'EPContactsPicker' => ['Pods/**/*.xib'] 25 | } 26 | 27 | end 28 | -------------------------------------------------------------------------------- /Contacts Picker/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /Contacts Picker/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSContactsUsageDescription 6 | Example application need access to contacts to work 7 | CFBundleDevelopmentRegion 8 | en 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 2.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 2.0 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /Pods/EPConstants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EPConstants.swift 3 | // EPContactPicker 4 | // 5 | // Created by Prabaharan Elangovan on 16/10/15. 6 | // Copyright © 2015 Prabaharan Elangovan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | //Declare all the static constants here 12 | struct EPGlobalConstants { 13 | 14 | //MARK: String Constants 15 | struct Strings { 16 | static let birdtdayDateFormat = "MMM d" 17 | static let contactsTitle = "Contacts" 18 | static let phoneNumberNotAvaialable = "No phone numbers available" 19 | static let emailNotAvaialable = "No emails available" 20 | static let bundleIdentifier = "EPContactsPicker" 21 | static let cellNibIdentifier = "EPContactCell" 22 | } 23 | 24 | //MARK: Color Constants 25 | struct Colors { 26 | static let emeraldColor = UIColor(red: (46/255), green: (204/255), blue: (113/255), alpha: 1.0) 27 | static let sunflowerColor = UIColor(red: (241/255), green: (196/255), blue: (15/255), alpha: 1.0) 28 | static let pumpkinColor = UIColor(red: (211/255), green: (84/255), blue: (0/255), alpha: 1.0) 29 | static let asbestosColor = UIColor(red: (127/255), green: (140/255), blue: (141/255), alpha: 1.0) 30 | static let amethystColor = UIColor(red: (155/255), green: (89/255), blue: (182/255), alpha: 1.0) 31 | static let peterRiverColor = UIColor(red: (52/255), green: (152/255), blue: (219/255), alpha: 1.0) 32 | static let pomegranateColor = UIColor(red: (192/255), green: (57/255), blue: (43/255), alpha: 1.0) 33 | } 34 | 35 | 36 | //MARK: Array Constants 37 | struct Arrays { 38 | static let alphabets = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","#"] //# indicates the names with numbers and blank spaces 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /Contacts Picker/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // EPContacts 4 | // 5 | // Created by Prabaharan Elangovan on 12/10/15. 6 | // Copyright © 2015 Prabaharan Elangovan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController, EPPickerDelegate { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | // Do any additional setup after loading the view, typically from a nib. 16 | } 17 | 18 | override func didReceiveMemoryWarning() { 19 | super.didReceiveMemoryWarning() 20 | // Dispose of any resources that can be recreated. 21 | } 22 | 23 | @IBAction func onTouchShowMeContactsButton(_ sender: AnyObject) { 24 | 25 | let contactPickerScene = EPContactsPicker(delegate: self, multiSelection:true, subtitleCellType: SubtitleCellValue.email) 26 | let navigationController = UINavigationController(rootViewController: contactPickerScene) 27 | self.present(navigationController, animated: true, completion: nil) 28 | 29 | } 30 | 31 | //MARK: EPContactsPicker delegates 32 | func epContactPicker(_: EPContactsPicker, didContactFetchFailed error : NSError) 33 | { 34 | print("Failed with error \(error.description)") 35 | } 36 | 37 | func epContactPicker(_: EPContactsPicker, didSelectContact contact : EPContact) 38 | { 39 | print("Contact \(contact.displayName()) has been selected") 40 | } 41 | 42 | func epContactPicker(_: EPContactsPicker, didCancel error : NSError) 43 | { 44 | print("User canceled the selection"); 45 | } 46 | 47 | func epContactPicker(_: EPContactsPicker, didSelectMultipleContacts contacts: [EPContact]) { 48 | print("The following contacts are selected") 49 | for contact in contacts { 50 | print("\(contact.displayName())") 51 | } 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /Contacts Picker/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Contacts Picker 4 | // 5 | // Created by Prabaharan Elangovan on 27/10/15. 6 | // Copyright © 2015 Prabaharan Elangovan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Pods/EPContact.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EPContact.swift 3 | // EPContacts 4 | // 5 | // Created by Prabaharan Elangovan on 13/10/15. 6 | // Copyright © 2015 Prabaharan Elangovan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Contacts 11 | 12 | open class EPContact { 13 | 14 | open var firstName: String 15 | open var lastName: String 16 | open var company: String 17 | open var thumbnailProfileImage: UIImage? 18 | open var profileImage: UIImage? 19 | open var birthday: Date? 20 | open var birthdayString: String? 21 | open var contactId: String? 22 | open var phoneNumbers = [(phoneNumber: String, phoneLabel: String)]() 23 | open var emails = [(email: String, emailLabel: String )]() 24 | 25 | public init (contact: CNContact) { 26 | firstName = contact.givenName 27 | lastName = contact.familyName 28 | company = contact.organizationName 29 | contactId = contact.identifier 30 | 31 | if let thumbnailImageData = contact.thumbnailImageData { 32 | thumbnailProfileImage = UIImage(data:thumbnailImageData) 33 | } 34 | 35 | if let imageData = contact.imageData { 36 | profileImage = UIImage(data:imageData) 37 | } 38 | 39 | if let birthdayDate = contact.birthday { 40 | 41 | birthday = Calendar(identifier: Calendar.Identifier.gregorian).date(from: birthdayDate) 42 | let dateFormatter = DateFormatter() 43 | dateFormatter.dateFormat = EPGlobalConstants.Strings.birdtdayDateFormat 44 | //Example Date Formats: Oct 4, Sep 18, Mar 9 45 | birthdayString = dateFormatter.string(from: birthday!) 46 | } 47 | 48 | for phoneNumber in contact.phoneNumbers { 49 | var phoneLabel = "phone" 50 | if let label = phoneNumber.label { 51 | phoneLabel = label 52 | } 53 | let phone = phoneNumber.value.stringValue 54 | 55 | phoneNumbers.append((phone,phoneLabel)) 56 | } 57 | 58 | for emailAddress in contact.emailAddresses { 59 | guard let emailLabel = emailAddress.label else { continue } 60 | let email = emailAddress.value as String 61 | 62 | emails.append((email,emailLabel)) 63 | } 64 | } 65 | 66 | open func displayName() -> String { 67 | return firstName + " " + lastName 68 | } 69 | 70 | open func contactInitials() -> String { 71 | var initials = String() 72 | 73 | if let firstNameFirstChar = firstName.characters.first { 74 | initials.append(firstNameFirstChar) 75 | } 76 | 77 | if let lastNameFirstChar = lastName.characters.first { 78 | initials.append(lastNameFirstChar) 79 | } 80 | 81 | return initials 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /Contacts Picker/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | EPContactsPicker 3 |

4 | 5 | EPContactsPicker 6 | =========== 7 | Contacts picker component using new contacts framework by apple 8 | 9 | 10 | [![Platform](https://img.shields.io/cocoapods/p/EPContactsPicker.svg?style=flat)](http://cocoapods.org/pods/EPContactsPicker) 11 | [![Swift 3](https://img.shields.io/badge/Swift-3.0-orange.svg?style=flat)](https://developer.apple.com/swift/) 12 | [![CocoaPods Compatible](https://img.shields.io/cocoapods/v/EPContactsPicker.svg?style=flat)](http://cocoadocs.org/docsets/EPContactsPicker) 13 | [![CI Status](https://travis-ci.org/ipraba/EPContactsPicker.svg?branch=master)](https://travis-ci.org/ipraba/EPContactsPicker) 14 | [![License](https://img.shields.io/cocoapods/l/Ouroboros.svg?style=flat)](https://github.com/ipraba/EPContactsPicker/blob/master/LICENSE) 15 | [![Twitter: @HaveYouMetPrabu](https://img.shields.io/badge/contact-@HaveYouMetPrabu-blue.svg?style=flat)](https://twitter.com/HaveYouMetPrabu) 16 | 17 | Preview 18 | ------- 19 | ![Single Selection](https://raw.githubusercontent.com/ipraba/EPContactsPicker/master/Screenshots/Screen2.png) ![Multi Selection](https://raw.githubusercontent.com/ipraba/EPContactsPicker/master/Screenshots/Screen3.png) 20 | 21 | # Installation # 22 | 23 | ## CocoaPods ## 24 | EPContactsPicker is available on CocoaPods. Just add the following to your project Podfile: 25 | 26 | `pod 'EPContactsPicker'` 27 | 28 | ## Manual Installation ## 29 | 30 | Just drag and drop the `EPContactsPicker` folder into your project 31 | 32 | # Requirements # 33 | 34 | * iOS9+ 35 | * Swift 3.0 36 | * ARC 37 | 38 | For manual installation you might have to add these frameworks in your Build Phases 39 | `ContactsUI.framework` and `Contacts.framework`. 40 | 41 | # Features # 42 | 43 | EPContacts Picker provides lot of features which lets you customize the picker 44 | 45 | 1. Single selection and multiselection option 46 | 2. Search Contacts 47 | 3. Making the secondary data to show as requested(Phonenumbers, Emails, Birthday and Organisation) 48 | 4. Section Indexes to easily navigate throught the contacts 49 | 5. Showing initials when image is not available 50 | 6. EPContact object to get the properties of the contacts 51 | 52 | # Initialization # 53 | 54 | Init the picker by passing delegate, multiselection option and the secondary data(Phone number, Email, brithday and Organisation) to be displayed 55 | 56 | let contactPickerScene = EPContactsPicker(delegate: self, multiSelection:false, subtitleCellType: SubtitleCellValue.Email) 57 | let navigationController = UINavigationController(rootViewController: contactPickerScene) 58 | self.presentViewController(navigationController, animated: true, completion: nil) 59 | 60 | # Delegates # 61 | 62 | EPContactsPicker provides you four delegates for getting the callbacks on the picker 63 | 64 | ```swift 65 | func epContactPicker(_: EPContactsPicker, didContactFetchFailed error : NSError) 66 | func epContactPicker(_: EPContactsPicker, didCancel error : NSError) 67 | func epContactPicker(_: EPContactsPicker, didSelectContact contact : EPContact) 68 | func epContactPicker(_: EPContactsPicker, didSelectMultipleContacts contacts : [EPContact]) 69 | ``` 70 | 71 | # EPContact Object # 72 | 73 | EPContact object provides you the properties of a contact. This contains properties like displayname, initials, firstname, lastname, organisation, birthdayString etc 74 | 75 | ## License ## 76 | 77 | EPContactsPicker is available under the MIT license. See the [LICENSE](https://github.com/ipraba/EPContactsPicker/blob/master/LICENSE) file for more info. 78 | 79 | ## Contributors ## 80 | 81 | [@ipraba](https://github.com/ipraba) 82 | [@Sorix](https://github.com/Sorix) 83 | 84 | -------------------------------------------------------------------------------- /Pods/EPContactCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EPContactCell.swift 3 | // EPContacts 4 | // 5 | // Created by Prabaharan Elangovan on 13/10/15. 6 | // Copyright © 2015 Prabaharan Elangovan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class EPContactCell: UITableViewCell { 12 | 13 | @IBOutlet weak var contactTextLabel: UILabel! 14 | @IBOutlet weak var contactDetailTextLabel: UILabel! 15 | @IBOutlet weak var contactImageView: UIImageView! 16 | @IBOutlet weak var contactInitialLabel: UILabel! 17 | @IBOutlet weak var contactContainerView: UIView! 18 | 19 | var contact: EPContact? 20 | 21 | override func awakeFromNib() { 22 | 23 | super.awakeFromNib() 24 | // Initialization code 25 | selectionStyle = UITableViewCellSelectionStyle.none 26 | contactContainerView.layer.masksToBounds = true 27 | contactContainerView.layer.cornerRadius = contactContainerView.frame.size.width/2 28 | } 29 | 30 | override func setSelected(_ selected: Bool, animated: Bool) { 31 | super.setSelected(selected, animated: animated) 32 | } 33 | 34 | func updateInitialsColorForIndexPath(_ indexpath: IndexPath) { 35 | //Applies color to Initial Label 36 | let colorArray = [EPGlobalConstants.Colors.amethystColor,EPGlobalConstants.Colors.asbestosColor,EPGlobalConstants.Colors.emeraldColor,EPGlobalConstants.Colors.peterRiverColor,EPGlobalConstants.Colors.pomegranateColor,EPGlobalConstants.Colors.pumpkinColor,EPGlobalConstants.Colors.sunflowerColor] 37 | let randomValue = (indexpath.row + indexpath.section) % colorArray.count 38 | contactInitialLabel.backgroundColor = colorArray[randomValue] 39 | } 40 | 41 | func updateContactsinUI(_ contact: EPContact, indexPath: IndexPath, subtitleType: SubtitleCellValue) { 42 | self.contact = contact 43 | //Update all UI in the cell here 44 | self.contactTextLabel?.text = contact.displayName() 45 | updateSubtitleBasedonType(subtitleType, contact: contact) 46 | if contact.thumbnailProfileImage != nil { 47 | self.contactImageView?.image = contact.thumbnailProfileImage 48 | self.contactImageView.isHidden = false 49 | self.contactInitialLabel.isHidden = true 50 | } else { 51 | self.contactInitialLabel.text = contact.contactInitials() 52 | updateInitialsColorForIndexPath(indexPath) 53 | self.contactImageView.isHidden = true 54 | self.contactInitialLabel.isHidden = false 55 | } 56 | } 57 | 58 | func updateSubtitleBasedonType(_ subtitleType: SubtitleCellValue , contact: EPContact) { 59 | 60 | switch subtitleType { 61 | 62 | case SubtitleCellValue.phoneNumber: 63 | let phoneNumberCount = contact.phoneNumbers.count 64 | 65 | if phoneNumberCount == 1 { 66 | self.contactDetailTextLabel.text = "\(contact.phoneNumbers[0].phoneNumber)" 67 | } 68 | else if phoneNumberCount > 1 { 69 | self.contactDetailTextLabel.text = "\(contact.phoneNumbers[0].phoneNumber) and \(contact.phoneNumbers.count-1) more" 70 | } 71 | else { 72 | self.contactDetailTextLabel.text = EPGlobalConstants.Strings.phoneNumberNotAvaialable 73 | } 74 | case SubtitleCellValue.email: 75 | let emailCount = contact.emails.count 76 | 77 | if emailCount == 1 { 78 | self.contactDetailTextLabel.text = "\(contact.emails[0].email)" 79 | } 80 | else if emailCount > 1 { 81 | self.contactDetailTextLabel.text = "\(contact.emails[0].email) and \(contact.emails.count-1) more" 82 | } 83 | else { 84 | self.contactDetailTextLabel.text = EPGlobalConstants.Strings.emailNotAvaialable 85 | } 86 | case SubtitleCellValue.birthday: 87 | self.contactDetailTextLabel.text = contact.birthdayString 88 | case SubtitleCellValue.organization: 89 | self.contactDetailTextLabel.text = contact.company 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Contacts Picker.xcodeproj/xcshareddata/xcschemes/Contacts Picker.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /Contacts Picker/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 29 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /Pods/EPContactCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 23 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /Contacts Picker.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | E210822E1D916C6F002CCAD5 /* EPContactsPicker.podspec in Resources */ = {isa = PBXBuildFile; fileRef = E210822D1D916C6F002CCAD5 /* EPContactsPicker.podspec */; }; 11 | F4C1C9421BDF8AB7001AA643 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4C1C9411BDF8AB7001AA643 /* AppDelegate.swift */; }; 12 | F4C1C9491BDF8AB7001AA643 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F4C1C9481BDF8AB7001AA643 /* Assets.xcassets */; }; 13 | F4C1C94C1BDF8AB7001AA643 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F4C1C94A1BDF8AB7001AA643 /* LaunchScreen.storyboard */; }; 14 | F4C1C9711BDF8BBA001AA643 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4C1C9701BDF8BBA001AA643 /* ViewController.swift */; }; 15 | F4C1C9741BDF8BE3001AA643 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F4C1C9721BDF8BE3001AA643 /* Main.storyboard */; }; 16 | F4C1C97C1BDF9227001AA643 /* EPConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4C1C9761BDF9227001AA643 /* EPConstants.swift */; }; 17 | F4C1C97D1BDF9227001AA643 /* EPContact.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4C1C9771BDF9227001AA643 /* EPContact.swift */; }; 18 | F4C1C97E1BDF9227001AA643 /* EPContactCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4C1C9781BDF9227001AA643 /* EPContactCell.swift */; }; 19 | F4C1C97F1BDF9227001AA643 /* EPContactCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F4C1C9791BDF9227001AA643 /* EPContactCell.xib */; }; 20 | F4C1C9801BDF9227001AA643 /* EPContactsPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4C1C97A1BDF9227001AA643 /* EPContactsPicker.swift */; }; 21 | F4C1C9811BDF9227001AA643 /* EPExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4C1C97B1BDF9227001AA643 /* EPExtensions.swift */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXFileReference section */ 25 | 4B7552C01CD28ADA00C638F1 /* .travis.yml */ = {isa = PBXFileReference; lastKnownFileType = text; path = .travis.yml; sourceTree = ""; }; 26 | E210822D1D916C6F002CCAD5 /* EPContactsPicker.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = EPContactsPicker.podspec; sourceTree = ""; }; 27 | F4C1C93E1BDF8AB7001AA643 /* Contacts Picker.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Contacts Picker.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 28 | F4C1C9411BDF8AB7001AA643 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 29 | F4C1C9481BDF8AB7001AA643 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 30 | F4C1C94B1BDF8AB7001AA643 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 31 | F4C1C94D1BDF8AB7001AA643 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 32 | F4C1C9701BDF8BBA001AA643 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ViewController.swift; path = "Contacts Picker/ViewController.swift"; sourceTree = SOURCE_ROOT; }; 33 | F4C1C9731BDF8BE3001AA643 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 34 | F4C1C9761BDF9227001AA643 /* EPConstants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EPConstants.swift; sourceTree = ""; }; 35 | F4C1C9771BDF9227001AA643 /* EPContact.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EPContact.swift; sourceTree = ""; }; 36 | F4C1C9781BDF9227001AA643 /* EPContactCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EPContactCell.swift; sourceTree = ""; }; 37 | F4C1C9791BDF9227001AA643 /* EPContactCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = EPContactCell.xib; sourceTree = ""; }; 38 | F4C1C97A1BDF9227001AA643 /* EPContactsPicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EPContactsPicker.swift; sourceTree = ""; }; 39 | F4C1C97B1BDF9227001AA643 /* EPExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EPExtensions.swift; sourceTree = ""; }; 40 | /* End PBXFileReference section */ 41 | 42 | /* Begin PBXFrameworksBuildPhase section */ 43 | F4C1C93B1BDF8AB7001AA643 /* Frameworks */ = { 44 | isa = PBXFrameworksBuildPhase; 45 | buildActionMask = 2147483647; 46 | files = ( 47 | ); 48 | runOnlyForDeploymentPostprocessing = 0; 49 | }; 50 | /* End PBXFrameworksBuildPhase section */ 51 | 52 | /* Begin PBXGroup section */ 53 | F4C1C9351BDF8AB7001AA643 = { 54 | isa = PBXGroup; 55 | children = ( 56 | 4B7552C01CD28ADA00C638F1 /* .travis.yml */, 57 | E210822D1D916C6F002CCAD5 /* EPContactsPicker.podspec */, 58 | F4C1C9401BDF8AB7001AA643 /* Contacts Picker */, 59 | F4C1C93F1BDF8AB7001AA643 /* Products */, 60 | ); 61 | sourceTree = ""; 62 | }; 63 | F4C1C93F1BDF8AB7001AA643 /* Products */ = { 64 | isa = PBXGroup; 65 | children = ( 66 | F4C1C93E1BDF8AB7001AA643 /* Contacts Picker.app */, 67 | ); 68 | name = Products; 69 | sourceTree = ""; 70 | }; 71 | F4C1C9401BDF8AB7001AA643 /* Contacts Picker */ = { 72 | isa = PBXGroup; 73 | children = ( 74 | F4C1C9751BDF9227001AA643 /* EPContactsPicker */, 75 | F4C1C9701BDF8BBA001AA643 /* ViewController.swift */, 76 | F4C1C9411BDF8AB7001AA643 /* AppDelegate.swift */, 77 | F4C1C9481BDF8AB7001AA643 /* Assets.xcassets */, 78 | F4C1C9721BDF8BE3001AA643 /* Main.storyboard */, 79 | F4C1C94A1BDF8AB7001AA643 /* LaunchScreen.storyboard */, 80 | F4C1C94D1BDF8AB7001AA643 /* Info.plist */, 81 | ); 82 | path = "Contacts Picker"; 83 | sourceTree = ""; 84 | }; 85 | F4C1C9751BDF9227001AA643 /* EPContactsPicker */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | F4C1C9761BDF9227001AA643 /* EPConstants.swift */, 89 | F4C1C9771BDF9227001AA643 /* EPContact.swift */, 90 | F4C1C9781BDF9227001AA643 /* EPContactCell.swift */, 91 | F4C1C9791BDF9227001AA643 /* EPContactCell.xib */, 92 | F4C1C97A1BDF9227001AA643 /* EPContactsPicker.swift */, 93 | F4C1C97B1BDF9227001AA643 /* EPExtensions.swift */, 94 | ); 95 | name = EPContactsPicker; 96 | path = Pods; 97 | sourceTree = SOURCE_ROOT; 98 | }; 99 | /* End PBXGroup section */ 100 | 101 | /* Begin PBXNativeTarget section */ 102 | F4C1C93D1BDF8AB7001AA643 /* Contacts Picker */ = { 103 | isa = PBXNativeTarget; 104 | buildConfigurationList = F4C1C95B1BDF8AB7001AA643 /* Build configuration list for PBXNativeTarget "Contacts Picker" */; 105 | buildPhases = ( 106 | F4C1C93A1BDF8AB7001AA643 /* Sources */, 107 | F4C1C93B1BDF8AB7001AA643 /* Frameworks */, 108 | F4C1C93C1BDF8AB7001AA643 /* Resources */, 109 | ); 110 | buildRules = ( 111 | ); 112 | dependencies = ( 113 | ); 114 | name = "Contacts Picker"; 115 | productName = "Contacts Picker"; 116 | productReference = F4C1C93E1BDF8AB7001AA643 /* Contacts Picker.app */; 117 | productType = "com.apple.product-type.application"; 118 | }; 119 | /* End PBXNativeTarget section */ 120 | 121 | /* Begin PBXProject section */ 122 | F4C1C9361BDF8AB7001AA643 /* Project object */ = { 123 | isa = PBXProject; 124 | attributes = { 125 | LastUpgradeCheck = 0800; 126 | ORGANIZATIONNAME = "Prabaharan Elangovan"; 127 | TargetAttributes = { 128 | F4C1C93D1BDF8AB7001AA643 = { 129 | CreatedOnToolsVersion = 7.0; 130 | LastSwiftMigration = 0800; 131 | ProvisioningStyle = Manual; 132 | }; 133 | }; 134 | }; 135 | buildConfigurationList = F4C1C9391BDF8AB7001AA643 /* Build configuration list for PBXProject "Contacts Picker" */; 136 | compatibilityVersion = "Xcode 3.2"; 137 | developmentRegion = English; 138 | hasScannedForEncodings = 0; 139 | knownRegions = ( 140 | en, 141 | Base, 142 | ); 143 | mainGroup = F4C1C9351BDF8AB7001AA643; 144 | productRefGroup = F4C1C93F1BDF8AB7001AA643 /* Products */; 145 | projectDirPath = ""; 146 | projectRoot = ""; 147 | targets = ( 148 | F4C1C93D1BDF8AB7001AA643 /* Contacts Picker */, 149 | ); 150 | }; 151 | /* End PBXProject section */ 152 | 153 | /* Begin PBXResourcesBuildPhase section */ 154 | F4C1C93C1BDF8AB7001AA643 /* Resources */ = { 155 | isa = PBXResourcesBuildPhase; 156 | buildActionMask = 2147483647; 157 | files = ( 158 | F4C1C94C1BDF8AB7001AA643 /* LaunchScreen.storyboard in Resources */, 159 | F4C1C9491BDF8AB7001AA643 /* Assets.xcassets in Resources */, 160 | F4C1C9741BDF8BE3001AA643 /* Main.storyboard in Resources */, 161 | F4C1C97F1BDF9227001AA643 /* EPContactCell.xib in Resources */, 162 | E210822E1D916C6F002CCAD5 /* EPContactsPicker.podspec in Resources */, 163 | ); 164 | runOnlyForDeploymentPostprocessing = 0; 165 | }; 166 | /* End PBXResourcesBuildPhase section */ 167 | 168 | /* Begin PBXSourcesBuildPhase section */ 169 | F4C1C93A1BDF8AB7001AA643 /* Sources */ = { 170 | isa = PBXSourcesBuildPhase; 171 | buildActionMask = 2147483647; 172 | files = ( 173 | F4C1C9421BDF8AB7001AA643 /* AppDelegate.swift in Sources */, 174 | F4C1C9801BDF9227001AA643 /* EPContactsPicker.swift in Sources */, 175 | F4C1C9711BDF8BBA001AA643 /* ViewController.swift in Sources */, 176 | F4C1C97C1BDF9227001AA643 /* EPConstants.swift in Sources */, 177 | F4C1C97E1BDF9227001AA643 /* EPContactCell.swift in Sources */, 178 | F4C1C97D1BDF9227001AA643 /* EPContact.swift in Sources */, 179 | F4C1C9811BDF9227001AA643 /* EPExtensions.swift in Sources */, 180 | ); 181 | runOnlyForDeploymentPostprocessing = 0; 182 | }; 183 | /* End PBXSourcesBuildPhase section */ 184 | 185 | /* Begin PBXVariantGroup section */ 186 | F4C1C94A1BDF8AB7001AA643 /* LaunchScreen.storyboard */ = { 187 | isa = PBXVariantGroup; 188 | children = ( 189 | F4C1C94B1BDF8AB7001AA643 /* Base */, 190 | ); 191 | name = LaunchScreen.storyboard; 192 | sourceTree = ""; 193 | }; 194 | F4C1C9721BDF8BE3001AA643 /* Main.storyboard */ = { 195 | isa = PBXVariantGroup; 196 | children = ( 197 | F4C1C9731BDF8BE3001AA643 /* Base */, 198 | ); 199 | name = Main.storyboard; 200 | sourceTree = ""; 201 | }; 202 | /* End PBXVariantGroup section */ 203 | 204 | /* Begin XCBuildConfiguration section */ 205 | F4C1C9591BDF8AB7001AA643 /* Debug */ = { 206 | isa = XCBuildConfiguration; 207 | buildSettings = { 208 | ALWAYS_SEARCH_USER_PATHS = NO; 209 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 210 | CLANG_CXX_LIBRARY = "libc++"; 211 | CLANG_ENABLE_MODULES = YES; 212 | CLANG_ENABLE_OBJC_ARC = YES; 213 | CLANG_WARN_BOOL_CONVERSION = YES; 214 | CLANG_WARN_CONSTANT_CONVERSION = YES; 215 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 216 | CLANG_WARN_EMPTY_BODY = YES; 217 | CLANG_WARN_ENUM_CONVERSION = YES; 218 | CLANG_WARN_INFINITE_RECURSION = YES; 219 | CLANG_WARN_INT_CONVERSION = YES; 220 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 221 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 222 | CLANG_WARN_UNREACHABLE_CODE = YES; 223 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 224 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 225 | COPY_PHASE_STRIP = NO; 226 | DEBUG_INFORMATION_FORMAT = dwarf; 227 | ENABLE_STRICT_OBJC_MSGSEND = YES; 228 | ENABLE_TESTABILITY = YES; 229 | GCC_C_LANGUAGE_STANDARD = gnu99; 230 | GCC_DYNAMIC_NO_PIC = NO; 231 | GCC_NO_COMMON_BLOCKS = YES; 232 | GCC_OPTIMIZATION_LEVEL = 0; 233 | GCC_PREPROCESSOR_DEFINITIONS = ( 234 | "DEBUG=1", 235 | "$(inherited)", 236 | ); 237 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 238 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 239 | GCC_WARN_UNDECLARED_SELECTOR = YES; 240 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 241 | GCC_WARN_UNUSED_FUNCTION = YES; 242 | GCC_WARN_UNUSED_VARIABLE = YES; 243 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 244 | MTL_ENABLE_DEBUG_INFO = YES; 245 | ONLY_ACTIVE_ARCH = YES; 246 | SDKROOT = iphoneos; 247 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 248 | TARGETED_DEVICE_FAMILY = "1,2"; 249 | }; 250 | name = Debug; 251 | }; 252 | F4C1C95A1BDF8AB7001AA643 /* Release */ = { 253 | isa = XCBuildConfiguration; 254 | buildSettings = { 255 | ALWAYS_SEARCH_USER_PATHS = NO; 256 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 257 | CLANG_CXX_LIBRARY = "libc++"; 258 | CLANG_ENABLE_MODULES = YES; 259 | CLANG_ENABLE_OBJC_ARC = YES; 260 | CLANG_WARN_BOOL_CONVERSION = YES; 261 | CLANG_WARN_CONSTANT_CONVERSION = YES; 262 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 263 | CLANG_WARN_EMPTY_BODY = YES; 264 | CLANG_WARN_ENUM_CONVERSION = YES; 265 | CLANG_WARN_INFINITE_RECURSION = YES; 266 | CLANG_WARN_INT_CONVERSION = YES; 267 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 268 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 269 | CLANG_WARN_UNREACHABLE_CODE = YES; 270 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 271 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 272 | COPY_PHASE_STRIP = NO; 273 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 274 | ENABLE_NS_ASSERTIONS = NO; 275 | ENABLE_STRICT_OBJC_MSGSEND = YES; 276 | GCC_C_LANGUAGE_STANDARD = gnu99; 277 | GCC_NO_COMMON_BLOCKS = YES; 278 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 279 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 280 | GCC_WARN_UNDECLARED_SELECTOR = YES; 281 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 282 | GCC_WARN_UNUSED_FUNCTION = YES; 283 | GCC_WARN_UNUSED_VARIABLE = YES; 284 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 285 | MTL_ENABLE_DEBUG_INFO = NO; 286 | SDKROOT = iphoneos; 287 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 288 | TARGETED_DEVICE_FAMILY = "1,2"; 289 | VALIDATE_PRODUCT = YES; 290 | }; 291 | name = Release; 292 | }; 293 | F4C1C95C1BDF8AB7001AA643 /* Debug */ = { 294 | isa = XCBuildConfiguration; 295 | buildSettings = { 296 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 297 | DEVELOPMENT_TEAM = ""; 298 | INFOPLIST_FILE = "Contacts Picker/Info.plist"; 299 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 300 | PRODUCT_BUNDLE_IDENTIFIER = "com.prabaharan.eppicker.Contacts-Picker"; 301 | PRODUCT_NAME = "$(TARGET_NAME)"; 302 | SWIFT_VERSION = 3.0; 303 | }; 304 | name = Debug; 305 | }; 306 | F4C1C95D1BDF8AB7001AA643 /* Release */ = { 307 | isa = XCBuildConfiguration; 308 | buildSettings = { 309 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 310 | DEVELOPMENT_TEAM = ""; 311 | INFOPLIST_FILE = "Contacts Picker/Info.plist"; 312 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 313 | PRODUCT_BUNDLE_IDENTIFIER = "com.prabaharan.eppicker.Contacts-Picker"; 314 | PRODUCT_NAME = "$(TARGET_NAME)"; 315 | SWIFT_VERSION = 3.0; 316 | }; 317 | name = Release; 318 | }; 319 | /* End XCBuildConfiguration section */ 320 | 321 | /* Begin XCConfigurationList section */ 322 | F4C1C9391BDF8AB7001AA643 /* Build configuration list for PBXProject "Contacts Picker" */ = { 323 | isa = XCConfigurationList; 324 | buildConfigurations = ( 325 | F4C1C9591BDF8AB7001AA643 /* Debug */, 326 | F4C1C95A1BDF8AB7001AA643 /* Release */, 327 | ); 328 | defaultConfigurationIsVisible = 0; 329 | defaultConfigurationName = Release; 330 | }; 331 | F4C1C95B1BDF8AB7001AA643 /* Build configuration list for PBXNativeTarget "Contacts Picker" */ = { 332 | isa = XCConfigurationList; 333 | buildConfigurations = ( 334 | F4C1C95C1BDF8AB7001AA643 /* Debug */, 335 | F4C1C95D1BDF8AB7001AA643 /* Release */, 336 | ); 337 | defaultConfigurationIsVisible = 0; 338 | defaultConfigurationName = Release; 339 | }; 340 | /* End XCConfigurationList section */ 341 | }; 342 | rootObject = F4C1C9361BDF8AB7001AA643 /* Project object */; 343 | } 344 | -------------------------------------------------------------------------------- /Pods/EPContactsPicker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EPContactsPicker.swift 3 | // EPContacts 4 | // 5 | // Created by Prabaharan Elangovan on 12/10/15. 6 | // Copyright © 2015 Prabaharan Elangovan. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Contacts 11 | 12 | 13 | public protocol EPPickerDelegate: class { 14 | func epContactPicker(_: EPContactsPicker, didContactFetchFailed error: NSError) 15 | func epContactPicker(_: EPContactsPicker, didCancel error: NSError) 16 | func epContactPicker(_: EPContactsPicker, didSelectContact contact: EPContact) 17 | func epContactPicker(_: EPContactsPicker, didSelectMultipleContacts contacts: [EPContact]) 18 | } 19 | 20 | public extension EPPickerDelegate { 21 | func epContactPicker(_: EPContactsPicker, didContactFetchFailed error: NSError) { } 22 | func epContactPicker(_: EPContactsPicker, didCancel error: NSError) { } 23 | func epContactPicker(_: EPContactsPicker, didSelectContact contact: EPContact) { } 24 | func epContactPicker(_: EPContactsPicker, didSelectMultipleContacts contacts: [EPContact]) { } 25 | } 26 | 27 | typealias ContactsHandler = (_ contacts : [CNContact] , _ error : NSError?) -> Void 28 | 29 | public enum SubtitleCellValue{ 30 | case phoneNumber 31 | case email 32 | case birthday 33 | case organization 34 | } 35 | 36 | open class EPContactsPicker: UITableViewController, UISearchResultsUpdating, UISearchBarDelegate { 37 | 38 | // MARK: - Properties 39 | 40 | open weak var contactDelegate: EPPickerDelegate? 41 | var contactsStore: CNContactStore? 42 | var resultSearchController = UISearchController() 43 | var orderedContacts = [String: [CNContact]]() //Contacts ordered in dicitonary alphabetically 44 | var sortedContactKeys = [String]() 45 | 46 | var selectedContacts = [EPContact]() 47 | var filteredContacts = [CNContact]() 48 | 49 | var subtitleCellValue = SubtitleCellValue.phoneNumber 50 | var multiSelectEnabled: Bool = false //Default is single selection contact 51 | 52 | // MARK: - Lifecycle Methods 53 | 54 | override open func viewDidLoad() { 55 | super.viewDidLoad() 56 | self.title = EPGlobalConstants.Strings.contactsTitle 57 | 58 | registerContactCell() 59 | inititlizeBarButtons() 60 | initializeSearchBar() 61 | reloadContacts() 62 | } 63 | 64 | func initializeSearchBar() { 65 | self.resultSearchController = ( { 66 | let controller = UISearchController(searchResultsController: nil) 67 | controller.searchResultsUpdater = self 68 | controller.dimsBackgroundDuringPresentation = false 69 | controller.hidesNavigationBarDuringPresentation = false 70 | controller.searchBar.sizeToFit() 71 | controller.searchBar.delegate = self 72 | self.tableView.tableHeaderView = controller.searchBar 73 | return controller 74 | })() 75 | } 76 | 77 | func inititlizeBarButtons() { 78 | let cancelButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.cancel, target: self, action: #selector(onTouchCancelButton)) 79 | self.navigationItem.leftBarButtonItem = cancelButton 80 | 81 | if multiSelectEnabled { 82 | let doneButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.done, target: self, action: #selector(onTouchDoneButton)) 83 | self.navigationItem.rightBarButtonItem = doneButton 84 | 85 | } 86 | } 87 | 88 | fileprivate func registerContactCell() { 89 | 90 | let podBundle = Bundle(for: self.classForCoder) 91 | if let bundleURL = podBundle.url(forResource: EPGlobalConstants.Strings.bundleIdentifier, withExtension: "bundle") { 92 | 93 | if let bundle = Bundle(url: bundleURL) { 94 | 95 | let cellNib = UINib(nibName: EPGlobalConstants.Strings.cellNibIdentifier, bundle: bundle) 96 | tableView.register(cellNib, forCellReuseIdentifier: "Cell") 97 | } 98 | else { 99 | assertionFailure("Could not load bundle") 100 | } 101 | } 102 | else { 103 | 104 | let cellNib = UINib(nibName: EPGlobalConstants.Strings.cellNibIdentifier, bundle: nil) 105 | tableView.register(cellNib, forCellReuseIdentifier: "Cell") 106 | } 107 | } 108 | 109 | override open func didReceiveMemoryWarning() { 110 | super.didReceiveMemoryWarning() 111 | // Dispose of any resources that can be recreated. 112 | } 113 | 114 | // MARK: - Initializers 115 | 116 | convenience public init(delegate: EPPickerDelegate?) { 117 | self.init(delegate: delegate, multiSelection: false) 118 | } 119 | 120 | convenience public init(delegate: EPPickerDelegate?, multiSelection : Bool) { 121 | self.init(style: .plain) 122 | self.multiSelectEnabled = multiSelection 123 | contactDelegate = delegate 124 | } 125 | 126 | convenience public init(delegate: EPPickerDelegate?, multiSelection : Bool, subtitleCellType: SubtitleCellValue) { 127 | self.init(style: .plain) 128 | self.multiSelectEnabled = multiSelection 129 | contactDelegate = delegate 130 | subtitleCellValue = subtitleCellType 131 | } 132 | 133 | 134 | // MARK: - Contact Operations 135 | 136 | open func reloadContacts() { 137 | getContacts( {(contacts, error) in 138 | if (error == nil) { 139 | DispatchQueue.main.async(execute: { 140 | self.tableView.reloadData() 141 | }) 142 | } 143 | }) 144 | } 145 | 146 | func getContacts(_ completion: @escaping ContactsHandler) { 147 | if contactsStore == nil { 148 | //ContactStore is control for accessing the Contacts 149 | contactsStore = CNContactStore() 150 | } 151 | let error = NSError(domain: "EPContactPickerErrorDomain", code: 1, userInfo: [NSLocalizedDescriptionKey: "No Contacts Access"]) 152 | 153 | switch CNContactStore.authorizationStatus(for: CNEntityType.contacts) { 154 | case CNAuthorizationStatus.denied, CNAuthorizationStatus.restricted: 155 | //User has denied the current app to access the contacts. 156 | 157 | let productName = Bundle.main.infoDictionary!["CFBundleName"]! 158 | 159 | let alert = UIAlertController(title: "Unable to access contacts", message: "\(productName) does not have access to contacts. Kindly enable it in privacy settings ", preferredStyle: UIAlertControllerStyle.alert) 160 | let okAction = UIAlertAction(title: "Ok", style: UIAlertActionStyle.default, handler: { action in 161 | completion([], error) 162 | self.dismiss(animated: true, completion: { 163 | self.contactDelegate?.epContactPicker(self, didContactFetchFailed: error) 164 | }) 165 | }) 166 | alert.addAction(okAction) 167 | self.present(alert, animated: true, completion: nil) 168 | 169 | case CNAuthorizationStatus.notDetermined: 170 | //This case means the user is prompted for the first time for allowing contacts 171 | contactsStore?.requestAccess(for: CNEntityType.contacts, completionHandler: { (granted, error) -> Void in 172 | //At this point an alert is provided to the user to provide access to contacts. This will get invoked if a user responds to the alert 173 | if (!granted ){ 174 | DispatchQueue.main.async(execute: { () -> Void in 175 | completion([], error! as NSError?) 176 | }) 177 | } 178 | else{ 179 | self.getContacts(completion) 180 | } 181 | }) 182 | 183 | case CNAuthorizationStatus.authorized: 184 | //Authorization granted by user for this app. 185 | var contactsArray = [CNContact]() 186 | 187 | let contactFetchRequest = CNContactFetchRequest(keysToFetch: allowedContactKeys()) 188 | 189 | do { 190 | try contactsStore?.enumerateContacts(with: contactFetchRequest, usingBlock: { (contact, stop) -> Void in 191 | //Ordering contacts based on alphabets in firstname 192 | contactsArray.append(contact) 193 | var key: String = "#" 194 | //If ordering has to be happening via family name change it here. 195 | if let firstLetter = contact.givenName[0..<1] , firstLetter.containsAlphabets() { 196 | key = firstLetter.uppercased() 197 | } 198 | var contacts = [CNContact]() 199 | 200 | if let segregatedContact = self.orderedContacts[key] { 201 | contacts = segregatedContact 202 | } 203 | contacts.append(contact) 204 | self.orderedContacts[key] = contacts 205 | 206 | }) 207 | self.sortedContactKeys = Array(self.orderedContacts.keys).sorted(by: <) 208 | if self.sortedContactKeys.first == "#" { 209 | self.sortedContactKeys.removeFirst() 210 | self.sortedContactKeys.append("#") 211 | } 212 | completion(contactsArray, nil) 213 | } 214 | //Catching exception as enumerateContactsWithFetchRequest can throw errors 215 | catch let error as NSError { 216 | print(error.localizedDescription) 217 | } 218 | 219 | } 220 | } 221 | 222 | func allowedContactKeys() -> [CNKeyDescriptor]{ 223 | //We have to provide only the keys which we have to access. We should avoid unnecessary keys when fetching the contact. Reducing the keys means faster the access. 224 | return [CNContactNamePrefixKey as CNKeyDescriptor, 225 | CNContactGivenNameKey as CNKeyDescriptor, 226 | CNContactFamilyNameKey as CNKeyDescriptor, 227 | CNContactOrganizationNameKey as CNKeyDescriptor, 228 | CNContactBirthdayKey as CNKeyDescriptor, 229 | CNContactImageDataKey as CNKeyDescriptor, 230 | CNContactThumbnailImageDataKey as CNKeyDescriptor, 231 | CNContactImageDataAvailableKey as CNKeyDescriptor, 232 | CNContactPhoneNumbersKey as CNKeyDescriptor, 233 | CNContactEmailAddressesKey as CNKeyDescriptor, 234 | ] 235 | } 236 | 237 | // MARK: - Table View DataSource 238 | 239 | override open func numberOfSections(in tableView: UITableView) -> Int { 240 | if resultSearchController.isActive { return 1 } 241 | return sortedContactKeys.count 242 | } 243 | 244 | override open func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 245 | if resultSearchController.isActive { return filteredContacts.count } 246 | if let contactsForSection = orderedContacts[sortedContactKeys[section]] { 247 | return contactsForSection.count 248 | } 249 | return 0 250 | } 251 | 252 | // MARK: - Table View Delegates 253 | 254 | override open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 255 | let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! EPContactCell 256 | cell.accessoryType = UITableViewCellAccessoryType.none 257 | //Convert CNContact to EPContact 258 | let contact: EPContact 259 | 260 | if resultSearchController.isActive { 261 | contact = EPContact(contact: filteredContacts[(indexPath as NSIndexPath).row]) 262 | } else { 263 | guard let contactsForSection = orderedContacts[sortedContactKeys[(indexPath as NSIndexPath).section]] else { 264 | assertionFailure() 265 | return UITableViewCell() 266 | } 267 | 268 | contact = EPContact(contact: contactsForSection[(indexPath as NSIndexPath).row]) 269 | } 270 | 271 | if multiSelectEnabled && selectedContacts.contains(where: { $0.contactId == contact.contactId }) { 272 | cell.accessoryType = UITableViewCellAccessoryType.checkmark 273 | } 274 | 275 | cell.updateContactsinUI(contact, indexPath: indexPath, subtitleType: subtitleCellValue) 276 | return cell 277 | } 278 | 279 | override open func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 280 | 281 | let cell = tableView.cellForRow(at: indexPath) as! EPContactCell 282 | let selectedContact = cell.contact! 283 | if multiSelectEnabled { 284 | //Keeps track of enable=ing and disabling contacts 285 | if cell.accessoryType == UITableViewCellAccessoryType.checkmark { 286 | cell.accessoryType = UITableViewCellAccessoryType.none 287 | selectedContacts = selectedContacts.filter(){ 288 | return selectedContact.contactId != $0.contactId 289 | } 290 | } 291 | else { 292 | cell.accessoryType = UITableViewCellAccessoryType.checkmark 293 | selectedContacts.append(selectedContact) 294 | } 295 | } 296 | else { 297 | //Single selection code 298 | resultSearchController.isActive = false 299 | self.dismiss(animated: true, completion: { 300 | DispatchQueue.main.async { 301 | self.contactDelegate?.epContactPicker(self, didSelectContact: selectedContact) 302 | } 303 | }) 304 | } 305 | } 306 | 307 | override open func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 308 | return 60.0 309 | } 310 | 311 | override open func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int { 312 | if resultSearchController.isActive { return 0 } 313 | tableView.scrollToRow(at: IndexPath(row: 0, section: index), at: UITableViewScrollPosition.top , animated: false) 314 | return sortedContactKeys.index(of: title)! 315 | } 316 | 317 | override open func sectionIndexTitles(for tableView: UITableView) -> [String]? { 318 | if resultSearchController.isActive { return nil } 319 | return sortedContactKeys 320 | } 321 | 322 | override open func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 323 | if resultSearchController.isActive { return nil } 324 | return sortedContactKeys[section] 325 | } 326 | 327 | // MARK: - Button Actions 328 | 329 | func onTouchCancelButton() { 330 | dismiss(animated: true, completion: { 331 | self.contactDelegate?.epContactPicker(self, didCancel: NSError(domain: "EPContactPickerErrorDomain", code: 2, userInfo: [ NSLocalizedDescriptionKey: "User Canceled Selection"])) 332 | }) 333 | } 334 | 335 | func onTouchDoneButton() { 336 | dismiss(animated: true, completion: { 337 | self.contactDelegate?.epContactPicker(self, didSelectMultipleContacts: self.selectedContacts) 338 | }) 339 | } 340 | 341 | // MARK: - Search Actions 342 | 343 | open func updateSearchResults(for searchController: UISearchController) 344 | { 345 | if let searchText = resultSearchController.searchBar.text , searchController.isActive { 346 | 347 | let predicate: NSPredicate 348 | if searchText.characters.count > 0 { 349 | predicate = CNContact.predicateForContacts(matchingName: searchText) 350 | } else { 351 | predicate = CNContact.predicateForContactsInContainer(withIdentifier: contactsStore!.defaultContainerIdentifier()) 352 | } 353 | 354 | let store = CNContactStore() 355 | do { 356 | filteredContacts = try store.unifiedContacts(matching: predicate, 357 | keysToFetch: allowedContactKeys()) 358 | //print("\(filteredContacts.count) count") 359 | 360 | self.tableView.reloadData() 361 | 362 | } 363 | catch { 364 | print("Error!") 365 | } 366 | } 367 | } 368 | 369 | open func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { 370 | 371 | DispatchQueue.main.async(execute: { 372 | self.tableView.reloadData() 373 | }) 374 | } 375 | 376 | } 377 | --------------------------------------------------------------------------------