├── 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 | 
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 | 
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 |
--------------------------------------------------------------------------------