├── AddressBookContacts ├── Assets.xcassets │ ├── Contents.json │ ├── background.imageset │ │ ├── background.jpg │ │ └── Contents.json │ ├── defaultUser.imageset │ │ ├── defaultUser@2x.png │ │ ├── defaultUser@3x.png │ │ └── Contents.json │ └── AppIcon.appiconset │ │ └── Contents.json ├── String+Email.swift ├── UIViewController+Alerts.swift ├── ViewController.swift ├── ContactTableViewCell.swift ├── Info.plist ├── AppDelegate.swift ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── ContactEntry.swift ├── AddressBookViewController.swift ├── ContactsViewController.swift ├── ContactTableViewCell.xib └── CreateContactViewController.swift ├── AddressBookContacts.xcodeproj ├── project.xcworkspace │ └── contents.xcworkspacedata ├── xcuserdata │ └── Nacho.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ ├── xcschememanagement.plist │ │ └── AddressBookContacts.xcscheme └── project.pbxproj └── README.md /AddressBookContacts/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /AddressBookContacts/Assets.xcassets/background.imageset/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DigitalLeaves/AddressBookContacts/HEAD/AddressBookContacts/Assets.xcassets/background.imageset/background.jpg -------------------------------------------------------------------------------- /AddressBookContacts/Assets.xcassets/defaultUser.imageset/defaultUser@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DigitalLeaves/AddressBookContacts/HEAD/AddressBookContacts/Assets.xcassets/defaultUser.imageset/defaultUser@2x.png -------------------------------------------------------------------------------- /AddressBookContacts/Assets.xcassets/defaultUser.imageset/defaultUser@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DigitalLeaves/AddressBookContacts/HEAD/AddressBookContacts/Assets.xcassets/defaultUser.imageset/defaultUser@3x.png -------------------------------------------------------------------------------- /AddressBookContacts.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /AddressBookContacts/Assets.xcassets/background.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "background.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /AddressBookContacts/Assets.xcassets/defaultUser.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "defaultUser@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "defaultUser@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /AddressBookContacts.xcodeproj/xcuserdata/Nacho.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /AddressBookContacts.xcodeproj/xcuserdata/Nacho.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | AddressBookContacts.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 6624EE1A1CC8161B005B6E6B 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /AddressBookContacts/String+Email.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+Email.swift 3 | // AddressBookContacts 4 | // 5 | // Created by Ignacio Nieto Carvajal on 21/4/16. 6 | // Copyright © 2016 Ignacio Nieto Carvajal. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension String { 12 | 13 | func isEmail() -> Bool { 14 | do { 15 | let regex = try NSRegularExpression(pattern: "^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]+$", options: NSRegularExpression.Options.caseInsensitive) 16 | return regex.firstMatch(in: self, options: [], range: NSMakeRange(0, self.characters.count)) != nil 17 | } catch { return false } 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /AddressBookContacts/UIViewController+Alerts.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController+Alerts.swift 3 | // AddressBookContacts 4 | // 5 | // Created by Ignacio Nieto Carvajal on 20/4/16. 6 | // Copyright © 2016 Ignacio Nieto Carvajal. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIViewController { 12 | /** 13 | * Shows a default alert/info message with an OK button. 14 | */ 15 | func showAlertMessage(_ message: String, okButtonTitle: String = "Ok") -> Void { 16 | let alert = UIAlertController(title: nil, message: message, preferredStyle: .alert) 17 | let okAction = UIAlertAction(title: okButtonTitle, style: .default, handler: nil) 18 | alert.addAction(okAction) 19 | self.present(alert, animated: true, completion: nil) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /AddressBookContacts/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | } 43 | ], 44 | "info" : { 45 | "version" : 1, 46 | "author" : "xcode" 47 | } 48 | } -------------------------------------------------------------------------------- /AddressBookContacts/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // AddressBookContacts 4 | // 5 | // Created by Ignacio Nieto Carvajal on 20/4/16. 6 | // Copyright © 2016 Ignacio Nieto Carvajal. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 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 addressBookFramework(_ sender: AnyObject) { 24 | self.performSegue(withIdentifier: "AddressBookFramework", sender: sender) 25 | } 26 | 27 | @IBAction func contactsFramework(_ sender: AnyObject) { 28 | if #available(iOS 9, *) { 29 | self.performSegue(withIdentifier: "ContactsFramework", sender: sender) 30 | } else { 31 | self.showAlertMessage("Sorry, only available for iOS 9 and up.") 32 | } 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /AddressBookContacts/ContactTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContactTableViewCell.swift 3 | // AddressBookContacts 4 | // 5 | // Created by Ignacio Nieto Carvajal on 20/4/16. 6 | // Copyright © 2016 Ignacio Nieto Carvajal. All rights reserved. 7 | // 8 | 9 | import AddressBook 10 | import Contacts 11 | import UIKit 12 | 13 | class ContactTableViewCell: UITableViewCell { 14 | // outlets 15 | @IBOutlet weak var contactImageView: UIImageView! 16 | @IBOutlet weak var contactNameLabel: UILabel! 17 | @IBOutlet weak var contactEmailLabel: UILabel! 18 | @IBOutlet weak var contactPhoneLabel: UILabel! 19 | 20 | override func awakeFromNib() { 21 | super.awakeFromNib() 22 | // Initialization code 23 | } 24 | 25 | override func setSelected(_ selected: Bool, animated: Bool) { 26 | super.setSelected(selected, animated: animated) 27 | 28 | // Configure the view for the selected state 29 | } 30 | 31 | func setCircularAvatar() { 32 | contactImageView.layer.cornerRadius = contactImageView.bounds.size.width / 2.0 33 | contactImageView.layer.masksToBounds = true 34 | } 35 | 36 | override func layoutIfNeeded() { 37 | super.layoutIfNeeded() 38 | setCircularAvatar() 39 | } 40 | 41 | func configureWithContactEntry(_ contact: ContactEntry) { 42 | contactNameLabel.text = contact.name 43 | contactEmailLabel.text = contact.email ?? "" 44 | contactPhoneLabel.text = contact.phone ?? "" 45 | contactImageView.image = contact.image ?? UIImage(named: "defaultUser") 46 | setCircularAvatar() 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /AddressBookContacts/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | NSContactsUsageDescription 26 | All of your contacts are belong to us... 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | NSPhotoLibraryUsageDescription 32 | AddressBookContacts needs to access your photo library for setting the contact's image. 33 | UIRequiredDeviceCapabilities 34 | 35 | armv7 36 | 37 | UISupportedInterfaceOrientations 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationLandscapeLeft 41 | UIInterfaceOrientationLandscapeRight 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /AddressBookContacts/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // AddressBookContacts 4 | // 5 | // Created by Ignacio Nieto Carvajal on 20/4/16. 6 | // Copyright © 2016 Ignacio Nieto Carvajal. 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 | -------------------------------------------------------------------------------- /AddressBookContacts/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /AddressBookContacts/ContactEntry.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContactEntry.swift 3 | // AddressBookContacts 4 | // 5 | // Created by Ignacio Nieto Carvajal on 20/4/16. 6 | // Copyright © 2016 Ignacio Nieto Carvajal. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AddressBook 11 | import Contacts 12 | 13 | class ContactEntry: NSObject { 14 | var name: String! 15 | var email: String? 16 | var phone: String? 17 | var image: UIImage? 18 | 19 | init(name: String, email: String?, phone: String?, image: UIImage?) { 20 | self.name = name 21 | self.email = email 22 | self.phone = phone 23 | self.image = image 24 | } 25 | 26 | init?(addressBookEntry: ABRecord) { 27 | super.init() 28 | 29 | // get AddressBook references (old-style) 30 | guard let nameRef = ABRecordCopyCompositeName(addressBookEntry)?.takeRetainedValue() else { return nil } 31 | 32 | // name 33 | self.name = nameRef as String 34 | 35 | // emails 36 | if let emailsMultivalueRef = ABRecordCopyValue(addressBookEntry, kABPersonEmailProperty)?.takeRetainedValue(), let emailsRef = ABMultiValueCopyArrayOfAllValues(emailsMultivalueRef)?.takeRetainedValue() { 37 | let emailsArray = emailsRef as NSArray 38 | for possibleEmail in emailsArray { if let properEmail = possibleEmail as? String , properEmail.isEmail() { self.email = properEmail; break } } 39 | } 40 | 41 | 42 | // image 43 | var image: UIImage? 44 | if ABPersonHasImageData(addressBookEntry) { 45 | image = UIImage(data: ABPersonCopyImageData(addressBookEntry).takeRetainedValue() as Data) 46 | } 47 | self.image = image ?? UIImage(named: "defaultUser") 48 | 49 | // phone 50 | if let phonesMultivalueRef = ABRecordCopyValue(addressBookEntry, kABPersonPhoneProperty)?.takeRetainedValue(), let phonesRef = ABMultiValueCopyArrayOfAllValues(phonesMultivalueRef)?.takeRetainedValue() { 51 | let phonesArray = phonesRef as NSArray 52 | if phonesArray.count > 0 { self.phone = phonesArray[0] as? String } 53 | } 54 | 55 | } 56 | 57 | @available(iOS 9.0, *) 58 | init?(cnContact: CNContact) { 59 | // name 60 | if !cnContact.isKeyAvailable(CNContactGivenNameKey) && !cnContact.isKeyAvailable(CNContactFamilyNameKey) { return nil } 61 | self.name = (cnContact.givenName + " " + cnContact.familyName).trimmingCharacters(in: CharacterSet.whitespaces) 62 | // image 63 | self.image = (cnContact.isKeyAvailable(CNContactImageDataKey) && cnContact.imageDataAvailable) ? UIImage(data: cnContact.imageData!) : nil 64 | // email 65 | if cnContact.isKeyAvailable(CNContactEmailAddressesKey) { 66 | for possibleEmail in cnContact.emailAddresses { 67 | let properEmail = possibleEmail.value as String 68 | if properEmail.isEmail() { self.email = properEmail; break } 69 | } 70 | } 71 | // phone 72 | if cnContact.isKeyAvailable(CNContactPhoneNumbersKey) { 73 | if cnContact.phoneNumbers.count > 0 { 74 | let phone = cnContact.phoneNumbers.first?.value 75 | self.phone = phone?.stringValue 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AddressBookContacts 2 | 3 | This is the sample project for the article on contacts management in swift found here: http://digitalleaves.com/blog/2016/04/managing-contacts-in-swift-addressbook-and-contacts-frameworks/ 4 | 5 | Accessing contacts in swift is not as hard as dealing with cryptography on iOS, but the AddressBook framework has never been specially developer-friendly, and the strongly typed nature of Swift and its deliberated distance to C makes it even tedious to use. Luckily for us, Apple introduced the Contacts framework with iOS 9, but there's still many chances that you will need to support iOS 8 and prior. 6 | 7 | In this project, we are going to delve into both frameworks and compare them in terms of convenience and ease of use in swift. At the end of the day, both frameworks work in quite a similar way, and both will allow us to access and modify the same information, so it's a matter of whether you need to support iOS 8 devices or not. 8 | 9 | ## Our sample application 10 | 11 | Our sample application it's a really simple app with three main screens. The first one will allow us to choose between using the AddressBook or Contacts frameworks. The second screen will retrieve our device's contacts and show them in a (more or less) fancy way, and the third one will allow us to create a new contact. 12 | 13 | ![](http://digitalleaves.com/wp-content/uploads/2016/04/contactsApp.jpg) 14 | 15 | We will set our application for targeting iOS 8+ devices, and we will use the #available(...) directive to 16 | 17 | ``` 18 | if #available(iOS 9, *) { 19 | callContactFrameworkRelatedFunctions(parameters) 20 | } else { 21 | self.showAlertMessage("Sorry, you can only use the Contacts framework from iOS 9.") 22 | } 23 | ``` 24 | 25 | The first screen is just two buttons that will take us to either our AddressBookViewController (that will use the old, C-Style AddressBook framework) or the ContactsViewController (that will use the new Contacts framework). Both of them will have a "Create" button to create a new contact, that will take us to the CreateContactViewController. This contact will handle the creation in both frameworks. 26 | 27 | ![](http://digitalleaves.com/wp-content/uploads/2016/04/appStructure-1024x524.png) 28 | 29 | # License 30 | The MIT License (MIT) 31 | Copyright (c) 2016 Ignacio Nieto Carvajal (https://digitalleaves.com). 32 | 33 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 34 | 35 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 36 | 37 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 38 | -------------------------------------------------------------------------------- /AddressBookContacts.xcodeproj/xcuserdata/Nacho.xcuserdatad/xcschemes/AddressBookContacts.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /AddressBookContacts/AddressBookViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AddressBookViewController.swift 3 | // AddressBookContacts 4 | // 5 | // Created by Ignacio Nieto Carvajal on 20/4/16. 6 | // Copyright © 2016 Ignacio Nieto Carvajal. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AddressBook 11 | fileprivate func < (lhs: T?, rhs: T?) -> Bool { 12 | switch (lhs, rhs) { 13 | case let (l?, r?): 14 | return l < r 15 | case (nil, _?): 16 | return true 17 | default: 18 | return false 19 | } 20 | } 21 | 22 | fileprivate func > (lhs: T?, rhs: T?) -> Bool { 23 | switch (lhs, rhs) { 24 | case let (l?, r?): 25 | return l > r 26 | default: 27 | return rhs < lhs 28 | } 29 | } 30 | 31 | 32 | class AddressBookViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { 33 | // outlets 34 | @IBOutlet weak var tableView: UITableView! 35 | @IBOutlet weak var noContactsLabel: UILabel! 36 | 37 | // data 38 | var contacts = [ContactEntry]() 39 | 40 | override func viewDidLoad() { 41 | super.viewDidLoad() 42 | 43 | tableView.register(UINib(nibName: "ContactTableViewCell", bundle: nil), forCellReuseIdentifier: "ContactTableViewCell") 44 | } 45 | 46 | override func viewWillAppear(_ animated: Bool) { 47 | super.viewWillAppear(animated) 48 | // initial appearance 49 | tableView.isHidden = true 50 | noContactsLabel.isHidden = false 51 | noContactsLabel.text = "Retrieving contacts..." 52 | 53 | retrieveAddressBookContacts { (success, contacts) in 54 | self.tableView.isHidden = !success 55 | self.noContactsLabel.isHidden = success 56 | if success && contacts?.count > 0 { 57 | self.contacts = contacts! 58 | self.tableView.reloadData() 59 | } else { 60 | self.noContactsLabel.text = "Unable to get contacts..." 61 | } 62 | } 63 | } 64 | 65 | override func didReceiveMemoryWarning() { 66 | super.didReceiveMemoryWarning() 67 | // Dispose of any resources that can be recreated. 68 | } 69 | 70 | @IBAction func goBack(_ sender: AnyObject) { 71 | self.presentingViewController?.dismiss(animated: true, completion: nil) 72 | } 73 | 74 | @IBAction func createNewContact(_ sender: AnyObject) { 75 | self.performSegue(withIdentifier: "CreateContact", sender: sender) 76 | } 77 | 78 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 79 | if let dvc = segue.destination as? CreateContactViewController { 80 | dvc.type = .addressBookContact 81 | } 82 | } 83 | 84 | // AddressBook methods 85 | func retrieveAddressBookContacts(_ completion: @escaping (_ success: Bool, _ contacts: [ContactEntry]?) -> Void) { 86 | let abAuthStatus = ABAddressBookGetAuthorizationStatus() 87 | if abAuthStatus == .denied || abAuthStatus == .restricted { 88 | completion(false, nil) 89 | return 90 | } 91 | 92 | let addressBookRef = ABAddressBookCreateWithOptions(nil, nil).takeRetainedValue() 93 | 94 | ABAddressBookRequestAccessWithCompletion(addressBookRef) { 95 | (granted: Bool, error: CFError?) in 96 | DispatchQueue.main.async { 97 | if !granted { 98 | self.showAlertMessage("Sorry, you have no permission for accessing the address book contacts.") 99 | } else { 100 | var contacts = [ContactEntry]() 101 | let abPeople = ABAddressBookCopyArrayOfAllPeople(addressBookRef).takeRetainedValue() as Array 102 | for abPerson in abPeople { 103 | if let contact = ContactEntry(addressBookEntry: abPerson) { contacts.append(contact) } 104 | } 105 | completion(true, contacts) 106 | } 107 | } 108 | } 109 | } 110 | 111 | // UITableViewDataSource && Delegate methods 112 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 113 | return contacts.count 114 | } 115 | 116 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 117 | let cell = tableView.dequeueReusableCell(withIdentifier: "ContactTableViewCell", for: indexPath) as! ContactTableViewCell 118 | let entry = contacts[(indexPath as NSIndexPath).row] 119 | cell.configureWithContactEntry(entry) 120 | cell.layoutIfNeeded() 121 | return cell 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /AddressBookContacts/ContactsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContactsViewController.swift 3 | // AddressBookContacts 4 | // 5 | // Created by Ignacio Nieto Carvajal on 20/4/16. 6 | // Copyright © 2016 Ignacio Nieto Carvajal. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Contacts 11 | fileprivate func < (lhs: T?, rhs: T?) -> Bool { 12 | switch (lhs, rhs) { 13 | case let (l?, r?): 14 | return l < r 15 | case (nil, _?): 16 | return true 17 | default: 18 | return false 19 | } 20 | } 21 | 22 | fileprivate func > (lhs: T?, rhs: T?) -> Bool { 23 | switch (lhs, rhs) { 24 | case let (l?, r?): 25 | return l > r 26 | default: 27 | return rhs < lhs 28 | } 29 | } 30 | 31 | 32 | @available(iOS 9.0, *) 33 | class ContactsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { 34 | // outlets 35 | @IBOutlet weak var tableView: UITableView! 36 | @IBOutlet weak var noContactsLabel: UILabel! 37 | 38 | // data 39 | var contactStore = CNContactStore() 40 | var contacts = [ContactEntry]() 41 | 42 | override func viewDidLoad() { 43 | super.viewDidLoad() 44 | 45 | tableView.register(UINib(nibName: "ContactTableViewCell", bundle: nil), forCellReuseIdentifier: "ContactTableViewCell") 46 | } 47 | 48 | override func viewWillAppear(_ animated: Bool) { 49 | super.viewWillAppear(animated) 50 | tableView.isHidden = true 51 | noContactsLabel.isHidden = false 52 | noContactsLabel.text = "Retrieving contacts..." 53 | } 54 | 55 | override func viewDidAppear(_ animated: Bool) { 56 | super.viewDidAppear(animated) 57 | requestAccessToContacts { (success) in 58 | if success { 59 | self.retrieveContacts({ (success, contacts) in 60 | self.tableView.isHidden = !success 61 | self.noContactsLabel.isHidden = success 62 | if success && contacts?.count > 0 { 63 | self.contacts = contacts! 64 | self.tableView.reloadData() 65 | } else { 66 | self.noContactsLabel.text = "Unable to get contacts..." 67 | } 68 | }) 69 | } 70 | } 71 | } 72 | 73 | 74 | func requestAccessToContacts(_ completion: @escaping (_ success: Bool) -> Void) { 75 | let authorizationStatus = CNContactStore.authorizationStatus(for: CNEntityType.contacts) 76 | 77 | switch authorizationStatus { 78 | case .authorized: completion(true) // authorized previously 79 | case .denied, .notDetermined: // needs to ask for authorization 80 | self.contactStore.requestAccess(for: CNEntityType.contacts, completionHandler: { (accessGranted, error) -> Void in 81 | completion(accessGranted) 82 | }) 83 | default: // not authorized. 84 | completion(false) 85 | } 86 | } 87 | 88 | func retrieveContacts(_ completion: (_ success: Bool, _ contacts: [ContactEntry]?) -> Void) { 89 | var contacts = [ContactEntry]() 90 | do { 91 | let contactsFetchRequest = CNContactFetchRequest(keysToFetch: [CNContactGivenNameKey as CNKeyDescriptor, CNContactFamilyNameKey as CNKeyDescriptor, CNContactImageDataKey as CNKeyDescriptor, CNContactImageDataAvailableKey as CNKeyDescriptor, CNContactPhoneNumbersKey as CNKeyDescriptor, CNContactEmailAddressesKey as CNKeyDescriptor]) 92 | try contactStore.enumerateContacts(with: contactsFetchRequest, usingBlock: { (cnContact, error) in 93 | if let contact = ContactEntry(cnContact: cnContact) { contacts.append(contact) } 94 | }) 95 | completion(true, contacts) 96 | } catch { 97 | completion(false, nil) 98 | } 99 | } 100 | 101 | override func didReceiveMemoryWarning() { 102 | super.didReceiveMemoryWarning() 103 | // Dispose of any resources that can be recreated. 104 | } 105 | 106 | @IBAction func goBack(_ sender: AnyObject) { 107 | self.presentingViewController?.dismiss(animated: true, completion: nil) 108 | } 109 | 110 | @IBAction func createNewContact(_ sender: AnyObject) { 111 | self.performSegue(withIdentifier: "CreateContact", sender: sender) 112 | } 113 | 114 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 115 | if let dvc = segue.destination as? CreateContactViewController { 116 | dvc.type = .cnContact 117 | } 118 | } 119 | 120 | // UITableViewDataSource && Delegate methods 121 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 122 | return contacts.count 123 | } 124 | 125 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 126 | let cell = tableView.dequeueReusableCell(withIdentifier: "ContactTableViewCell", for: indexPath) as! ContactTableViewCell 127 | let entry = contacts[(indexPath as NSIndexPath).row] 128 | cell.configureWithContactEntry(entry) 129 | cell.layoutIfNeeded() 130 | 131 | return cell 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /AddressBookContacts/ContactTableViewCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 31 | 37 | 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 | -------------------------------------------------------------------------------- /AddressBookContacts/CreateContactViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CreateContactViewController.swift 3 | // AddressBookContacts 4 | // 5 | // Created by Ignacio Nieto Carvajal on 21/4/16. 6 | // Copyright © 2016 Ignacio Nieto Carvajal. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Contacts 11 | import AddressBook 12 | 13 | enum ContactType { 14 | case addressBookContact 15 | case cnContact 16 | } 17 | 18 | class CreateContactViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate { 19 | // outlets 20 | @IBOutlet weak var contactImageView: UIImageView! 21 | @IBOutlet weak var firstNameTextfield: UITextField! 22 | @IBOutlet weak var lastNameTextfield: UITextField! 23 | @IBOutlet weak var emailAddressTextfield: UITextField! 24 | @IBOutlet weak var phoneNumberTextfield: UITextField! 25 | 26 | // data 27 | var type: ContactType? 28 | var contactImage: UIImage? 29 | 30 | override func viewDidLoad() { 31 | super.viewDidLoad() 32 | 33 | // Do any additional setup after loading the view. 34 | } 35 | 36 | override func viewWillAppear(_ animated: Bool) { 37 | super.viewWillAppear(animated) 38 | contactImageView.layer.cornerRadius = contactImageView.frame.size.width / 2.0 39 | contactImageView.layer.masksToBounds = true 40 | } 41 | 42 | override func didReceiveMemoryWarning() { 43 | super.didReceiveMemoryWarning() 44 | // Dispose of any resources that can be recreated. 45 | } 46 | 47 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 48 | self.view.endEditing(true) 49 | } 50 | 51 | // create contact 52 | func createAddressBookContactWithFirstName(_ firstName: String, lastName: String, email: String?, phone: String?, image: UIImage?) { 53 | // first check permissions. 54 | let abAuthStatus = ABAddressBookGetAuthorizationStatus() 55 | if abAuthStatus == .denied || abAuthStatus == .restricted { 56 | self.showAlertMessage("Sorry, you are not authorize to access the contacts.") 57 | return 58 | } 59 | 60 | // get addressbook reference. 61 | let addressBookRef = ABAddressBookCreateWithOptions(nil, nil).takeRetainedValue() 62 | // now let's create the contact. 63 | let newContact: ABRecord = ABPersonCreate().takeRetainedValue() 64 | 65 | // first name 66 | if !ABRecordSetValue(newContact, kABPersonFirstNameProperty, firstName as CFTypeRef, nil) { 67 | self.showAlertMessage("Error setting first name for the new contact") 68 | return 69 | } 70 | // last name 71 | if !ABRecordSetValue(newContact, kABPersonLastNameProperty, lastName as CFTypeRef, nil) { 72 | self.showAlertMessage("Error setting last name for the new contact") 73 | return 74 | } 75 | // email 76 | if email != nil { 77 | let emails: ABMutableMultiValue = 78 | ABMultiValueCreateMutable(ABPropertyType(kABMultiStringPropertyType)).takeRetainedValue() 79 | ABMultiValueAddValueAndLabel(emails, email! as CFTypeRef!, kABHomeLabel, nil) 80 | if !ABRecordSetValue(newContact, kABPersonEmailProperty, emails, nil) { 81 | self.showAlertMessage("Error setting email for the new contact") 82 | return 83 | } 84 | 85 | } 86 | 87 | // phone number 88 | if phone != nil { 89 | let phoneNumbers: ABMutableMultiValue = 90 | ABMultiValueCreateMutable(ABPropertyType(kABMultiStringPropertyType)).takeRetainedValue() 91 | ABMultiValueAddValueAndLabel(phoneNumbers, phone! as CFTypeRef!, kABPersonPhoneMainLabel, nil) 92 | if !ABRecordSetValue(newContact, kABPersonPhoneProperty, phoneNumbers, nil) { 93 | self.showAlertMessage("Error setting phone number for the new contact") 94 | return 95 | } 96 | } 97 | 98 | // image 99 | if image != nil { 100 | let imageData = UIImageJPEGRepresentation(image!, 0.9) 101 | if !ABPersonSetImageData(newContact, imageData as CFData!, nil) { 102 | self.showAlertMessage("Error setting image for the new contact") 103 | return 104 | } 105 | } 106 | 107 | // finally, store person and save addressbook 108 | var errorSavingContact = false 109 | if ABAddressBookAddRecord(addressBookRef, newContact, nil) { // stored. Now save addressbook. 110 | if ABAddressBookHasUnsavedChanges(addressBookRef){ 111 | if !ABAddressBookSave(addressBookRef, nil) { 112 | errorSavingContact = true 113 | } 114 | } 115 | } 116 | 117 | if errorSavingContact { self.showAlertMessage("There was an error storing your new contact. Please try again.") } 118 | else { self.presentingViewController?.dismiss(animated: true, completion: nil) } 119 | } 120 | 121 | @available(iOS 9.0, *) 122 | func createCNContactWithFirstName(_ firstName: String, lastName: String, email: String?, phone: String?, image: UIImage?) { 123 | // create contact with mandatory values: first and last name 124 | let newContact = CNMutableContact() 125 | newContact.givenName = firstName 126 | newContact.familyName = lastName 127 | 128 | // email 129 | if email != nil { 130 | let contactEmail = CNLabeledValue(label: CNLabelHome, value: email! as NSString) 131 | newContact.emailAddresses = [contactEmail] 132 | } 133 | // phone 134 | if phone != nil { 135 | let contactPhone = CNLabeledValue(label: CNLabelHome, value: CNPhoneNumber(stringValue: phone!)) 136 | newContact.phoneNumbers = [contactPhone] 137 | } 138 | 139 | // image 140 | if image != nil { 141 | newContact.imageData = UIImageJPEGRepresentation(image!, 0.9) 142 | } 143 | 144 | do { 145 | let newContactRequest = CNSaveRequest() 146 | newContactRequest.add(newContact, toContainerWithIdentifier: nil) 147 | try CNContactStore().execute(newContactRequest) 148 | self.presentingViewController?.dismiss(animated: true, completion: nil) 149 | } catch { 150 | self.showAlertMessage("I was unable to create the new contact. An error occurred.") 151 | } 152 | } 153 | 154 | // MARK: - Button actions 155 | @IBAction func createContact(_ sender: AnyObject) { 156 | // check if we can create a contact. 157 | if let firstName = firstNameTextfield.text , firstName.characters.count > 0, 158 | let lastName = lastNameTextfield.text , lastName.characters.count > 0 { 159 | let email = emailAddressTextfield.text 160 | let phone = phoneNumberTextfield.text 161 | 162 | if type == .addressBookContact { 163 | createAddressBookContactWithFirstName(firstName, lastName: lastName, email: email, phone: phone, image: contactImage) 164 | } else if type == .cnContact { 165 | if #available(iOS 9, *) { 166 | createCNContactWithFirstName(firstName, lastName: lastName, email: email, phone: phone, image: contactImage) 167 | } else { 168 | self.showAlertMessage("Sorry, you can only use the Contacts framework from iOS 9.") 169 | } 170 | 171 | } 172 | } else { 173 | self.showAlertMessage("Please, insert at least a first and last name for the contact.") 174 | } 175 | } 176 | 177 | @IBAction func changeContactImage(_ sender: AnyObject) { 178 | let picker = UIImagePickerController() 179 | 180 | picker.delegate = self 181 | picker.sourceType = .photoLibrary 182 | picker.allowsEditing = true 183 | 184 | present(picker, animated: true, completion: nil) 185 | } 186 | 187 | func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) { 188 | self.contactImage = info[UIImagePickerControllerEditedImage] as? UIImage 189 | self.contactImageView.image = self.contactImage 190 | self.dismiss(animated: true, completion: nil) 191 | } 192 | 193 | 194 | @IBAction func goBack(_ sender: AnyObject) { 195 | self.presentingViewController?.dismiss(animated: true, completion: nil) 196 | } 197 | 198 | } 199 | -------------------------------------------------------------------------------- /AddressBookContacts.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 6624EE1F1CC8161B005B6E6B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6624EE1E1CC8161B005B6E6B /* AppDelegate.swift */; }; 11 | 6624EE211CC8161B005B6E6B /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6624EE201CC8161B005B6E6B /* ViewController.swift */; }; 12 | 6624EE241CC8161B005B6E6B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6624EE221CC8161B005B6E6B /* Main.storyboard */; }; 13 | 6624EE261CC8161B005B6E6B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6624EE251CC8161B005B6E6B /* Assets.xcassets */; }; 14 | 6624EE291CC8161B005B6E6B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6624EE271CC8161B005B6E6B /* LaunchScreen.storyboard */; }; 15 | 6624EE341CC81BBF005B6E6B /* AddressBookViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6624EE331CC81BBF005B6E6B /* AddressBookViewController.swift */; }; 16 | 6624EE361CC81BC8005B6E6B /* ContactsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6624EE351CC81BC8005B6E6B /* ContactsViewController.swift */; }; 17 | 6624EE391CC81BF0005B6E6B /* ContactTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6624EE371CC81BF0005B6E6B /* ContactTableViewCell.swift */; }; 18 | 6624EE3A1CC81BF0005B6E6B /* ContactTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6624EE381CC81BF0005B6E6B /* ContactTableViewCell.xib */; }; 19 | 6624EE3D1CC82408005B6E6B /* UIViewController+Alerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6624EE3C1CC82408005B6E6B /* UIViewController+Alerts.swift */; }; 20 | 6624EE401CC824C7005B6E6B /* ContactEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6624EE3F1CC824C7005B6E6B /* ContactEntry.swift */; }; 21 | 6624EE421CC8BA53005B6E6B /* String+Email.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6624EE411CC8BA53005B6E6B /* String+Email.swift */; }; 22 | 66406E461CC96C50003F528E /* CreateContactViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66406E451CC96C50003F528E /* CreateContactViewController.swift */; }; 23 | /* End PBXBuildFile section */ 24 | 25 | /* Begin PBXFileReference section */ 26 | 6624EE1B1CC8161B005B6E6B /* AddressBookContacts.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AddressBookContacts.app; sourceTree = BUILT_PRODUCTS_DIR; }; 27 | 6624EE1E1CC8161B005B6E6B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 28 | 6624EE201CC8161B005B6E6B /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 29 | 6624EE231CC8161B005B6E6B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 30 | 6624EE251CC8161B005B6E6B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 31 | 6624EE281CC8161B005B6E6B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 32 | 6624EE2A1CC8161B005B6E6B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 33 | 6624EE331CC81BBF005B6E6B /* AddressBookViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddressBookViewController.swift; sourceTree = ""; }; 34 | 6624EE351CC81BC8005B6E6B /* ContactsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactsViewController.swift; sourceTree = ""; }; 35 | 6624EE371CC81BF0005B6E6B /* ContactTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactTableViewCell.swift; sourceTree = ""; }; 36 | 6624EE381CC81BF0005B6E6B /* ContactTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ContactTableViewCell.xib; sourceTree = ""; }; 37 | 6624EE3C1CC82408005B6E6B /* UIViewController+Alerts.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIViewController+Alerts.swift"; sourceTree = ""; }; 38 | 6624EE3F1CC824C7005B6E6B /* ContactEntry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactEntry.swift; sourceTree = ""; }; 39 | 6624EE411CC8BA53005B6E6B /* String+Email.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Email.swift"; sourceTree = ""; }; 40 | 66406E451CC96C50003F528E /* CreateContactViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateContactViewController.swift; sourceTree = ""; }; 41 | /* End PBXFileReference section */ 42 | 43 | /* Begin PBXFrameworksBuildPhase section */ 44 | 6624EE181CC8161B005B6E6B /* Frameworks */ = { 45 | isa = PBXFrameworksBuildPhase; 46 | buildActionMask = 2147483647; 47 | files = ( 48 | ); 49 | runOnlyForDeploymentPostprocessing = 0; 50 | }; 51 | /* End PBXFrameworksBuildPhase section */ 52 | 53 | /* Begin PBXGroup section */ 54 | 6624EE121CC8161B005B6E6B = { 55 | isa = PBXGroup; 56 | children = ( 57 | 6624EE1D1CC8161B005B6E6B /* AddressBookContacts */, 58 | 6624EE1C1CC8161B005B6E6B /* Products */, 59 | ); 60 | sourceTree = ""; 61 | }; 62 | 6624EE1C1CC8161B005B6E6B /* Products */ = { 63 | isa = PBXGroup; 64 | children = ( 65 | 6624EE1B1CC8161B005B6E6B /* AddressBookContacts.app */, 66 | ); 67 | name = Products; 68 | sourceTree = ""; 69 | }; 70 | 6624EE1D1CC8161B005B6E6B /* AddressBookContacts */ = { 71 | isa = PBXGroup; 72 | children = ( 73 | 6624EE3E1CC824B5005B6E6B /* Model */, 74 | 6624EE3B1CC823F7005B6E6B /* Categories */, 75 | 6624EE321CC81BA9005B6E6B /* View Controllers */, 76 | 6624EE311CC81B26005B6E6B /* Views and storyboards */, 77 | 6624EE301CC81B16005B6E6B /* App and resources */, 78 | ); 79 | path = AddressBookContacts; 80 | sourceTree = ""; 81 | }; 82 | 6624EE301CC81B16005B6E6B /* App and resources */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | 6624EE1E1CC8161B005B6E6B /* AppDelegate.swift */, 86 | 6624EE251CC8161B005B6E6B /* Assets.xcassets */, 87 | 6624EE2A1CC8161B005B6E6B /* Info.plist */, 88 | ); 89 | name = "App and resources"; 90 | sourceTree = ""; 91 | }; 92 | 6624EE311CC81B26005B6E6B /* Views and storyboards */ = { 93 | isa = PBXGroup; 94 | children = ( 95 | 6624EE221CC8161B005B6E6B /* Main.storyboard */, 96 | 6624EE271CC8161B005B6E6B /* LaunchScreen.storyboard */, 97 | 6624EE371CC81BF0005B6E6B /* ContactTableViewCell.swift */, 98 | 6624EE381CC81BF0005B6E6B /* ContactTableViewCell.xib */, 99 | ); 100 | name = "Views and storyboards"; 101 | sourceTree = ""; 102 | }; 103 | 6624EE321CC81BA9005B6E6B /* View Controllers */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | 6624EE201CC8161B005B6E6B /* ViewController.swift */, 107 | 6624EE331CC81BBF005B6E6B /* AddressBookViewController.swift */, 108 | 6624EE351CC81BC8005B6E6B /* ContactsViewController.swift */, 109 | 66406E451CC96C50003F528E /* CreateContactViewController.swift */, 110 | ); 111 | name = "View Controllers"; 112 | sourceTree = ""; 113 | }; 114 | 6624EE3B1CC823F7005B6E6B /* Categories */ = { 115 | isa = PBXGroup; 116 | children = ( 117 | 6624EE3C1CC82408005B6E6B /* UIViewController+Alerts.swift */, 118 | ); 119 | name = Categories; 120 | sourceTree = ""; 121 | }; 122 | 6624EE3E1CC824B5005B6E6B /* Model */ = { 123 | isa = PBXGroup; 124 | children = ( 125 | 6624EE3F1CC824C7005B6E6B /* ContactEntry.swift */, 126 | 6624EE411CC8BA53005B6E6B /* String+Email.swift */, 127 | ); 128 | name = Model; 129 | sourceTree = ""; 130 | }; 131 | /* End PBXGroup section */ 132 | 133 | /* Begin PBXNativeTarget section */ 134 | 6624EE1A1CC8161B005B6E6B /* AddressBookContacts */ = { 135 | isa = PBXNativeTarget; 136 | buildConfigurationList = 6624EE2D1CC8161B005B6E6B /* Build configuration list for PBXNativeTarget "AddressBookContacts" */; 137 | buildPhases = ( 138 | 6624EE171CC8161B005B6E6B /* Sources */, 139 | 6624EE181CC8161B005B6E6B /* Frameworks */, 140 | 6624EE191CC8161B005B6E6B /* Resources */, 141 | ); 142 | buildRules = ( 143 | ); 144 | dependencies = ( 145 | ); 146 | name = AddressBookContacts; 147 | productName = AddressBookContacts; 148 | productReference = 6624EE1B1CC8161B005B6E6B /* AddressBookContacts.app */; 149 | productType = "com.apple.product-type.application"; 150 | }; 151 | /* End PBXNativeTarget section */ 152 | 153 | /* Begin PBXProject section */ 154 | 6624EE131CC8161B005B6E6B /* Project object */ = { 155 | isa = PBXProject; 156 | attributes = { 157 | LastSwiftUpdateCheck = 0730; 158 | LastUpgradeCheck = 0800; 159 | ORGANIZATIONNAME = "Ignacio Nieto Carvajal"; 160 | TargetAttributes = { 161 | 6624EE1A1CC8161B005B6E6B = { 162 | CreatedOnToolsVersion = 7.3; 163 | LastSwiftMigration = 0800; 164 | }; 165 | }; 166 | }; 167 | buildConfigurationList = 6624EE161CC8161B005B6E6B /* Build configuration list for PBXProject "AddressBookContacts" */; 168 | compatibilityVersion = "Xcode 3.2"; 169 | developmentRegion = English; 170 | hasScannedForEncodings = 0; 171 | knownRegions = ( 172 | en, 173 | Base, 174 | ); 175 | mainGroup = 6624EE121CC8161B005B6E6B; 176 | productRefGroup = 6624EE1C1CC8161B005B6E6B /* Products */; 177 | projectDirPath = ""; 178 | projectRoot = ""; 179 | targets = ( 180 | 6624EE1A1CC8161B005B6E6B /* AddressBookContacts */, 181 | ); 182 | }; 183 | /* End PBXProject section */ 184 | 185 | /* Begin PBXResourcesBuildPhase section */ 186 | 6624EE191CC8161B005B6E6B /* Resources */ = { 187 | isa = PBXResourcesBuildPhase; 188 | buildActionMask = 2147483647; 189 | files = ( 190 | 6624EE3A1CC81BF0005B6E6B /* ContactTableViewCell.xib in Resources */, 191 | 6624EE291CC8161B005B6E6B /* LaunchScreen.storyboard in Resources */, 192 | 6624EE261CC8161B005B6E6B /* Assets.xcassets in Resources */, 193 | 6624EE241CC8161B005B6E6B /* Main.storyboard in Resources */, 194 | ); 195 | runOnlyForDeploymentPostprocessing = 0; 196 | }; 197 | /* End PBXResourcesBuildPhase section */ 198 | 199 | /* Begin PBXSourcesBuildPhase section */ 200 | 6624EE171CC8161B005B6E6B /* Sources */ = { 201 | isa = PBXSourcesBuildPhase; 202 | buildActionMask = 2147483647; 203 | files = ( 204 | 6624EE341CC81BBF005B6E6B /* AddressBookViewController.swift in Sources */, 205 | 6624EE3D1CC82408005B6E6B /* UIViewController+Alerts.swift in Sources */, 206 | 66406E461CC96C50003F528E /* CreateContactViewController.swift in Sources */, 207 | 6624EE391CC81BF0005B6E6B /* ContactTableViewCell.swift in Sources */, 208 | 6624EE361CC81BC8005B6E6B /* ContactsViewController.swift in Sources */, 209 | 6624EE211CC8161B005B6E6B /* ViewController.swift in Sources */, 210 | 6624EE401CC824C7005B6E6B /* ContactEntry.swift in Sources */, 211 | 6624EE1F1CC8161B005B6E6B /* AppDelegate.swift in Sources */, 212 | 6624EE421CC8BA53005B6E6B /* String+Email.swift in Sources */, 213 | ); 214 | runOnlyForDeploymentPostprocessing = 0; 215 | }; 216 | /* End PBXSourcesBuildPhase section */ 217 | 218 | /* Begin PBXVariantGroup section */ 219 | 6624EE221CC8161B005B6E6B /* Main.storyboard */ = { 220 | isa = PBXVariantGroup; 221 | children = ( 222 | 6624EE231CC8161B005B6E6B /* Base */, 223 | ); 224 | name = Main.storyboard; 225 | sourceTree = ""; 226 | }; 227 | 6624EE271CC8161B005B6E6B /* LaunchScreen.storyboard */ = { 228 | isa = PBXVariantGroup; 229 | children = ( 230 | 6624EE281CC8161B005B6E6B /* Base */, 231 | ); 232 | name = LaunchScreen.storyboard; 233 | sourceTree = ""; 234 | }; 235 | /* End PBXVariantGroup section */ 236 | 237 | /* Begin XCBuildConfiguration section */ 238 | 6624EE2B1CC8161B005B6E6B /* Debug */ = { 239 | isa = XCBuildConfiguration; 240 | buildSettings = { 241 | ALWAYS_SEARCH_USER_PATHS = NO; 242 | CLANG_ANALYZER_NONNULL = YES; 243 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 244 | CLANG_CXX_LIBRARY = "libc++"; 245 | CLANG_ENABLE_MODULES = YES; 246 | CLANG_ENABLE_OBJC_ARC = YES; 247 | CLANG_WARN_BOOL_CONVERSION = YES; 248 | CLANG_WARN_CONSTANT_CONVERSION = YES; 249 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 250 | CLANG_WARN_EMPTY_BODY = YES; 251 | CLANG_WARN_ENUM_CONVERSION = YES; 252 | CLANG_WARN_INFINITE_RECURSION = YES; 253 | CLANG_WARN_INT_CONVERSION = YES; 254 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 255 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 256 | CLANG_WARN_UNREACHABLE_CODE = YES; 257 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 258 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 259 | COPY_PHASE_STRIP = NO; 260 | DEBUG_INFORMATION_FORMAT = dwarf; 261 | ENABLE_STRICT_OBJC_MSGSEND = YES; 262 | ENABLE_TESTABILITY = YES; 263 | GCC_C_LANGUAGE_STANDARD = gnu99; 264 | GCC_DYNAMIC_NO_PIC = NO; 265 | GCC_NO_COMMON_BLOCKS = YES; 266 | GCC_OPTIMIZATION_LEVEL = 0; 267 | GCC_PREPROCESSOR_DEFINITIONS = ( 268 | "DEBUG=1", 269 | "$(inherited)", 270 | ); 271 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 272 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 273 | GCC_WARN_UNDECLARED_SELECTOR = YES; 274 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 275 | GCC_WARN_UNUSED_FUNCTION = YES; 276 | GCC_WARN_UNUSED_VARIABLE = YES; 277 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 278 | MTL_ENABLE_DEBUG_INFO = YES; 279 | ONLY_ACTIVE_ARCH = YES; 280 | SDKROOT = iphoneos; 281 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 282 | }; 283 | name = Debug; 284 | }; 285 | 6624EE2C1CC8161B005B6E6B /* Release */ = { 286 | isa = XCBuildConfiguration; 287 | buildSettings = { 288 | ALWAYS_SEARCH_USER_PATHS = NO; 289 | CLANG_ANALYZER_NONNULL = YES; 290 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 291 | CLANG_CXX_LIBRARY = "libc++"; 292 | CLANG_ENABLE_MODULES = YES; 293 | CLANG_ENABLE_OBJC_ARC = YES; 294 | CLANG_WARN_BOOL_CONVERSION = YES; 295 | CLANG_WARN_CONSTANT_CONVERSION = YES; 296 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 297 | CLANG_WARN_EMPTY_BODY = YES; 298 | CLANG_WARN_ENUM_CONVERSION = YES; 299 | CLANG_WARN_INFINITE_RECURSION = YES; 300 | CLANG_WARN_INT_CONVERSION = YES; 301 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 302 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 303 | CLANG_WARN_UNREACHABLE_CODE = YES; 304 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 305 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 306 | COPY_PHASE_STRIP = NO; 307 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 308 | ENABLE_NS_ASSERTIONS = NO; 309 | ENABLE_STRICT_OBJC_MSGSEND = YES; 310 | GCC_C_LANGUAGE_STANDARD = gnu99; 311 | GCC_NO_COMMON_BLOCKS = YES; 312 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 313 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 314 | GCC_WARN_UNDECLARED_SELECTOR = YES; 315 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 316 | GCC_WARN_UNUSED_FUNCTION = YES; 317 | GCC_WARN_UNUSED_VARIABLE = YES; 318 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 319 | MTL_ENABLE_DEBUG_INFO = NO; 320 | SDKROOT = iphoneos; 321 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 322 | VALIDATE_PRODUCT = YES; 323 | }; 324 | name = Release; 325 | }; 326 | 6624EE2E1CC8161B005B6E6B /* Debug */ = { 327 | isa = XCBuildConfiguration; 328 | buildSettings = { 329 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 330 | INFOPLIST_FILE = AddressBookContacts/Info.plist; 331 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 332 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 333 | PRODUCT_BUNDLE_IDENTIFIER = "com.Ignacio-Nieto-Carvajal.AddressBookContacts"; 334 | PRODUCT_NAME = "$(TARGET_NAME)"; 335 | SWIFT_VERSION = 3.0; 336 | }; 337 | name = Debug; 338 | }; 339 | 6624EE2F1CC8161B005B6E6B /* Release */ = { 340 | isa = XCBuildConfiguration; 341 | buildSettings = { 342 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 343 | INFOPLIST_FILE = AddressBookContacts/Info.plist; 344 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 345 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 346 | PRODUCT_BUNDLE_IDENTIFIER = "com.Ignacio-Nieto-Carvajal.AddressBookContacts"; 347 | PRODUCT_NAME = "$(TARGET_NAME)"; 348 | SWIFT_VERSION = 3.0; 349 | }; 350 | name = Release; 351 | }; 352 | /* End XCBuildConfiguration section */ 353 | 354 | /* Begin XCConfigurationList section */ 355 | 6624EE161CC8161B005B6E6B /* Build configuration list for PBXProject "AddressBookContacts" */ = { 356 | isa = XCConfigurationList; 357 | buildConfigurations = ( 358 | 6624EE2B1CC8161B005B6E6B /* Debug */, 359 | 6624EE2C1CC8161B005B6E6B /* Release */, 360 | ); 361 | defaultConfigurationIsVisible = 0; 362 | defaultConfigurationName = Release; 363 | }; 364 | 6624EE2D1CC8161B005B6E6B /* Build configuration list for PBXNativeTarget "AddressBookContacts" */ = { 365 | isa = XCConfigurationList; 366 | buildConfigurations = ( 367 | 6624EE2E1CC8161B005B6E6B /* Debug */, 368 | 6624EE2F1CC8161B005B6E6B /* Release */, 369 | ); 370 | defaultConfigurationIsVisible = 0; 371 | defaultConfigurationName = Release; 372 | }; 373 | /* End XCConfigurationList section */ 374 | }; 375 | rootObject = 6624EE131CC8161B005B6E6B /* Project object */; 376 | } 377 | -------------------------------------------------------------------------------- /AddressBookContacts/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 40 | 54 | 62 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 128 | 141 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 203 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 249 | 255 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 342 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 362 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | --------------------------------------------------------------------------------