├── MVVM Contacts Starter ├── MVVM Contacts Starter │ ├── ContactViewModel.swift │ ├── ContactViewModelController.swift │ ├── Contact.swift │ ├── ContactsTableViewCell.swift │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── profile_placeholder.imageset │ │ │ ├── profile_picture_badge@2x.png │ │ │ ├── profile_picture_badge@3x.png │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Contact+CoreDataProperties.swift │ ├── AddContactViewController.swift │ ├── ContactsViewController.swift │ ├── Array+Insertion.swift │ ├── MVVM_Contacts.xcdatamodeld │ │ └── MVVM_Contacts.xcdatamodel │ │ │ └── contents │ ├── CoreDataStore.swift │ ├── Info.plist │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── ContactLocalDataManager.swift │ └── AppDelegate.swift └── MVVM Contacts Starter.xcodeproj │ ├── project.xcworkspace │ └── contents.xcworkspacedata │ └── project.pbxproj ├── img ├── contacts.png ├── add-contact.png ├── mvc-pattern.png ├── mvvm-pattern.png ├── auth0-login-screen.png └── viper-architecture.png ├── README.md ├── MVVM Contacts Final ├── MVVM Contacts Final │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── profile_placeholder.imageset │ │ │ ├── profile_picture_badge@2x.png │ │ │ ├── profile_picture_badge@3x.png │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Contact+CoreDataProperties.swift │ ├── ContactsTableViewCell.swift │ ├── ContactViewModel.swift │ ├── Contact.swift │ ├── Array+Insertion.swift │ ├── MVVM_Contacts.xcdatamodeld │ │ └── MVVM_Contacts.xcdatamodel │ │ │ └── contents │ ├── CoreDataStore.swift │ ├── Info.plist │ ├── ContactViewModelController.swift │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── AddContactViewController.swift │ ├── ContactLocalDataManager.swift │ ├── ContactsViewController.swift │ └── AppDelegate.swift └── MVVM Contacts Final.xcodeproj │ ├── project.xcworkspace │ └── contents.xcworkspacedata │ └── project.pbxproj ├── VIPER Contacts Final ├── VIPER Contacts Final │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── profile_placeholder.imageset │ │ │ ├── profile_picture_badge@2x.png │ │ │ ├── profile_picture_badge@3x.png │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Contact+CoreDataProperties.swift │ ├── Array+Insertion.swift │ ├── AddContact │ │ ├── Interactor │ │ │ └── AddContactInteractor.swift │ │ ├── Presenter │ │ │ └── AddContactPresenter.swift │ │ ├── DataManager │ │ │ └── Local │ │ │ │ └── AddContactLocalDataManager.swift │ │ ├── View │ │ │ └── AddContactView.swift │ │ ├── WireFrame │ │ │ └── AddContactWireFrame.swift │ │ └── Protocols │ │ │ └── AddContactProtocols.swift │ ├── ContactList │ │ ├── Interactor │ │ │ └── ContactListInteractor.swift │ │ ├── DataManager │ │ │ └── Local │ │ │ │ └── ContactListLocalDataManager.swift │ │ ├── Presenter │ │ │ └── ContactListPresenter.swift │ │ ├── WireFrame │ │ │ └── ContactListWireFrame.swift │ │ ├── View │ │ │ └── ContactListView.swift │ │ └── Protocols │ │ │ └── ContactListProtocols.swift │ ├── VIPER_Contacts_Starter.xcdatamodeld │ │ └── VIPER_Contacts_Starter.xcdatamodel │ │ │ └── contents │ ├── Contact.swift │ ├── CoreDataStore.swift │ ├── Info.plist │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ └── AppDelegate.swift └── VIPER Contacts Final.xcodeproj │ └── project.xcworkspace │ └── contents.xcworkspacedata ├── VIPER Contacts Starter ├── VIPER Contacts Starter │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── profile_placeholder.imageset │ │ │ ├── profile_picture_badge@2x.png │ │ │ ├── profile_picture_badge@3x.png │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Contact.swift │ ├── Contact+CoreDataProperties.swift │ ├── ContactList │ │ ├── Interactor │ │ │ └── ContactListInteractor.swift │ │ ├── WireFrame │ │ │ └── ContactListWireFrame.swift │ │ ├── Presenter │ │ │ └── ContactListPresenter.swift │ │ ├── View │ │ │ └── ContactListView.swift │ │ ├── DataManager │ │ │ └── Local │ │ │ │ └── ContactListLocalDataManager.swift │ │ └── Protocols │ │ │ └── ContactListProtocols.swift │ ├── Array+Insertion.swift │ ├── AddContact │ │ ├── Interactor │ │ │ └── AddContactInteractor.swift │ │ ├── Presenter │ │ │ └── AddContactPresenter.swift │ │ ├── DataManager │ │ │ └── Local │ │ │ │ └── AddContactLocalDataManager.swift │ │ ├── View │ │ │ └── AddContactView.swift │ │ ├── WireFrame │ │ │ └── AddContactWireFrame.swift │ │ └── Protocols │ │ │ └── AddContactProtocols.swift │ ├── VIPER_Contacts_Starter.xcdatamodeld │ │ └── VIPER_Contacts_Starter.xcdatamodel │ │ │ └── contents │ ├── CoreDataStore.swift │ ├── Info.plist │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ └── AppDelegate.swift └── VIPER Contacts Starter.xcodeproj │ └── project.xcworkspace │ └── contents.xcworkspacedata ├── LICENSE └── .gitignore /MVVM Contacts Starter/MVVM Contacts Starter/ContactViewModel.swift: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MVVM Contacts Starter/MVVM Contacts Starter/ContactViewModelController.swift: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /img/contacts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0-tutorials/mvvm_viper/HEAD/img/contacts.png -------------------------------------------------------------------------------- /img/add-contact.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0-tutorials/mvvm_viper/HEAD/img/add-contact.png -------------------------------------------------------------------------------- /img/mvc-pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0-tutorials/mvvm_viper/HEAD/img/mvc-pattern.png -------------------------------------------------------------------------------- /img/mvvm-pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0-tutorials/mvvm_viper/HEAD/img/mvvm-pattern.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mvvm_viper 2 | Blog post written as guest author for the auth0 blog. Contains example code 3 | -------------------------------------------------------------------------------- /img/auth0-login-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0-tutorials/mvvm_viper/HEAD/img/auth0-login-screen.png -------------------------------------------------------------------------------- /img/viper-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0-tutorials/mvvm_viper/HEAD/img/viper-architecture.png -------------------------------------------------------------------------------- /MVVM Contacts Starter/MVVM Contacts Starter/Contact.swift: -------------------------------------------------------------------------------- 1 | import CoreData 2 | 3 | open class Contact: NSManagedObject { 4 | } 5 | -------------------------------------------------------------------------------- /MVVM Contacts Final/MVVM Contacts Final/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /MVVM Contacts Starter/MVVM Contacts Starter/ContactsTableViewCell.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class ContactsTableViewCell: UITableViewCell { 4 | } 5 | -------------------------------------------------------------------------------- /VIPER Contacts Final/VIPER Contacts Final/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /MVVM Contacts Starter/MVVM Contacts Starter/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /VIPER Contacts Starter/VIPER Contacts Starter/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /VIPER Contacts Starter/VIPER Contacts Starter/Contact.swift: -------------------------------------------------------------------------------- 1 | import CoreData 2 | 3 | open class Contact: NSManagedObject {} 4 | 5 | public struct ContactViewModel {} 6 | -------------------------------------------------------------------------------- /MVVM Contacts Final/MVVM Contacts Final/Contact+CoreDataProperties.swift: -------------------------------------------------------------------------------- 1 | import CoreData 2 | 3 | extension Contact { 4 | 5 | @NSManaged var firstName: String? 6 | @NSManaged var lastName: String? 7 | 8 | } 9 | -------------------------------------------------------------------------------- /VIPER Contacts Final/VIPER Contacts Final/Contact+CoreDataProperties.swift: -------------------------------------------------------------------------------- 1 | import CoreData 2 | 3 | extension Contact { 4 | 5 | @NSManaged var firstName: String? 6 | @NSManaged var lastName: String? 7 | 8 | } 9 | -------------------------------------------------------------------------------- /MVVM Contacts Starter/MVVM Contacts Starter/Contact+CoreDataProperties.swift: -------------------------------------------------------------------------------- 1 | import CoreData 2 | 3 | extension Contact { 4 | 5 | @NSManaged var firstName: String? 6 | @NSManaged var lastName: String? 7 | 8 | } 9 | -------------------------------------------------------------------------------- /VIPER Contacts Starter/VIPER Contacts Starter/Contact+CoreDataProperties.swift: -------------------------------------------------------------------------------- 1 | import CoreData 2 | 3 | extension Contact { 4 | 5 | @NSManaged var firstName: String? 6 | @NSManaged var lastName: String? 7 | 8 | } 9 | -------------------------------------------------------------------------------- /MVVM Contacts Final/MVVM Contacts Final/Assets.xcassets/profile_placeholder.imageset/profile_picture_badge@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0-tutorials/mvvm_viper/HEAD/MVVM Contacts Final/MVVM Contacts Final/Assets.xcassets/profile_placeholder.imageset/profile_picture_badge@2x.png -------------------------------------------------------------------------------- /MVVM Contacts Final/MVVM Contacts Final/Assets.xcassets/profile_placeholder.imageset/profile_picture_badge@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0-tutorials/mvvm_viper/HEAD/MVVM Contacts Final/MVVM Contacts Final/Assets.xcassets/profile_placeholder.imageset/profile_picture_badge@3x.png -------------------------------------------------------------------------------- /MVVM Contacts Starter/MVVM Contacts Starter.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /VIPER Contacts Final/VIPER Contacts Final/Assets.xcassets/profile_placeholder.imageset/profile_picture_badge@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0-tutorials/mvvm_viper/HEAD/VIPER Contacts Final/VIPER Contacts Final/Assets.xcassets/profile_placeholder.imageset/profile_picture_badge@2x.png -------------------------------------------------------------------------------- /VIPER Contacts Final/VIPER Contacts Final/Assets.xcassets/profile_placeholder.imageset/profile_picture_badge@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0-tutorials/mvvm_viper/HEAD/VIPER Contacts Final/VIPER Contacts Final/Assets.xcassets/profile_placeholder.imageset/profile_picture_badge@3x.png -------------------------------------------------------------------------------- /MVVM Contacts Starter/MVVM Contacts Starter/Assets.xcassets/profile_placeholder.imageset/profile_picture_badge@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0-tutorials/mvvm_viper/HEAD/MVVM Contacts Starter/MVVM Contacts Starter/Assets.xcassets/profile_placeholder.imageset/profile_picture_badge@2x.png -------------------------------------------------------------------------------- /MVVM Contacts Starter/MVVM Contacts Starter/Assets.xcassets/profile_placeholder.imageset/profile_picture_badge@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0-tutorials/mvvm_viper/HEAD/MVVM Contacts Starter/MVVM Contacts Starter/Assets.xcassets/profile_placeholder.imageset/profile_picture_badge@3x.png -------------------------------------------------------------------------------- /VIPER Contacts Starter/VIPER Contacts Starter/Assets.xcassets/profile_placeholder.imageset/profile_picture_badge@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0-tutorials/mvvm_viper/HEAD/VIPER Contacts Starter/VIPER Contacts Starter/Assets.xcassets/profile_placeholder.imageset/profile_picture_badge@2x.png -------------------------------------------------------------------------------- /VIPER Contacts Starter/VIPER Contacts Starter/Assets.xcassets/profile_placeholder.imageset/profile_picture_badge@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auth0-tutorials/mvvm_viper/HEAD/VIPER Contacts Starter/VIPER Contacts Starter/Assets.xcassets/profile_placeholder.imageset/profile_picture_badge@3x.png -------------------------------------------------------------------------------- /MVVM Contacts Final/MVVM Contacts Final.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /VIPER Contacts Final/VIPER Contacts Final.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /VIPER Contacts Starter/VIPER Contacts Starter/ContactList/Interactor/ContactListInteractor.swift: -------------------------------------------------------------------------------- 1 | 2 | class ContactListInteractor: ContactListInteractorInputProtocol { 3 | weak var presenter: ContactListInteractorOutputProtocol? 4 | var localDatamanager: ContactListLocalDataManagerInputProtocol? 5 | 6 | func retrieveContacts() {} 7 | 8 | } 9 | -------------------------------------------------------------------------------- /VIPER Contacts Starter/VIPER Contacts Starter.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MVVM Contacts Final/MVVM Contacts Final/ContactsTableViewCell.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class ContactsTableViewCell: UITableViewCell { 4 | var cellModel: ContactViewModel? { 5 | didSet { 6 | bindViewModel() 7 | } 8 | } 9 | 10 | func bindViewModel() { 11 | textLabel?.text = cellModel?.fullName 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /VIPER Contacts Starter/VIPER Contacts Starter/ContactList/WireFrame/ContactListWireFrame.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class ContactListWireFrame: ContactListWireFrameProtocol { 4 | 5 | class func createContactListModule() -> UIViewController { 6 | return UIViewController() 7 | } 8 | 9 | func presentAddContactScreen(from view: ContactListViewProtocol) {} 10 | 11 | } 12 | -------------------------------------------------------------------------------- /MVVM Contacts Final/MVVM Contacts Final/ContactViewModel.swift: -------------------------------------------------------------------------------- 1 | 2 | public struct ContactViewModel { 3 | var fullName: String 4 | } 5 | 6 | public func <(lhs: ContactViewModel, rhs: ContactViewModel) -> Bool { 7 | return lhs.fullName.lowercased() < rhs.fullName.lowercased() 8 | } 9 | 10 | public func >(lhs: ContactViewModel, rhs: ContactViewModel) -> Bool { 11 | return lhs.fullName.lowercased() > rhs.fullName.lowercased() 12 | } 13 | -------------------------------------------------------------------------------- /MVVM Contacts Final/MVVM Contacts Final/Contact.swift: -------------------------------------------------------------------------------- 1 | import CoreData 2 | 3 | open class Contact: NSManagedObject { 4 | 5 | var fullName: String { 6 | get { 7 | var name = "" 8 | if let firstName = firstName { 9 | name += firstName 10 | } 11 | if let lastName = lastName { 12 | name += " \(lastName)" 13 | } 14 | return name 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /MVVM Contacts Starter/MVVM Contacts Starter/AddContactViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class AddContactViewController: UIViewController { 4 | 5 | @IBOutlet var firstNameTextField: UITextField! 6 | @IBOutlet var lastNameTextField: UITextField! 7 | 8 | @IBAction func didClickOnDoneButton(_ sender: UIBarButtonItem) { 9 | } 10 | 11 | @IBAction func didClickOnCancelButton(_ sender: UIBarButtonItem) { 12 | dismiss(animated: true, completion: nil) 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /MVVM Contacts Final/MVVM Contacts Final/Assets.xcassets/profile_placeholder.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "profile_picture_badge@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "profile_picture_badge@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VIPER Contacts Final/VIPER Contacts Final/Assets.xcassets/profile_placeholder.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "profile_picture_badge@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "profile_picture_badge@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /MVVM Contacts Starter/MVVM Contacts Starter/Assets.xcassets/profile_placeholder.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "profile_picture_badge@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "profile_picture_badge@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /VIPER Contacts Starter/VIPER Contacts Starter/Assets.xcassets/profile_placeholder.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "profile_picture_badge@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "filename" : "profile_picture_badge@3x.png", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /MVVM Contacts Starter/MVVM Contacts Starter/ContactsViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class ContactsViewController: UIViewController { 4 | 5 | @IBOutlet var tableView: UITableView! 6 | } 7 | 8 | extension ContactsViewController: UITableViewDataSource { 9 | 10 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 11 | return UITableViewCell() 12 | } 13 | 14 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 15 | return 0 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /MVVM Contacts Final/MVVM Contacts Final/Array+Insertion.swift: -------------------------------------------------------------------------------- 1 | 2 | extension Array { 3 | func insertionIndex(of elem: Element, isOrderedBefore: (Element, Element) -> Bool) -> Int { 4 | var lo = 0 5 | var hi = self.count - 1 6 | while lo <= hi { 7 | let mid = (lo + hi)/2 8 | if isOrderedBefore(self[mid], elem) { 9 | lo = mid + 1 10 | } else if isOrderedBefore(elem, self[mid]) { 11 | hi = mid - 1 12 | } else { 13 | return mid 14 | } 15 | } 16 | return lo 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /VIPER Contacts Final/VIPER Contacts Final/Array+Insertion.swift: -------------------------------------------------------------------------------- 1 | 2 | extension Array { 3 | func insertionIndex(of elem: Element, isOrderedBefore: (Element, Element) -> Bool) -> Int { 4 | var lo = 0 5 | var hi = self.count - 1 6 | while lo <= hi { 7 | let mid = (lo + hi)/2 8 | if isOrderedBefore(self[mid], elem) { 9 | lo = mid + 1 10 | } else if isOrderedBefore(elem, self[mid]) { 11 | hi = mid - 1 12 | } else { 13 | return mid 14 | } 15 | } 16 | return lo 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /MVVM Contacts Starter/MVVM Contacts Starter/Array+Insertion.swift: -------------------------------------------------------------------------------- 1 | 2 | extension Array { 3 | func insertionIndex(of elem: Element, isOrderedBefore: (Element, Element) -> Bool) -> Int { 4 | var lo = 0 5 | var hi = self.count - 1 6 | while lo <= hi { 7 | let mid = (lo + hi)/2 8 | if isOrderedBefore(self[mid], elem) { 9 | lo = mid + 1 10 | } else if isOrderedBefore(elem, self[mid]) { 11 | hi = mid - 1 12 | } else { 13 | return mid 14 | } 15 | } 16 | return lo 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /VIPER Contacts Starter/VIPER Contacts Starter/Array+Insertion.swift: -------------------------------------------------------------------------------- 1 | 2 | extension Array { 3 | func insertionIndex(of elem: Element, isOrderedBefore: (Element, Element) -> Bool) -> Int { 4 | var lo = 0 5 | var hi = self.count - 1 6 | while lo <= hi { 7 | let mid = (lo + hi)/2 8 | if isOrderedBefore(self[mid], elem) { 9 | lo = mid + 1 10 | } else if isOrderedBefore(elem, self[mid]) { 11 | hi = mid - 1 12 | } else { 13 | return mid 14 | } 15 | } 16 | return lo 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /VIPER Contacts Final/VIPER Contacts Final/AddContact/Interactor/AddContactInteractor.swift: -------------------------------------------------------------------------------- 1 | 2 | class AddContactInteractor: AddContactInteractorInputProtocol { 3 | weak var presenter: AddContactInteractorOutputProtocol? 4 | var APIDataManager: AddContactAPIDataManagerInputProtocol? 5 | var localDatamanager: AddContactLocalDataManagerInputProtocol? 6 | 7 | func saveNewContact(firstName: String, lastName: String) -> Contact? { 8 | do { 9 | let contact = try localDatamanager?.createContact(firstName: firstName, lastName: lastName) 10 | return contact 11 | } catch { 12 | return nil 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /VIPER Contacts Final/VIPER Contacts Final/ContactList/Interactor/ContactListInteractor.swift: -------------------------------------------------------------------------------- 1 | 2 | class ContactListInteractor: ContactListInteractorInputProtocol { 3 | weak var presenter: ContactListInteractorOutputProtocol? 4 | var localDatamanager: ContactListLocalDataManagerInputProtocol? 5 | 6 | func retrieveContacts() { 7 | do { 8 | if let contactList = try localDatamanager?.retrieveContactList() { 9 | presenter?.didRetrieveContacts(contactList) 10 | } else { 11 | presenter?.didRetrieveContacts([]) 12 | } 13 | 14 | } catch { 15 | presenter?.didRetrieveContacts([]) 16 | } 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /VIPER Contacts Starter/VIPER Contacts Starter/ContactList/Presenter/ContactListPresenter.swift: -------------------------------------------------------------------------------- 1 | 2 | class ContactListPresenter: ContactListPresenterProtocol { 3 | weak var view: ContactListViewProtocol? 4 | var interactor: ContactListInteractorInputProtocol? 5 | var wireFrame: ContactListWireFrameProtocol? 6 | 7 | func viewDidLoad() {} 8 | 9 | func addNewContact(from view: ContactListViewProtocol) {} 10 | 11 | } 12 | 13 | extension ContactListPresenter: ContactListInteractorOutputProtocol { 14 | 15 | func didRetrieveContacts(_ contacts: [Contact]) {} 16 | 17 | } 18 | 19 | extension ContactListPresenter: AddModuleDelegate { 20 | 21 | func didAddContact(_ contact: Contact) {} 22 | 23 | func didCancelAddContact() {} 24 | 25 | } 26 | -------------------------------------------------------------------------------- /MVVM Contacts Final/MVVM Contacts Final/MVVM_Contacts.xcdatamodeld/MVVM_Contacts.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /MVVM Contacts Starter/MVVM Contacts Starter/MVVM_Contacts.xcdatamodeld/MVVM_Contacts.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /VIPER Contacts Starter/VIPER Contacts Starter/AddContact/Interactor/AddContactInteractor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by AUTHOR 3 | // Copyright (c) YEAR AUTHOR. All rights reserved. 4 | // 5 | 6 | 7 | class AddContactInteractor: AddContactInteractorInputProtocol { 8 | weak var presenter: AddContactInteractorOutputProtocol? 9 | var APIDataManager: AddContactAPIDataManagerInputProtocol? 10 | var localDatamanager: AddContactLocalDataManagerInputProtocol? 11 | 12 | func saveNewContact(firstName: String, lastName: String) -> Contact? { 13 | do { 14 | let contact = try localDatamanager?.createContact(firstName: firstName, lastName: lastName) 15 | return contact 16 | } catch { 17 | return nil 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /VIPER Contacts Final/VIPER Contacts Final/VIPER_Contacts_Starter.xcdatamodeld/VIPER_Contacts_Starter.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /VIPER Contacts Starter/VIPER Contacts Starter/VIPER_Contacts_Starter.xcdatamodeld/VIPER_Contacts_Starter.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /MVVM Contacts Final/MVVM Contacts Final/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /VIPER Contacts Final/VIPER Contacts Final/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /VIPER Contacts Starter/VIPER Contacts Starter/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /VIPER Contacts Final/VIPER Contacts Final/Contact.swift: -------------------------------------------------------------------------------- 1 | import CoreData 2 | 3 | open class Contact: NSManagedObject { 4 | 5 | var fullName: String { 6 | get { 7 | var name = "" 8 | if let firstName = firstName { 9 | name += firstName 10 | } 11 | if let lastName = lastName { 12 | name += " " + lastName 13 | } 14 | return name 15 | } 16 | } 17 | 18 | } 19 | 20 | public struct ContactViewModel { 21 | var fullName = "" 22 | } 23 | 24 | public func <(lhs: ContactViewModel, rhs: ContactViewModel) -> Bool { 25 | return lhs.fullName.lowercased() < rhs.fullName.lowercased() 26 | } 27 | 28 | public func >(lhs: ContactViewModel, rhs: ContactViewModel) -> Bool { 29 | return lhs.fullName.lowercased() > rhs.fullName.lowercased() 30 | } 31 | -------------------------------------------------------------------------------- /VIPER Contacts Final/VIPER Contacts Final/CoreDataStore.swift: -------------------------------------------------------------------------------- 1 | import CoreData 2 | import UIKit 3 | 4 | class CoreDataStore { 5 | 6 | static var persistentStoreCoordinator: NSPersistentStoreCoordinator? { 7 | if let appDelegate = UIApplication.shared.delegate as? AppDelegate { 8 | return appDelegate.persistentStoreCoordinator 9 | } 10 | return nil 11 | } 12 | 13 | static var managedObjectModel: NSManagedObjectModel? { 14 | if let appDelegate = UIApplication.shared.delegate as? AppDelegate { 15 | return appDelegate.managedObjectModel 16 | } 17 | return nil 18 | } 19 | 20 | static var managedObjectContext: NSManagedObjectContext? { 21 | if let appDelegate = UIApplication.shared.delegate as? AppDelegate { 22 | return appDelegate.managedObjectContext 23 | } 24 | return nil 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /VIPER Contacts Starter/VIPER Contacts Starter/ContactList/View/ContactListView.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class ContactListView: UIViewController { 4 | 5 | @IBOutlet var tableView: UITableView! 6 | var presenter: ContactListPresenterProtocol? 7 | 8 | @IBAction func didClickOnAddButton(_ sender: UIBarButtonItem) {} 9 | 10 | } 11 | 12 | extension ContactListView: ContactListViewProtocol { 13 | 14 | func reloadInterface(with contacts: [ContactViewModel]) {} 15 | 16 | func didInsertContact(_ contact: ContactViewModel) {} 17 | 18 | } 19 | 20 | extension ContactListView: UITableViewDataSource { 21 | 22 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 23 | return UITableViewCell() 24 | } 25 | 26 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 27 | return 0 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /MVVM Contacts Starter/MVVM Contacts Starter/CoreDataStore.swift: -------------------------------------------------------------------------------- 1 | import CoreData 2 | import UIKit 3 | 4 | class CoreDataStore { 5 | 6 | static var persistentStoreCoordinator: NSPersistentStoreCoordinator? { 7 | if let appDelegate = UIApplication.shared.delegate as? AppDelegate { 8 | return appDelegate.persistentStoreCoordinator 9 | } 10 | return nil 11 | } 12 | 13 | static var managedObjectModel: NSManagedObjectModel? { 14 | if let appDelegate = UIApplication.shared.delegate as? AppDelegate { 15 | return appDelegate.managedObjectModel 16 | } 17 | return nil 18 | } 19 | 20 | static var managedObjectContext: NSManagedObjectContext? { 21 | if let appDelegate = UIApplication.shared.delegate as? AppDelegate { 22 | return appDelegate.managedObjectContext 23 | } 24 | return nil 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /VIPER Contacts Starter/VIPER Contacts Starter/CoreDataStore.swift: -------------------------------------------------------------------------------- 1 | import CoreData 2 | import UIKit 3 | 4 | class CoreDataStore { 5 | 6 | static var persistentStoreCoordinator: NSPersistentStoreCoordinator? { 7 | if let appDelegate = UIApplication.shared.delegate as? AppDelegate { 8 | return appDelegate.persistentStoreCoordinator 9 | } 10 | return nil 11 | } 12 | 13 | static var managedObjectModel: NSManagedObjectModel? { 14 | if let appDelegate = UIApplication.shared.delegate as? AppDelegate { 15 | return appDelegate.managedObjectModel 16 | } 17 | return nil 18 | } 19 | 20 | static var managedObjectContext: NSManagedObjectContext? { 21 | if let appDelegate = UIApplication.shared.delegate as? AppDelegate { 22 | return appDelegate.managedObjectContext 23 | } 24 | return nil 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /VIPER Contacts Final/VIPER Contacts Final/ContactList/DataManager/Local/ContactListLocalDataManager.swift: -------------------------------------------------------------------------------- 1 | import CoreData 2 | 3 | class ContactListLocalDataManager: ContactListLocalDataManagerInputProtocol { 4 | 5 | func retrieveContactList() throws -> [Contact] { 6 | guard let managedOC = CoreDataStore.managedObjectContext else { 7 | throw PersistenceError.managedObjectContextNotFound 8 | } 9 | 10 | let request: NSFetchRequest = NSFetchRequest(entityName: String(describing: Contact.self)) 11 | let caseInsensitiveSelector = #selector(NSString.caseInsensitiveCompare(_:)) 12 | let sortDescriptorFirstName = NSSortDescriptor(key: "firstName", ascending: true, selector: caseInsensitiveSelector) 13 | let sortDescriptorLastName = NSSortDescriptor(key: "lastName", ascending: true, selector: caseInsensitiveSelector) 14 | request.sortDescriptors = [sortDescriptorFirstName, sortDescriptorLastName] 15 | 16 | return try managedOC.fetch(request) 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /MVVM Contacts Starter/MVVM Contacts Starter/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 | } -------------------------------------------------------------------------------- /VIPER Contacts Starter/VIPER Contacts Starter/ContactList/DataManager/Local/ContactListLocalDataManager.swift: -------------------------------------------------------------------------------- 1 | import CoreData 2 | 3 | class ContactListLocalDataManager: ContactListLocalDataManagerInputProtocol { 4 | 5 | func retrieveContactList() throws -> [Contact] { 6 | guard let managedOC = CoreDataStore.managedObjectContext else { 7 | throw PersistenceError.managedObjectContextNotFound 8 | } 9 | 10 | let request: NSFetchRequest = NSFetchRequest(entityName: String(describing: Contact.self)) 11 | let caseInsensitiveSelector = #selector(NSString.caseInsensitiveCompare(_:)) 12 | let sortDescriptorFirstName = NSSortDescriptor(key: "firstName", ascending: true, selector: caseInsensitiveSelector) 13 | let sortDescriptorLastName = NSSortDescriptor(key: "lastName", ascending: true, selector: caseInsensitiveSelector) 14 | request.sortDescriptors = [sortDescriptorFirstName, sortDescriptorLastName] 15 | 16 | return try managedOC.fetch(request) 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /VIPER Contacts Final/VIPER Contacts Final/AddContact/Presenter/AddContactPresenter.swift: -------------------------------------------------------------------------------- 1 | 2 | class AddContactPresenter: AddContactPresenterProtocol, AddContactInteractorOutputProtocol { 3 | weak var view: AddContactViewProtocol? 4 | var interactor: AddContactInteractorInputProtocol? 5 | var wireFrame: AddContactWireFrameProtocol? 6 | var delegate: AddModuleDelegate? 7 | 8 | func cancelAddContactAction() { 9 | if let view = view { 10 | wireFrame?.dismissAddContactInterface(from: view) { [weak delegate] in 11 | delegate?.didCancelAddContact() 12 | } 13 | } 14 | } 15 | 16 | func addNewContact(firstName: String, lastName: String) { 17 | let contact = interactor?.saveNewContact(firstName: firstName, lastName: lastName) 18 | if let view = view, let contact = contact { 19 | wireFrame?.dismissAddContactInterface(from: view) { [weak delegate] in 20 | delegate?.didAddContact(contact) 21 | } 22 | } 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /VIPER Contacts Final/VIPER Contacts Final/AddContact/DataManager/Local/AddContactLocalDataManager.swift: -------------------------------------------------------------------------------- 1 | import CoreData 2 | 3 | enum PersistenceError: Error { 4 | case managedObjectContextNotFound 5 | case couldNotCreateObject 6 | case objectNotFound 7 | } 8 | 9 | class AddContactLocalDataManager: AddContactLocalDataManagerInputProtocol { 10 | 11 | func createContact(firstName: String, lastName: String) throws -> Contact { 12 | guard let managedOC = CoreDataStore.managedObjectContext else { 13 | throw PersistenceError.managedObjectContextNotFound 14 | } 15 | 16 | if let newContact = NSEntityDescription.entity(forEntityName: "Contact", 17 | in: managedOC) { 18 | let contact = Contact(entity: newContact, insertInto: managedOC) 19 | contact.firstName = firstName 20 | contact.lastName = lastName 21 | try managedOC.save() 22 | return contact 23 | } 24 | throw PersistenceError.couldNotCreateObject 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /MVVM Contacts Final/MVVM Contacts Final/CoreDataStore.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoreDataStore.swift 3 | // VIPER Contacts Final 4 | // 5 | // Created by Rafael Sacchi on 8/29/16. 6 | // Copyright © 2016 Rafael Sacchi. All rights reserved. 7 | // 8 | 9 | import CoreData 10 | import UIKit 11 | 12 | class CoreDataStore { 13 | 14 | static var persistentStoreCoordinator: NSPersistentStoreCoordinator? { 15 | if let appDelegate = UIApplication.shared.delegate as? AppDelegate { 16 | return appDelegate.persistentStoreCoordinator 17 | } 18 | return nil 19 | } 20 | 21 | static var managedObjectModel: NSManagedObjectModel? { 22 | if let appDelegate = UIApplication.shared.delegate as? AppDelegate { 23 | return appDelegate.managedObjectModel 24 | } 25 | return nil 26 | } 27 | 28 | static var managedObjectContext: NSManagedObjectContext? { 29 | if let appDelegate = UIApplication.shared.delegate as? AppDelegate { 30 | return appDelegate.managedObjectContext 31 | } 32 | return nil 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 auth0-tutorials 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /VIPER Contacts Starter/VIPER Contacts Starter/AddContact/Presenter/AddContactPresenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by AUTHOR 3 | // Copyright (c) YEAR AUTHOR. All rights reserved. 4 | // 5 | 6 | 7 | class AddContactPresenter: AddContactPresenterProtocol, AddContactInteractorOutputProtocol { 8 | weak var view: AddContactViewProtocol? 9 | var interactor: AddContactInteractorInputProtocol? 10 | var wireFrame: AddContactWireFrameProtocol? 11 | var delegate: AddModuleDelegate? 12 | 13 | func cancelAddContactAction() { 14 | if let view = view { 15 | wireFrame?.dismissAddContactInterface(from: view) { [weak delegate] in 16 | delegate?.didCancelAddContact() 17 | } 18 | } 19 | } 20 | 21 | func addNewContact(firstName: String, lastName: String) { 22 | let contact = interactor?.saveNewContact(firstName: firstName, lastName: lastName) 23 | if let view = view, let contact = contact { 24 | wireFrame?.dismissAddContactInterface(from: view) { [weak delegate] in 25 | delegate?.didAddContact(contact) 26 | } 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /VIPER Contacts Final/VIPER Contacts Final/ContactList/Presenter/ContactListPresenter.swift: -------------------------------------------------------------------------------- 1 | 2 | class ContactListPresenter: ContactListPresenterProtocol { 3 | weak var view: ContactListViewProtocol? 4 | var interactor: ContactListInteractorInputProtocol? 5 | var wireFrame: ContactListWireFrameProtocol? 6 | 7 | func viewDidLoad() { 8 | interactor?.retrieveContacts() 9 | } 10 | 11 | func addNewContact(from view: ContactListViewProtocol) { 12 | wireFrame?.presentAddContactScreen(from: view) 13 | } 14 | 15 | } 16 | 17 | extension ContactListPresenter: ContactListInteractorOutputProtocol { 18 | 19 | func didRetrieveContacts(_ contacts: [Contact]) { 20 | view?.reloadInterface(with: contacts.map() { 21 | return ContactViewModel(fullName: $0.fullName) 22 | }) 23 | } 24 | 25 | } 26 | 27 | extension ContactListPresenter: AddModuleDelegate { 28 | 29 | func didAddContact(_ contact: Contact) { 30 | let contactViewModel = ContactViewModel(fullName: contact.fullName) 31 | view?.didInsertContact(contactViewModel) 32 | } 33 | 34 | func didCancelAddContact() {} 35 | 36 | } 37 | -------------------------------------------------------------------------------- /VIPER Contacts Starter/VIPER Contacts Starter/AddContact/DataManager/Local/AddContactLocalDataManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by AUTHOR 3 | // Copyright (c) YEAR AUTHOR. All rights reserved. 4 | // 5 | 6 | import CoreData 7 | 8 | enum PersistenceError: Error { 9 | case managedObjectContextNotFound 10 | case couldNotCreateObject 11 | case objectNotFound 12 | } 13 | 14 | class AddContactLocalDataManager: AddContactLocalDataManagerInputProtocol { 15 | 16 | func createContact(firstName: String, lastName: String) throws -> Contact { 17 | guard let managedOC = CoreDataStore.managedObjectContext else { 18 | throw PersistenceError.managedObjectContextNotFound 19 | } 20 | 21 | if let newContact = NSEntityDescription.entity(forEntityName: "Contact", 22 | in: managedOC) { 23 | let contact = Contact(entity: newContact, insertInto: managedOC) 24 | contact.firstName = firstName 25 | contact.lastName = lastName 26 | try managedOC.save() 27 | return contact 28 | } 29 | throw PersistenceError.couldNotCreateObject 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /VIPER Contacts Final/VIPER Contacts Final/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 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UIStatusBarStyle 32 | UIStatusBarStyleLightContent 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UIViewControllerBasedStatusBarAppearance 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /VIPER Contacts Final/VIPER Contacts Final/AddContact/View/AddContactView.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class AddContactView: UIViewController, AddContactViewProtocol { 4 | var presenter: AddContactPresenterProtocol? 5 | 6 | @IBOutlet var firstNameTextField: UITextField! 7 | @IBOutlet var lastNameTextField: UITextField! 8 | 9 | override func viewDidLoad() { 10 | super.viewDidLoad() 11 | firstNameTextField.becomeFirstResponder() 12 | } 13 | 14 | @IBAction func didClickOnDoneButton(_ sender: UIBarButtonItem) { 15 | guard 16 | let firstName = firstNameTextField.text, 17 | let lastName = lastNameTextField.text 18 | else { 19 | return 20 | } 21 | 22 | if firstName.isEmpty || lastName.isEmpty { 23 | showEmptyNameAlert() 24 | return 25 | } 26 | presenter?.addNewContact(firstName: firstName, lastName: lastName) 27 | } 28 | 29 | @IBAction func didClickOnCancelButton(_ sender: UIBarButtonItem) { 30 | presenter?.cancelAddContactAction() 31 | } 32 | 33 | fileprivate func showEmptyNameAlert() { 34 | let alertView = UIAlertController(title: "Error", 35 | message: "A contact must have first and last names", 36 | preferredStyle: .alert) 37 | alertView.addAction(UIAlertAction(title: "Ok", style: .destructive, handler: nil)) 38 | present(alertView, animated: true, completion: nil) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /MVVM Contacts Final/MVVM Contacts Final/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 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UIStatusBarStyle 34 | UIStatusBarStyleLightContent 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UIViewControllerBasedStatusBarAppearance 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /MVVM Contacts Starter/MVVM Contacts Starter/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 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UIStatusBarStyle 34 | UIStatusBarStyleLightContent 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UIViewControllerBasedStatusBarAppearance 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /VIPER Contacts Starter/VIPER Contacts Starter/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 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UIStatusBarStyle 34 | UIStatusBarStyleLightContent 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UIViewControllerBasedStatusBarAppearance 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /MVVM Contacts Final/MVVM Contacts Final/ContactViewModelController.swift: -------------------------------------------------------------------------------- 1 | 2 | class ContactViewModelController { 3 | 4 | fileprivate var contactViewModelList: [ContactViewModel] = [] 5 | fileprivate var dataManager = ContactLocalDataManager() 6 | 7 | var contactsCount: Int { 8 | return contactViewModelList.count 9 | } 10 | 11 | func retrieveContacts(_ success: (() -> Void)?, failure: (() -> Void)?) { 12 | do { 13 | let contacts = try dataManager.retrieveContactList() 14 | contactViewModelList = contacts.map() { ContactViewModel(fullName: $0.fullName) } 15 | success?() 16 | } catch { 17 | failure?() 18 | } 19 | } 20 | 21 | func viewModel(at index: Int) -> ContactViewModel { 22 | return contactViewModelList[index] 23 | } 24 | 25 | func createContact(firstName: String, lastName: String, 26 | success: ((ContactViewModel, Int) -> Void)?, 27 | failure: (() -> Void)?) { 28 | do { 29 | let contact = try dataManager.createContact(firstName: firstName, lastName: lastName) 30 | let contactViewModel = ContactViewModel(fullName: contact.fullName) 31 | let insertionIndex = contactViewModelList.insertionIndex(of: contactViewModel) { $0 < $1 } 32 | contactViewModelList.insert(contactViewModel, at: insertionIndex) 33 | success?(contactViewModel, insertionIndex) 34 | } catch { 35 | failure?() 36 | } 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /VIPER Contacts Starter/VIPER Contacts Starter/AddContact/View/AddContactView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by AUTHOR 3 | // Copyright (c) YEAR AUTHOR. All rights reserved. 4 | // 5 | 6 | import UIKit 7 | 8 | class AddContactView: UIViewController, AddContactViewProtocol { 9 | var presenter: AddContactPresenterProtocol? 10 | 11 | @IBOutlet var firstNameTextField: UITextField! 12 | @IBOutlet var lastNameTextField: UITextField! 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | firstNameTextField.becomeFirstResponder() 17 | } 18 | 19 | @IBAction func didClickOnDoneButton(_ sender: UIBarButtonItem) { 20 | guard 21 | let firstName = firstNameTextField.text, 22 | let lastName = lastNameTextField.text 23 | else { 24 | return 25 | } 26 | 27 | if firstName.isEmpty || lastName.isEmpty { 28 | showEmptyNameAlert() 29 | return 30 | } 31 | presenter?.addNewContact(firstName: firstName, lastName: lastName) 32 | } 33 | 34 | @IBAction func didClickOnCancelButton(_ sender: UIBarButtonItem) { 35 | presenter?.cancelAddContactAction() 36 | } 37 | 38 | fileprivate func showEmptyNameAlert() { 39 | let alertView = UIAlertController(title: "Error", 40 | message: "A contact must have first and last names", 41 | preferredStyle: .alert) 42 | alertView.addAction(UIAlertAction(title: "Ok", style: .destructive, handler: nil)) 43 | present(alertView, animated: true, completion: nil) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | # Xcode 3 | # 4 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 5 | 6 | ## Build generated 7 | build/ 8 | DerivedData/ 9 | 10 | ## Various settings 11 | *.pbxuser 12 | !default.pbxuser 13 | *.mode1v3 14 | !default.mode1v3 15 | *.mode2v3 16 | !default.mode2v3 17 | *.perspectivev3 18 | !default.perspectivev3 19 | xcuserdata/ 20 | 21 | ## Other 22 | *.moved-aside 23 | *.xcuserstate 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | .build/ 40 | 41 | # CocoaPods 42 | # 43 | # We recommend against adding the Pods directory to your .gitignore. However 44 | # you should judge for yourself, the pros and cons are mentioned at: 45 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 46 | # 47 | # Pods/ 48 | 49 | # Carthage 50 | # 51 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 52 | # Carthage/Checkouts 53 | 54 | Carthage/Build 55 | 56 | # fastlane 57 | # 58 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 59 | # screenshots whenever they are needed. 60 | # For more information about the recommended setup visit: 61 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 62 | 63 | fastlane/report.xml 64 | fastlane/Preview.html 65 | fastlane/screenshots 66 | fastlane/test_output 67 | -------------------------------------------------------------------------------- /VIPER Contacts Final/VIPER Contacts Final/AddContact/WireFrame/AddContactWireFrame.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class AddContactWireFrame: AddContactWireFrameProtocol { 4 | 5 | class func createAddContactModule(with delegate: AddModuleDelegate) -> UIViewController { 6 | 7 | let navController = mainStoryboard.instantiateViewController(withIdentifier: "AddContactsNavigationController") 8 | if let view = navController.childViewControllers.first as? AddContactViewProtocol { 9 | // Generating module components 10 | let presenter: AddContactPresenterProtocol & AddContactInteractorOutputProtocol = AddContactPresenter() 11 | let interactor: AddContactInteractorInputProtocol = AddContactInteractor() 12 | let localDataManager: AddContactLocalDataManagerInputProtocol = AddContactLocalDataManager() 13 | let wireFrame: AddContactWireFrameProtocol = AddContactWireFrame() 14 | 15 | // Connecting 16 | view.presenter = presenter 17 | presenter.view = view 18 | presenter.wireFrame = wireFrame 19 | presenter.interactor = interactor 20 | presenter.delegate = delegate 21 | interactor.presenter = presenter 22 | interactor.localDatamanager = localDataManager 23 | 24 | return navController 25 | } 26 | return UIViewController() 27 | } 28 | 29 | static var mainStoryboard: UIStoryboard { 30 | return UIStoryboard(name: "Main", bundle: Bundle.main) 31 | } 32 | 33 | func dismissAddContactInterface(from view: AddContactViewProtocol, completion: (() -> Void)?) { 34 | if let view = view as? UIViewController { 35 | view.dismiss(animated: true, completion: completion) 36 | } 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /MVVM Contacts Final/MVVM Contacts Final/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 | -------------------------------------------------------------------------------- /MVVM Contacts Starter/MVVM Contacts Starter/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 | -------------------------------------------------------------------------------- /VIPER Contacts Final/VIPER Contacts Final/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 | -------------------------------------------------------------------------------- /VIPER Contacts Starter/VIPER Contacts Starter/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 | -------------------------------------------------------------------------------- /VIPER Contacts Final/VIPER Contacts Final/ContactList/WireFrame/ContactListWireFrame.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class ContactListWireFrame: ContactListWireFrameProtocol { 4 | 5 | class func createContactListModule() -> UIViewController { 6 | let navController = mainStoryboard.instantiateViewController(withIdentifier: "ContactsNavigationController") 7 | if let view = navController.childViewControllers.first as? ContactListView { 8 | let presenter: ContactListPresenterProtocol & ContactListInteractorOutputProtocol = ContactListPresenter() 9 | let interactor: ContactListInteractorInputProtocol = ContactListInteractor() 10 | let localDataManager: ContactListLocalDataManagerInputProtocol = ContactListLocalDataManager() 11 | let wireFrame: ContactListWireFrameProtocol = ContactListWireFrame() 12 | 13 | view.presenter = presenter 14 | presenter.view = view 15 | presenter.wireFrame = wireFrame 16 | presenter.interactor = interactor 17 | interactor.presenter = presenter 18 | interactor.localDatamanager = localDataManager 19 | 20 | return navController 21 | } 22 | return UIViewController() 23 | } 24 | 25 | static var mainStoryboard: UIStoryboard { 26 | return UIStoryboard(name: "Main", bundle: Bundle.main) 27 | } 28 | 29 | func presentAddContactScreen(from view: ContactListViewProtocol) { 30 | 31 | guard let delegate = view.presenter as? AddModuleDelegate else { 32 | return 33 | } 34 | 35 | let addContactsView = AddContactWireFrame.createAddContactModule(with: delegate) 36 | if let sourceView = view as? UIViewController { 37 | sourceView.present(addContactsView, animated: true, completion: nil) 38 | } 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /MVVM Contacts Starter/MVVM Contacts Starter/ContactLocalDataManager.swift: -------------------------------------------------------------------------------- 1 | import CoreData 2 | 3 | enum PersistenceError: Error { 4 | case managedObjectContextNotFound 5 | case couldNotCreateObject 6 | case objectNotFound 7 | } 8 | 9 | class ContactLocalDataManager { 10 | 11 | func createContact(firstName: String, lastName: String) throws -> Contact { 12 | guard let managedOC = CoreDataStore.managedObjectContext else { 13 | throw PersistenceError.managedObjectContextNotFound 14 | } 15 | 16 | if let newContact = NSEntityDescription.entity(forEntityName: String(describing: Contact.self), 17 | in: managedOC) { 18 | let contact = Contact(entity: newContact, insertInto: managedOC) 19 | contact.firstName = firstName 20 | contact.lastName = lastName 21 | try managedOC.save() 22 | return contact 23 | } 24 | throw PersistenceError.couldNotCreateObject 25 | } 26 | 27 | func retrieveContactList() throws -> [Contact] { 28 | guard let managedOC = CoreDataStore.managedObjectContext else { 29 | throw PersistenceError.managedObjectContextNotFound 30 | } 31 | 32 | let request: NSFetchRequest = NSFetchRequest(entityName: String(describing: Contact.self)) 33 | let caseInsensitiveSelector = #selector(NSString.caseInsensitiveCompare(_:)) 34 | let sortDescriptorFirstName = NSSortDescriptor(key: "firstName", ascending: true, selector: caseInsensitiveSelector) 35 | let sortDescriptorLastName = NSSortDescriptor(key: "lastName", ascending: true, selector: caseInsensitiveSelector) 36 | request.sortDescriptors = [sortDescriptorFirstName, sortDescriptorLastName] 37 | 38 | return try managedOC.fetch(request) 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /VIPER Contacts Final/VIPER Contacts Final/ContactList/View/ContactListView.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class ContactListView: UIViewController { 4 | 5 | @IBOutlet var tableView: UITableView! 6 | var presenter: ContactListPresenterProtocol? 7 | var contactList: [ContactViewModel] = [] 8 | 9 | override func viewDidLoad() { 10 | super.viewDidLoad() 11 | presenter?.viewDidLoad() 12 | 13 | tableView.tableFooterView = UIView() 14 | } 15 | 16 | @IBAction func didClickOnAddButton(_ sender: UIBarButtonItem) { 17 | presenter?.addNewContact(from: self) 18 | } 19 | 20 | } 21 | 22 | extension ContactListView: ContactListViewProtocol { 23 | 24 | func reloadInterface(with contacts: [ContactViewModel]) { 25 | contactList = contacts 26 | tableView.reloadData() 27 | } 28 | 29 | func didInsertContact(_ contact: ContactViewModel) { 30 | let insertionIndex = contactList.insertionIndex(of: contact) { $0 < $1 } 31 | contactList.insert(contact, at: insertionIndex) 32 | 33 | let indexPath = IndexPath(row: insertionIndex, section: 0) 34 | tableView.beginUpdates() 35 | tableView.insertRows(at: [indexPath], with: .right) 36 | tableView.endUpdates() 37 | } 38 | 39 | } 40 | 41 | extension ContactListView: UITableViewDataSource { 42 | 43 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 44 | guard let cell = tableView.dequeueReusableCell(withIdentifier: "ContactCell") else { 45 | return UITableViewCell() 46 | } 47 | cell.textLabel?.text = contactList[(indexPath as NSIndexPath).row].fullName 48 | return cell 49 | } 50 | 51 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 52 | return contactList.count 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /VIPER Contacts Starter/VIPER Contacts Starter/AddContact/WireFrame/AddContactWireFrame.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by AUTHOR 3 | // Copyright (c) YEAR AUTHOR. All rights reserved. 4 | // 5 | 6 | import UIKit 7 | 8 | class AddContactWireFrame: AddContactWireFrameProtocol { 9 | 10 | class func createAddContactModule(with delegate: AddModuleDelegate) -> UIViewController { 11 | 12 | let navController = mainStoryboard.instantiateViewController(withIdentifier: "AddContactsNavigationController") 13 | if let view = navController.childViewControllers.first as? AddContactViewProtocol { 14 | // Generating module components 15 | let presenter: AddContactPresenterProtocol & AddContactInteractorOutputProtocol = AddContactPresenter() 16 | let interactor: AddContactInteractorInputProtocol = AddContactInteractor() 17 | let localDataManager: AddContactLocalDataManagerInputProtocol = AddContactLocalDataManager() 18 | let wireFrame: AddContactWireFrameProtocol = AddContactWireFrame() 19 | 20 | // Connecting 21 | view.presenter = presenter 22 | presenter.view = view 23 | presenter.wireFrame = wireFrame 24 | presenter.interactor = interactor 25 | presenter.delegate = delegate 26 | interactor.presenter = presenter 27 | interactor.localDatamanager = localDataManager 28 | 29 | return navController 30 | } 31 | return UIViewController() 32 | } 33 | 34 | static var mainStoryboard: UIStoryboard { 35 | return UIStoryboard(name: "Main", bundle: Bundle.main) 36 | } 37 | 38 | func dismissAddContactInterface(from view: AddContactViewProtocol, completion: (() -> Void)?) { 39 | if let view = view as? UIViewController { 40 | view.dismiss(animated: true, completion: completion) 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /VIPER Contacts Final/VIPER Contacts Final/ContactList/Protocols/ContactListProtocols.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | protocol ContactListViewProtocol: class { 4 | var presenter: ContactListPresenterProtocol? { get set } 5 | 6 | // PRESENTER -> VIEW 7 | func didInsertContact(_ contact: ContactViewModel) 8 | func reloadInterface(with contacts: [ContactViewModel]) 9 | } 10 | 11 | protocol ContactListWireFrameProtocol: class { 12 | static func createContactListModule() -> UIViewController 13 | 14 | // PRESENTER -> WIREFRAME 15 | func presentAddContactScreen(from view: ContactListViewProtocol) 16 | } 17 | 18 | protocol ContactListPresenterProtocol: class { 19 | var view: ContactListViewProtocol? { get set } 20 | var interactor: ContactListInteractorInputProtocol? { get set } 21 | var wireFrame: ContactListWireFrameProtocol? { get set } 22 | 23 | // VIEW -> PRESENTER 24 | func viewDidLoad() 25 | func addNewContact(from view: ContactListViewProtocol) 26 | } 27 | 28 | protocol ContactListInteractorOutputProtocol: class { 29 | // INTERACTOR -> PRESENTER 30 | 31 | func didRetrieveContacts(_ contacts: [Contact]) 32 | } 33 | 34 | protocol ContactListInteractorInputProtocol: class { 35 | var presenter: ContactListInteractorOutputProtocol? { get set } 36 | var localDatamanager: ContactListLocalDataManagerInputProtocol? { get set } 37 | /** 38 | * Add here your methods for communication PRESENTER -> INTERACTOR 39 | */ 40 | func retrieveContacts() 41 | } 42 | 43 | protocol ContactListDataManagerInputProtocol: class { 44 | /** 45 | * Add here your methods for communication INTERACTOR -> DATAMANAGER 46 | */ 47 | } 48 | 49 | protocol ContactListLocalDataManagerInputProtocol: class { 50 | /** 51 | * Add here your methods for communication INTERACTOR -> LOCALDATAMANAGER 52 | */ 53 | func retrieveContactList() throws -> [Contact] 54 | } 55 | -------------------------------------------------------------------------------- /VIPER Contacts Starter/VIPER Contacts Starter/ContactList/Protocols/ContactListProtocols.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | protocol ContactListViewProtocol: class { 4 | var presenter: ContactListPresenterProtocol? { get set } 5 | 6 | // PRESENTER -> VIEW 7 | func didInsertContact(_ contact: ContactViewModel) 8 | func reloadInterface(with contacts: [ContactViewModel]) 9 | } 10 | 11 | protocol ContactListWireFrameProtocol: class { 12 | static func createContactListModule() -> UIViewController 13 | 14 | // PRESENTER -> WIREFRAME 15 | func presentAddContactScreen(from view: ContactListViewProtocol) 16 | } 17 | 18 | protocol ContactListPresenterProtocol: class { 19 | var view: ContactListViewProtocol? { get set } 20 | var interactor: ContactListInteractorInputProtocol? { get set } 21 | var wireFrame: ContactListWireFrameProtocol? { get set } 22 | 23 | // VIEW -> PRESENTER 24 | func viewDidLoad() 25 | func addNewContact(from view: ContactListViewProtocol) 26 | } 27 | 28 | protocol ContactListInteractorOutputProtocol: class { 29 | // INTERACTOR -> PRESENTER 30 | 31 | func didRetrieveContacts(_ contacts: [Contact]) 32 | } 33 | 34 | protocol ContactListInteractorInputProtocol: class { 35 | var presenter: ContactListInteractorOutputProtocol? { get set } 36 | var localDatamanager: ContactListLocalDataManagerInputProtocol? { get set } 37 | /** 38 | * Add here your methods for communication PRESENTER -> INTERACTOR 39 | */ 40 | func retrieveContacts() 41 | } 42 | 43 | protocol ContactListDataManagerInputProtocol: class { 44 | /** 45 | * Add here your methods for communication INTERACTOR -> DATAMANAGER 46 | */ 47 | } 48 | 49 | protocol ContactListLocalDataManagerInputProtocol: class { 50 | /** 51 | * Add here your methods for communication INTERACTOR -> LOCALDATAMANAGER 52 | */ 53 | func retrieveContactList() throws -> [Contact] 54 | } 55 | -------------------------------------------------------------------------------- /MVVM Contacts Final/MVVM Contacts Final/AddContactViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class AddContactViewController: UIViewController { 4 | 5 | @IBOutlet var firstNameTextField: UITextField! 6 | @IBOutlet var lastNameTextField: UITextField! 7 | var contactsViewModelController: ContactViewModelController? 8 | var didAddContact: ((ContactViewModel, Int) -> Void)? 9 | 10 | override func viewDidLoad() { 11 | super.viewDidLoad() 12 | firstNameTextField.becomeFirstResponder() 13 | } 14 | 15 | @IBAction func didClickOnDoneButton(_ sender: UIBarButtonItem) { 16 | guard let firstName = firstNameTextField.text, 17 | let lastName = lastNameTextField.text 18 | else { 19 | return 20 | } 21 | 22 | if firstName.isEmpty || lastName.isEmpty { 23 | showEmptyNameAlert() 24 | return 25 | } 26 | 27 | dismiss(animated: true) { [unowned self] in 28 | self.contactsViewModelController?.createContact(firstName: firstName, lastName: lastName, 29 | success: self.didAddContact, failure: nil) 30 | } 31 | 32 | } 33 | 34 | @IBAction func didClickOnCancelButton(_ sender: UIBarButtonItem) { 35 | dismiss(animated: true, completion: nil) 36 | } 37 | 38 | fileprivate func showEmptyNameAlert() { 39 | showMessage(title: "Error", message: "A contact must have first and last names") 40 | } 41 | 42 | fileprivate func showMessage(title: String, message: String) { 43 | let alertView = UIAlertController(title: title, 44 | message: message, 45 | preferredStyle: .alert) 46 | alertView.addAction(UIAlertAction(title: "Ok", style: .destructive, handler: nil)) 47 | present(alertView, animated: true, completion: nil) 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /MVVM Contacts Final/MVVM Contacts Final/ContactLocalDataManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by AUTHOR 3 | // Copyright (c) YEAR AUTHOR. All rights reserved. 4 | // 5 | 6 | import CoreData 7 | 8 | enum PersistenceError: Error { 9 | case managedObjectContextNotFound 10 | case couldNotCreateObject 11 | case objectNotFound 12 | } 13 | 14 | class ContactLocalDataManager { 15 | 16 | func createContact(firstName: String, lastName: String) throws -> Contact { 17 | guard let managedOC = CoreDataStore.managedObjectContext else { 18 | throw PersistenceError.managedObjectContextNotFound 19 | } 20 | 21 | if let newContact = NSEntityDescription.entity(forEntityName: String(describing: Contact.self), 22 | in: managedOC) { 23 | let contact = Contact(entity: newContact, insertInto: managedOC) 24 | contact.firstName = firstName 25 | contact.lastName = lastName 26 | try managedOC.save() 27 | return contact 28 | } 29 | throw PersistenceError.couldNotCreateObject 30 | } 31 | 32 | func retrieveContactList() throws -> [Contact] { 33 | guard let managedOC = CoreDataStore.managedObjectContext else { 34 | throw PersistenceError.managedObjectContextNotFound 35 | } 36 | 37 | let request: NSFetchRequest = NSFetchRequest(entityName: String(describing: Contact.self)) 38 | let caseInsensitiveSelector = #selector(NSString.caseInsensitiveCompare(_:)) 39 | let sortDescriptorFirstName = NSSortDescriptor(key: "firstName", ascending: true, selector: caseInsensitiveSelector) 40 | let sortDescriptorLastName = NSSortDescriptor(key: "lastName", ascending: true, selector: caseInsensitiveSelector) 41 | request.sortDescriptors = [sortDescriptorFirstName, sortDescriptorLastName] 42 | 43 | return try managedOC.fetch(request) 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /MVVM Contacts Final/MVVM Contacts Final/ContactsViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class ContactsViewController: UIViewController { 4 | 5 | @IBOutlet var tableView: UITableView! 6 | let contactViewModelController = ContactViewModelController() 7 | 8 | override func viewDidLoad() { 9 | super.viewDidLoad() 10 | tableView.tableFooterView = UIView() 11 | contactViewModelController.retrieveContacts({ [unowned self] in 12 | self.tableView.reloadData() 13 | }, failure: nil) 14 | } 15 | 16 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 17 | let addContactNavigationController = segue.destination as? UINavigationController 18 | let addContactVC = addContactNavigationController?.viewControllers[0] as? AddContactViewController 19 | 20 | addContactVC?.contactsViewModelController = contactViewModelController 21 | addContactVC?.didAddContact = { [unowned self] (contactViewModel, index) in 22 | let indexPath = IndexPath(row: index, section: 0) 23 | self.tableView.beginUpdates() 24 | self.tableView.insertRows(at: [indexPath], with: .left) 25 | self.tableView.endUpdates() 26 | } 27 | } 28 | 29 | } 30 | 31 | extension ContactsViewController: UITableViewDataSource { 32 | 33 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 34 | let cell = tableView.dequeueReusableCell(withIdentifier: "ContactCell") as? ContactsTableViewCell 35 | guard let contactsCell = cell else { 36 | return UITableViewCell() 37 | } 38 | 39 | contactsCell.cellModel = contactViewModelController.viewModel(at: (indexPath as NSIndexPath).row) 40 | return contactsCell 41 | } 42 | 43 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 44 | return contactViewModelController.contactsCount 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /VIPER Contacts Final/VIPER Contacts Final/AddContact/Protocols/AddContactProtocols.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import CoreData 3 | 4 | protocol AddModuleDelegate: class { 5 | func didAddContact(_ contact: Contact) 6 | func didCancelAddContact() 7 | } 8 | 9 | protocol AddContactViewProtocol: class { 10 | var presenter: AddContactPresenterProtocol? { get set } 11 | /** 12 | * Add here your methods for communication PRESENTER -> VIEW 13 | */ 14 | } 15 | 16 | protocol AddContactWireFrameProtocol: class { 17 | static func createAddContactModule(with delegate: AddModuleDelegate) -> UIViewController 18 | /** 19 | * Add here your methods for communication PRESENTER -> WIREFRAME 20 | */ 21 | func dismissAddContactInterface(from view: AddContactViewProtocol, completion: (() -> Void)?) 22 | } 23 | 24 | protocol AddContactPresenterProtocol: class { 25 | var view: AddContactViewProtocol? { get set } 26 | var interactor: AddContactInteractorInputProtocol? { get set } 27 | var wireFrame: AddContactWireFrameProtocol? { get set } 28 | var delegate: AddModuleDelegate? { get set } 29 | 30 | // VIEW -> PRESENTER 31 | func cancelAddContactAction() 32 | func addNewContact(firstName: String, lastName: String) 33 | } 34 | 35 | protocol AddContactInteractorOutputProtocol: class { 36 | /** 37 | * Add here your methods for communication INTERACTOR -> PRESENTER 38 | */ 39 | } 40 | 41 | protocol AddContactInteractorInputProtocol: class { 42 | var presenter: AddContactInteractorOutputProtocol? { get set } 43 | var APIDataManager: AddContactAPIDataManagerInputProtocol? { get set } 44 | var localDatamanager: AddContactLocalDataManagerInputProtocol? { get set } 45 | 46 | // PRESENTER -> INTERACTOR 47 | func saveNewContact(firstName: String, lastName: String) -> Contact? 48 | } 49 | 50 | protocol AddContactDataManagerInputProtocol: class { 51 | /** 52 | * Add here your methods for communication INTERACTOR -> DATAMANAGER 53 | */ 54 | } 55 | 56 | protocol AddContactAPIDataManagerInputProtocol: class { 57 | /** 58 | * Add here your methods for communication INTERACTOR -> APIDATAMANAGER 59 | */ 60 | } 61 | 62 | protocol AddContactLocalDataManagerInputProtocol: class { 63 | /** 64 | * Add here your methods for communication INTERACTOR -> LOCALDATAMANAGER 65 | */ 66 | func createContact(firstName: String, lastName: String) throws -> Contact 67 | } 68 | -------------------------------------------------------------------------------- /VIPER Contacts Starter/VIPER Contacts Starter/AddContact/Protocols/AddContactProtocols.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by AUTHOR 3 | // Copyright (c) YEAR AUTHOR. All rights reserved. 4 | // 5 | 6 | import UIKit 7 | import CoreData 8 | 9 | protocol AddModuleDelegate: class { 10 | func didAddContact(_ contact: Contact) 11 | func didCancelAddContact() 12 | } 13 | 14 | protocol AddContactViewProtocol: class { 15 | var presenter: AddContactPresenterProtocol? { get set } 16 | /** 17 | * Add here your methods for communication PRESENTER -> VIEW 18 | */ 19 | } 20 | 21 | protocol AddContactWireFrameProtocol: class { 22 | static func createAddContactModule(with delegate: AddModuleDelegate) -> UIViewController 23 | /** 24 | * Add here your methods for communication PRESENTER -> WIREFRAME 25 | */ 26 | func dismissAddContactInterface(from view: AddContactViewProtocol, completion: (() -> Void)?) 27 | } 28 | 29 | protocol AddContactPresenterProtocol: class { 30 | var view: AddContactViewProtocol? { get set } 31 | var interactor: AddContactInteractorInputProtocol? { get set } 32 | var wireFrame: AddContactWireFrameProtocol? { get set } 33 | var delegate: AddModuleDelegate? { get set } 34 | 35 | // VIEW -> PRESENTER 36 | func cancelAddContactAction() 37 | func addNewContact(firstName: String, lastName: String) 38 | } 39 | 40 | protocol AddContactInteractorOutputProtocol: class { 41 | /** 42 | * Add here your methods for communication INTERACTOR -> PRESENTER 43 | */ 44 | } 45 | 46 | protocol AddContactInteractorInputProtocol: class { 47 | var presenter: AddContactInteractorOutputProtocol? { get set } 48 | var APIDataManager: AddContactAPIDataManagerInputProtocol? { get set } 49 | var localDatamanager: AddContactLocalDataManagerInputProtocol? { get set } 50 | 51 | // PRESENTER -> INTERACTOR 52 | func saveNewContact(firstName: String, lastName: String) -> Contact? 53 | } 54 | 55 | protocol AddContactDataManagerInputProtocol: class { 56 | /** 57 | * Add here your methods for communication INTERACTOR -> DATAMANAGER 58 | */ 59 | } 60 | 61 | protocol AddContactAPIDataManagerInputProtocol: class { 62 | /** 63 | * Add here your methods for communication INTERACTOR -> APIDATAMANAGER 64 | */ 65 | } 66 | 67 | protocol AddContactLocalDataManagerInputProtocol: class { 68 | /** 69 | * Add here your methods for communication INTERACTOR -> LOCALDATAMANAGER 70 | */ 71 | func createContact(firstName: String, lastName: String) throws -> Contact 72 | } 73 | -------------------------------------------------------------------------------- /MVVM Contacts Final/MVVM Contacts Final/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import CoreData 3 | 4 | @UIApplicationMain 5 | class AppDelegate: UIResponder, UIApplicationDelegate { 6 | 7 | var window: UIWindow? 8 | 9 | 10 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 11 | // Override point for customization after application launch. 12 | return true 13 | } 14 | 15 | func applicationWillResignActive(_ application: UIApplication) { 16 | // 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. 17 | // 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. 18 | } 19 | 20 | func applicationDidEnterBackground(_ application: UIApplication) { 21 | // 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. 22 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 23 | } 24 | 25 | func applicationWillEnterForeground(_ application: UIApplication) { 26 | // 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. 27 | } 28 | 29 | func applicationDidBecomeActive(_ application: UIApplication) { 30 | // 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. 31 | } 32 | 33 | func applicationWillTerminate(_ application: UIApplication) { 34 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 35 | } 36 | 37 | // MARK: - Core Data stack 38 | 39 | lazy var applicationDocumentsDirectory: URL = { 40 | let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) 41 | return urls[urls.count-1] 42 | }() 43 | 44 | lazy var managedObjectModel: NSManagedObjectModel = { 45 | // The managed object model for the application. This property is not optional. It is a fatal error for the application not to be able to find and load its model. 46 | let modelURL = Bundle.main.url(forResource: "MVVM_Contacts", withExtension: "momd")! 47 | return NSManagedObjectModel(contentsOf: modelURL)! 48 | }() 49 | 50 | lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = { 51 | // The persistent store coordinator for the application. This implementation creates and returns a coordinator, having added the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail. 52 | // Create the coordinator and store 53 | let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel) 54 | let url = self.applicationDocumentsDirectory.appendingPathComponent("SingleViewCoreData.sqlite") 55 | var failureReason = "There was an error creating or loading the application's saved data." 56 | do { 57 | try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: nil) 58 | } catch { 59 | // Report any error we got. 60 | var dict = [String: AnyObject]() 61 | dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data" as AnyObject? 62 | dict[NSLocalizedFailureReasonErrorKey] = failureReason as AnyObject? 63 | 64 | dict[NSUnderlyingErrorKey] = error as NSError 65 | let wrappedError = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict) 66 | // Replace this with code to handle the error appropriately. 67 | // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 68 | NSLog("Unresolved error \(wrappedError), \(wrappedError.userInfo)") 69 | abort() 70 | } 71 | 72 | return coordinator 73 | }() 74 | 75 | lazy var managedObjectContext: NSManagedObjectContext = { 76 | // Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail. 77 | let coordinator = self.persistentStoreCoordinator 78 | var managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType) 79 | managedObjectContext.persistentStoreCoordinator = coordinator 80 | return managedObjectContext 81 | }() 82 | 83 | // MARK: - Core Data Saving support 84 | 85 | func saveContext () { 86 | if managedObjectContext.hasChanges { 87 | do { 88 | try managedObjectContext.save() 89 | } catch { 90 | // Replace this implementation with code to handle the error appropriately. 91 | // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 92 | let nserror = error as NSError 93 | NSLog("Unresolved error \(nserror), \(nserror.userInfo)") 94 | abort() 95 | } 96 | } 97 | } 98 | 99 | 100 | } 101 | -------------------------------------------------------------------------------- /MVVM Contacts Starter/MVVM Contacts Starter/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import CoreData 3 | 4 | @UIApplicationMain 5 | class AppDelegate: UIResponder, UIApplicationDelegate { 6 | 7 | var window: UIWindow? 8 | 9 | 10 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 11 | // Override point for customization after application launch. 12 | return true 13 | } 14 | 15 | func applicationWillResignActive(_ application: UIApplication) { 16 | // 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. 17 | // 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. 18 | } 19 | 20 | func applicationDidEnterBackground(_ application: UIApplication) { 21 | // 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. 22 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 23 | } 24 | 25 | func applicationWillEnterForeground(_ application: UIApplication) { 26 | // 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. 27 | } 28 | 29 | func applicationDidBecomeActive(_ application: UIApplication) { 30 | // 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. 31 | } 32 | 33 | func applicationWillTerminate(_ application: UIApplication) { 34 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 35 | } 36 | 37 | // MARK: - Core Data stack 38 | 39 | lazy var applicationDocumentsDirectory: URL = { 40 | let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) 41 | return urls[urls.count-1] 42 | }() 43 | 44 | lazy var managedObjectModel: NSManagedObjectModel = { 45 | // The managed object model for the application. This property is not optional. It is a fatal error for the application not to be able to find and load its model. 46 | let modelURL = Bundle.main.url(forResource: "MVVM_Contacts", withExtension: "momd")! 47 | return NSManagedObjectModel(contentsOf: modelURL)! 48 | }() 49 | 50 | lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = { 51 | // The persistent store coordinator for the application. This implementation creates and returns a coordinator, having added the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail. 52 | // Create the coordinator and store 53 | let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel) 54 | let url = self.applicationDocumentsDirectory.appendingPathComponent("SingleViewCoreData.sqlite") 55 | var failureReason = "There was an error creating or loading the application's saved data." 56 | do { 57 | try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: nil) 58 | } catch { 59 | // Report any error we got. 60 | var dict = [String: AnyObject]() 61 | dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data" as AnyObject? 62 | dict[NSLocalizedFailureReasonErrorKey] = failureReason as AnyObject? 63 | 64 | dict[NSUnderlyingErrorKey] = error as NSError 65 | let wrappedError = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict) 66 | // Replace this with code to handle the error appropriately. 67 | // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 68 | NSLog("Unresolved error \(wrappedError), \(wrappedError.userInfo)") 69 | abort() 70 | } 71 | 72 | return coordinator 73 | }() 74 | 75 | lazy var managedObjectContext: NSManagedObjectContext = { 76 | // Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail. 77 | let coordinator = self.persistentStoreCoordinator 78 | var managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType) 79 | managedObjectContext.persistentStoreCoordinator = coordinator 80 | return managedObjectContext 81 | }() 82 | 83 | // MARK: - Core Data Saving support 84 | 85 | func saveContext () { 86 | if managedObjectContext.hasChanges { 87 | do { 88 | try managedObjectContext.save() 89 | } catch { 90 | // Replace this implementation with code to handle the error appropriately. 91 | // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 92 | let nserror = error as NSError 93 | NSLog("Unresolved error \(nserror), \(nserror.userInfo)") 94 | abort() 95 | } 96 | } 97 | } 98 | 99 | 100 | } 101 | -------------------------------------------------------------------------------- /VIPER Contacts Final/VIPER Contacts Final/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import CoreData 3 | 4 | @UIApplicationMain 5 | class AppDelegate: UIResponder, UIApplicationDelegate { 6 | 7 | var window: UIWindow? 8 | 9 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 10 | 11 | let contactsList = ContactListWireFrame.createContactListModule() 12 | 13 | window = UIWindow(frame: UIScreen.main.bounds) 14 | window?.rootViewController = contactsList 15 | window?.makeKeyAndVisible() 16 | 17 | return true 18 | } 19 | 20 | func applicationWillResignActive(_ application: UIApplication) { 21 | // 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. 22 | // 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. 23 | } 24 | 25 | func applicationDidEnterBackground(_ application: UIApplication) { 26 | // 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. 27 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 28 | } 29 | 30 | func applicationWillEnterForeground(_ application: UIApplication) { 31 | // 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. 32 | } 33 | 34 | func applicationDidBecomeActive(_ application: UIApplication) { 35 | // 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. 36 | } 37 | 38 | func applicationWillTerminate(_ application: UIApplication) { 39 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 40 | self.saveContext() 41 | } 42 | 43 | // MARK: - Core Data stack 44 | 45 | lazy var applicationDocumentsDirectory: URL = { 46 | let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) 47 | return urls[urls.count-1] 48 | }() 49 | 50 | lazy var managedObjectModel: NSManagedObjectModel = { 51 | // The managed object model for the application. This property is not optional. It is a fatal error for the application not to be able to find and load its model. 52 | let modelURL = Bundle.main.url(forResource: "VIPER_Contacts_Starter", withExtension: "momd")! 53 | return NSManagedObjectModel(contentsOf: modelURL)! 54 | }() 55 | 56 | lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = { 57 | // The persistent store coordinator for the application. This implementation creates and returns a coordinator, having added the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail. 58 | // Create the coordinator and store 59 | let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel) 60 | let url = self.applicationDocumentsDirectory.appendingPathComponent("SingleViewCoreData.sqlite") 61 | var failureReason = "There was an error creating or loading the application's saved data." 62 | do { 63 | try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: nil) 64 | } catch { 65 | // Report any error we got. 66 | var dict = [String: AnyObject]() 67 | dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data" as AnyObject? 68 | dict[NSLocalizedFailureReasonErrorKey] = failureReason as AnyObject? 69 | 70 | dict[NSUnderlyingErrorKey] = error as NSError 71 | let wrappedError = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict) 72 | // Replace this with code to handle the error appropriately. 73 | // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 74 | NSLog("Unresolved error \(wrappedError), \(wrappedError.userInfo)") 75 | abort() 76 | } 77 | 78 | return coordinator 79 | }() 80 | 81 | lazy var managedObjectContext: NSManagedObjectContext = { 82 | // Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail. 83 | let coordinator = self.persistentStoreCoordinator 84 | var managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType) 85 | managedObjectContext.persistentStoreCoordinator = coordinator 86 | return managedObjectContext 87 | }() 88 | 89 | // MARK: - Core Data Saving support 90 | 91 | func saveContext () { 92 | if managedObjectContext.hasChanges { 93 | do { 94 | try managedObjectContext.save() 95 | } catch { 96 | // Replace this implementation with code to handle the error appropriately. 97 | // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 98 | let nserror = error as NSError 99 | NSLog("Unresolved error \(nserror), \(nserror.userInfo)") 100 | abort() 101 | } 102 | } 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /VIPER Contacts Starter/VIPER Contacts Starter/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import CoreData 3 | 4 | @UIApplicationMain 5 | class AppDelegate: UIResponder, UIApplicationDelegate { 6 | 7 | var window: UIWindow? 8 | 9 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 10 | 11 | let contactsList = ContactListWireFrame.createContactListModule() 12 | 13 | window = UIWindow(frame: UIScreen.main.bounds) 14 | window?.rootViewController = contactsList 15 | window?.makeKeyAndVisible() 16 | 17 | return true 18 | } 19 | 20 | func applicationWillResignActive(_ application: UIApplication) { 21 | // 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. 22 | // 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. 23 | } 24 | 25 | func applicationDidEnterBackground(_ application: UIApplication) { 26 | // 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. 27 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 28 | } 29 | 30 | func applicationWillEnterForeground(_ application: UIApplication) { 31 | // 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. 32 | } 33 | 34 | func applicationDidBecomeActive(_ application: UIApplication) { 35 | // 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. 36 | } 37 | 38 | func applicationWillTerminate(_ application: UIApplication) { 39 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 40 | self.saveContext() 41 | } 42 | 43 | // MARK: - Core Data stack 44 | 45 | lazy var applicationDocumentsDirectory: URL = { 46 | let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) 47 | return urls[urls.count-1] 48 | }() 49 | 50 | lazy var managedObjectModel: NSManagedObjectModel = { 51 | // The managed object model for the application. This property is not optional. It is a fatal error for the application not to be able to find and load its model. 52 | let modelURL = Bundle.main.url(forResource: "VIPER_Contacts_Starter", withExtension: "momd")! 53 | return NSManagedObjectModel(contentsOf: modelURL)! 54 | }() 55 | 56 | lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = { 57 | // The persistent store coordinator for the application. This implementation creates and returns a coordinator, having added the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail. 58 | // Create the coordinator and store 59 | let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel) 60 | let url = self.applicationDocumentsDirectory.appendingPathComponent("SingleViewCoreData.sqlite") 61 | var failureReason = "There was an error creating or loading the application's saved data." 62 | do { 63 | try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: nil) 64 | } catch { 65 | // Report any error we got. 66 | var dict = [String: AnyObject]() 67 | dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data" as AnyObject? 68 | dict[NSLocalizedFailureReasonErrorKey] = failureReason as AnyObject? 69 | 70 | dict[NSUnderlyingErrorKey] = error as NSError 71 | let wrappedError = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict) 72 | // Replace this with code to handle the error appropriately. 73 | // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 74 | NSLog("Unresolved error \(wrappedError), \(wrappedError.userInfo)") 75 | abort() 76 | } 77 | 78 | return coordinator 79 | }() 80 | 81 | lazy var managedObjectContext: NSManagedObjectContext = { 82 | // Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail. 83 | let coordinator = self.persistentStoreCoordinator 84 | var managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType) 85 | managedObjectContext.persistentStoreCoordinator = coordinator 86 | return managedObjectContext 87 | }() 88 | 89 | // MARK: - Core Data Saving support 90 | 91 | func saveContext () { 92 | if managedObjectContext.hasChanges { 93 | do { 94 | try managedObjectContext.save() 95 | } catch { 96 | // Replace this implementation with code to handle the error appropriately. 97 | // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 98 | let nserror = error as NSError 99 | NSLog("Unresolved error \(nserror), \(nserror.userInfo)") 100 | abort() 101 | } 102 | } 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /MVVM Contacts Final/MVVM Contacts Final.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 8D24E8441D766F79006CB8CB /* ContactLocalDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D24E8411D766F79006CB8CB /* ContactLocalDataManager.swift */; }; 11 | 8D24E8461D766F79006CB8CB /* CoreDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D24E8431D766F79006CB8CB /* CoreDataStore.swift */; }; 12 | 8D24E84A1D766FDE006CB8CB /* Contact+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D24E8491D766FDE006CB8CB /* Contact+CoreDataProperties.swift */; }; 13 | 8D24E84E1D767228006CB8CB /* MVVM_Contacts.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 8D24E84C1D767228006CB8CB /* MVVM_Contacts.xcdatamodeld */; }; 14 | 8D24E8521D7E50A5006CB8CB /* ContactViewModelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D24E8511D7E50A5006CB8CB /* ContactViewModelController.swift */; }; 15 | 8D24E8541D7E5711006CB8CB /* ContactsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D24E8531D7E5711006CB8CB /* ContactsTableViewCell.swift */; }; 16 | 8D660A0F1D60AC1A00F3D26B /* AddContactViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D660A031D60AC1A00F3D26B /* AddContactViewController.swift */; }; 17 | 8D660A111D60AC1A00F3D26B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D660A051D60AC1A00F3D26B /* AppDelegate.swift */; }; 18 | 8D660A121D60AC1A00F3D26B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8D660A061D60AC1A00F3D26B /* Assets.xcassets */; }; 19 | 8D660A131D60AC1A00F3D26B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8D660A071D60AC1A00F3D26B /* LaunchScreen.storyboard */; }; 20 | 8D660A141D60AC1A00F3D26B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8D660A091D60AC1A00F3D26B /* Main.storyboard */; }; 21 | 8D660A151D60AC1A00F3D26B /* Contact.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D660A0B1D60AC1A00F3D26B /* Contact.swift */; }; 22 | 8D660A161D60AC1A00F3D26B /* ContactsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D660A0C1D60AC1A00F3D26B /* ContactsViewController.swift */; }; 23 | 8D660A1C1D61158000F3D26B /* Array+Insertion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D660A1B1D61158000F3D26B /* Array+Insertion.swift */; }; 24 | 8DF7220F1D7FA20B007F6129 /* ContactViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DF7220E1D7FA20B007F6129 /* ContactViewModel.swift */; }; 25 | /* End PBXBuildFile section */ 26 | 27 | /* Begin PBXFileReference section */ 28 | 8D24E8411D766F79006CB8CB /* ContactLocalDataManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactLocalDataManager.swift; sourceTree = ""; }; 29 | 8D24E8431D766F79006CB8CB /* CoreDataStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreDataStore.swift; sourceTree = ""; }; 30 | 8D24E8491D766FDE006CB8CB /* Contact+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Contact+CoreDataProperties.swift"; sourceTree = ""; }; 31 | 8D24E84D1D767228006CB8CB /* MVVM_Contacts.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MVVM_Contacts.xcdatamodel; sourceTree = ""; }; 32 | 8D24E8511D7E50A5006CB8CB /* ContactViewModelController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactViewModelController.swift; sourceTree = ""; }; 33 | 8D24E8531D7E5711006CB8CB /* ContactsTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactsTableViewCell.swift; sourceTree = ""; }; 34 | 8D660A031D60AC1A00F3D26B /* AddContactViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddContactViewController.swift; sourceTree = ""; }; 35 | 8D660A051D60AC1A00F3D26B /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 36 | 8D660A061D60AC1A00F3D26B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 37 | 8D660A081D60AC1A00F3D26B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 38 | 8D660A0A1D60AC1A00F3D26B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 39 | 8D660A0B1D60AC1A00F3D26B /* Contact.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Contact.swift; sourceTree = ""; }; 40 | 8D660A0C1D60AC1A00F3D26B /* ContactsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactsViewController.swift; sourceTree = ""; }; 41 | 8D660A0E1D60AC1A00F3D26B /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 42 | 8D660A1B1D61158000F3D26B /* Array+Insertion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Array+Insertion.swift"; sourceTree = ""; }; 43 | 8D676C7E1D5FBB3300F3B088 /* MVVM Contacts Final.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "MVVM Contacts Final.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | 8DF7220E1D7FA20B007F6129 /* ContactViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactViewModel.swift; sourceTree = ""; }; 45 | /* End PBXFileReference section */ 46 | 47 | /* Begin PBXFrameworksBuildPhase section */ 48 | 8D676C7B1D5FBB3200F3B088 /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | ); 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | /* End PBXFrameworksBuildPhase section */ 56 | 57 | /* Begin PBXGroup section */ 58 | 8D24E84B1D766FE5006CB8CB /* Contact Model */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 8D24E8491D766FDE006CB8CB /* Contact+CoreDataProperties.swift */, 62 | 8D660A0B1D60AC1A00F3D26B /* Contact.swift */, 63 | ); 64 | name = "Contact Model"; 65 | sourceTree = ""; 66 | }; 67 | 8D660A021D60AC1A00F3D26B /* MVVM Contacts Final */ = { 68 | isa = PBXGroup; 69 | children = ( 70 | 8DF722261D804809007F6129 /* DataManager */, 71 | 8DF7220D1D7FA1F9007F6129 /* Contact ViewModel */, 72 | 8D24E84B1D766FE5006CB8CB /* Contact Model */, 73 | 8D660A1A1D60ADDB00F3D26B /* Contacts View */, 74 | 8D660A191D60ADD100F3D26B /* AddContact View */, 75 | 8D660A051D60AC1A00F3D26B /* AppDelegate.swift */, 76 | 8D660A061D60AC1A00F3D26B /* Assets.xcassets */, 77 | 8D660A071D60AC1A00F3D26B /* LaunchScreen.storyboard */, 78 | 8D660A091D60AC1A00F3D26B /* Main.storyboard */, 79 | 8D660A0E1D60AC1A00F3D26B /* Info.plist */, 80 | 8D660A1B1D61158000F3D26B /* Array+Insertion.swift */, 81 | ); 82 | path = "MVVM Contacts Final"; 83 | sourceTree = ""; 84 | }; 85 | 8D660A191D60ADD100F3D26B /* AddContact View */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | 8D660A031D60AC1A00F3D26B /* AddContactViewController.swift */, 89 | ); 90 | name = "AddContact View"; 91 | sourceTree = ""; 92 | }; 93 | 8D660A1A1D60ADDB00F3D26B /* Contacts View */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | 8D660A0C1D60AC1A00F3D26B /* ContactsViewController.swift */, 97 | 8D24E8531D7E5711006CB8CB /* ContactsTableViewCell.swift */, 98 | ); 99 | name = "Contacts View"; 100 | sourceTree = ""; 101 | }; 102 | 8D676C751D5FBB3200F3B088 = { 103 | isa = PBXGroup; 104 | children = ( 105 | 8D660A021D60AC1A00F3D26B /* MVVM Contacts Final */, 106 | 8D676C7F1D5FBB3300F3B088 /* Products */, 107 | ); 108 | sourceTree = ""; 109 | }; 110 | 8D676C7F1D5FBB3300F3B088 /* Products */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | 8D676C7E1D5FBB3300F3B088 /* MVVM Contacts Final.app */, 114 | ); 115 | name = Products; 116 | sourceTree = ""; 117 | }; 118 | 8DF7220D1D7FA1F9007F6129 /* Contact ViewModel */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | 8D24E8511D7E50A5006CB8CB /* ContactViewModelController.swift */, 122 | 8DF7220E1D7FA20B007F6129 /* ContactViewModel.swift */, 123 | ); 124 | name = "Contact ViewModel"; 125 | sourceTree = ""; 126 | }; 127 | 8DF722261D804809007F6129 /* DataManager */ = { 128 | isa = PBXGroup; 129 | children = ( 130 | 8D24E8411D766F79006CB8CB /* ContactLocalDataManager.swift */, 131 | 8D24E8431D766F79006CB8CB /* CoreDataStore.swift */, 132 | 8D24E84C1D767228006CB8CB /* MVVM_Contacts.xcdatamodeld */, 133 | ); 134 | name = DataManager; 135 | sourceTree = ""; 136 | }; 137 | /* End PBXGroup section */ 138 | 139 | /* Begin PBXNativeTarget section */ 140 | 8D676C7D1D5FBB3200F3B088 /* MVVM Contacts Final */ = { 141 | isa = PBXNativeTarget; 142 | buildConfigurationList = 8D676C901D5FBB3300F3B088 /* Build configuration list for PBXNativeTarget "MVVM Contacts Final" */; 143 | buildPhases = ( 144 | 8D676C7A1D5FBB3200F3B088 /* Sources */, 145 | 8D676C7B1D5FBB3200F3B088 /* Frameworks */, 146 | 8D676C7C1D5FBB3200F3B088 /* Resources */, 147 | ); 148 | buildRules = ( 149 | ); 150 | dependencies = ( 151 | ); 152 | name = "MVVM Contacts Final"; 153 | productName = "MVVM Contacts Starter"; 154 | productReference = 8D676C7E1D5FBB3300F3B088 /* MVVM Contacts Final.app */; 155 | productType = "com.apple.product-type.application"; 156 | }; 157 | /* End PBXNativeTarget section */ 158 | 159 | /* Begin PBXProject section */ 160 | 8D676C761D5FBB3200F3B088 /* Project object */ = { 161 | isa = PBXProject; 162 | attributes = { 163 | LastSwiftUpdateCheck = 0730; 164 | LastUpgradeCheck = 0800; 165 | ORGANIZATIONNAME = "Rafael Sacchi"; 166 | TargetAttributes = { 167 | 8D676C7D1D5FBB3200F3B088 = { 168 | CreatedOnToolsVersion = 7.3.1; 169 | DevelopmentTeam = JK33ZA2AUD; 170 | LastSwiftMigration = 0800; 171 | }; 172 | }; 173 | }; 174 | buildConfigurationList = 8D676C791D5FBB3200F3B088 /* Build configuration list for PBXProject "MVVM Contacts Final" */; 175 | compatibilityVersion = "Xcode 3.2"; 176 | developmentRegion = English; 177 | hasScannedForEncodings = 0; 178 | knownRegions = ( 179 | en, 180 | Base, 181 | ); 182 | mainGroup = 8D676C751D5FBB3200F3B088; 183 | productRefGroup = 8D676C7F1D5FBB3300F3B088 /* Products */; 184 | projectDirPath = ""; 185 | projectRoot = ""; 186 | targets = ( 187 | 8D676C7D1D5FBB3200F3B088 /* MVVM Contacts Final */, 188 | ); 189 | }; 190 | /* End PBXProject section */ 191 | 192 | /* Begin PBXResourcesBuildPhase section */ 193 | 8D676C7C1D5FBB3200F3B088 /* Resources */ = { 194 | isa = PBXResourcesBuildPhase; 195 | buildActionMask = 2147483647; 196 | files = ( 197 | 8D660A141D60AC1A00F3D26B /* Main.storyboard in Resources */, 198 | 8D660A121D60AC1A00F3D26B /* Assets.xcassets in Resources */, 199 | 8D660A131D60AC1A00F3D26B /* LaunchScreen.storyboard in Resources */, 200 | ); 201 | runOnlyForDeploymentPostprocessing = 0; 202 | }; 203 | /* End PBXResourcesBuildPhase section */ 204 | 205 | /* Begin PBXSourcesBuildPhase section */ 206 | 8D676C7A1D5FBB3200F3B088 /* Sources */ = { 207 | isa = PBXSourcesBuildPhase; 208 | buildActionMask = 2147483647; 209 | files = ( 210 | 8D24E8541D7E5711006CB8CB /* ContactsTableViewCell.swift in Sources */, 211 | 8DF7220F1D7FA20B007F6129 /* ContactViewModel.swift in Sources */, 212 | 8D24E8441D766F79006CB8CB /* ContactLocalDataManager.swift in Sources */, 213 | 8D660A1C1D61158000F3D26B /* Array+Insertion.swift in Sources */, 214 | 8D24E84E1D767228006CB8CB /* MVVM_Contacts.xcdatamodeld in Sources */, 215 | 8D24E8521D7E50A5006CB8CB /* ContactViewModelController.swift in Sources */, 216 | 8D660A151D60AC1A00F3D26B /* Contact.swift in Sources */, 217 | 8D24E8461D766F79006CB8CB /* CoreDataStore.swift in Sources */, 218 | 8D660A111D60AC1A00F3D26B /* AppDelegate.swift in Sources */, 219 | 8D24E84A1D766FDE006CB8CB /* Contact+CoreDataProperties.swift in Sources */, 220 | 8D660A0F1D60AC1A00F3D26B /* AddContactViewController.swift in Sources */, 221 | 8D660A161D60AC1A00F3D26B /* ContactsViewController.swift in Sources */, 222 | ); 223 | runOnlyForDeploymentPostprocessing = 0; 224 | }; 225 | /* End PBXSourcesBuildPhase section */ 226 | 227 | /* Begin PBXVariantGroup section */ 228 | 8D660A071D60AC1A00F3D26B /* LaunchScreen.storyboard */ = { 229 | isa = PBXVariantGroup; 230 | children = ( 231 | 8D660A081D60AC1A00F3D26B /* Base */, 232 | ); 233 | name = LaunchScreen.storyboard; 234 | sourceTree = ""; 235 | }; 236 | 8D660A091D60AC1A00F3D26B /* Main.storyboard */ = { 237 | isa = PBXVariantGroup; 238 | children = ( 239 | 8D660A0A1D60AC1A00F3D26B /* Base */, 240 | ); 241 | name = Main.storyboard; 242 | sourceTree = ""; 243 | }; 244 | /* End PBXVariantGroup section */ 245 | 246 | /* Begin XCBuildConfiguration section */ 247 | 8D676C8E1D5FBB3300F3B088 /* Debug */ = { 248 | isa = XCBuildConfiguration; 249 | buildSettings = { 250 | ALWAYS_SEARCH_USER_PATHS = NO; 251 | CLANG_ANALYZER_NONNULL = YES; 252 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 253 | CLANG_CXX_LIBRARY = "libc++"; 254 | CLANG_ENABLE_MODULES = YES; 255 | CLANG_ENABLE_OBJC_ARC = YES; 256 | CLANG_WARN_BOOL_CONVERSION = YES; 257 | CLANG_WARN_CONSTANT_CONVERSION = YES; 258 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 259 | CLANG_WARN_EMPTY_BODY = YES; 260 | CLANG_WARN_ENUM_CONVERSION = YES; 261 | CLANG_WARN_INFINITE_RECURSION = YES; 262 | CLANG_WARN_INT_CONVERSION = YES; 263 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 264 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 265 | CLANG_WARN_UNREACHABLE_CODE = YES; 266 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 267 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 268 | COPY_PHASE_STRIP = NO; 269 | DEBUG_INFORMATION_FORMAT = dwarf; 270 | ENABLE_STRICT_OBJC_MSGSEND = YES; 271 | ENABLE_TESTABILITY = YES; 272 | GCC_C_LANGUAGE_STANDARD = gnu99; 273 | GCC_DYNAMIC_NO_PIC = NO; 274 | GCC_NO_COMMON_BLOCKS = YES; 275 | GCC_OPTIMIZATION_LEVEL = 0; 276 | GCC_PREPROCESSOR_DEFINITIONS = ( 277 | "DEBUG=1", 278 | "$(inherited)", 279 | ); 280 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 281 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 282 | GCC_WARN_UNDECLARED_SELECTOR = YES; 283 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 284 | GCC_WARN_UNUSED_FUNCTION = YES; 285 | GCC_WARN_UNUSED_VARIABLE = YES; 286 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 287 | MTL_ENABLE_DEBUG_INFO = YES; 288 | ONLY_ACTIVE_ARCH = YES; 289 | SDKROOT = iphoneos; 290 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 291 | }; 292 | name = Debug; 293 | }; 294 | 8D676C8F1D5FBB3300F3B088 /* Release */ = { 295 | isa = XCBuildConfiguration; 296 | buildSettings = { 297 | ALWAYS_SEARCH_USER_PATHS = NO; 298 | CLANG_ANALYZER_NONNULL = YES; 299 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 300 | CLANG_CXX_LIBRARY = "libc++"; 301 | CLANG_ENABLE_MODULES = YES; 302 | CLANG_ENABLE_OBJC_ARC = YES; 303 | CLANG_WARN_BOOL_CONVERSION = YES; 304 | CLANG_WARN_CONSTANT_CONVERSION = YES; 305 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 306 | CLANG_WARN_EMPTY_BODY = YES; 307 | CLANG_WARN_ENUM_CONVERSION = YES; 308 | CLANG_WARN_INFINITE_RECURSION = YES; 309 | CLANG_WARN_INT_CONVERSION = YES; 310 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 311 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 312 | CLANG_WARN_UNREACHABLE_CODE = YES; 313 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 314 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 315 | COPY_PHASE_STRIP = NO; 316 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 317 | ENABLE_NS_ASSERTIONS = NO; 318 | ENABLE_STRICT_OBJC_MSGSEND = YES; 319 | GCC_C_LANGUAGE_STANDARD = gnu99; 320 | GCC_NO_COMMON_BLOCKS = YES; 321 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 322 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 323 | GCC_WARN_UNDECLARED_SELECTOR = YES; 324 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 325 | GCC_WARN_UNUSED_FUNCTION = YES; 326 | GCC_WARN_UNUSED_VARIABLE = YES; 327 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 328 | MTL_ENABLE_DEBUG_INFO = NO; 329 | SDKROOT = iphoneos; 330 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 331 | VALIDATE_PRODUCT = YES; 332 | }; 333 | name = Release; 334 | }; 335 | 8D676C911D5FBB3300F3B088 /* Debug */ = { 336 | isa = XCBuildConfiguration; 337 | buildSettings = { 338 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 339 | DEVELOPMENT_TEAM = JK33ZA2AUD; 340 | INFOPLIST_FILE = "$(SRCROOT)/MVVM Contacts Final/Info.plist"; 341 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 342 | PRODUCT_BUNDLE_IDENTIFIER = "com.rsacchi.MVVM-Contacts-Final"; 343 | PRODUCT_NAME = "MVVM Contacts Final"; 344 | SWIFT_VERSION = 3.0; 345 | }; 346 | name = Debug; 347 | }; 348 | 8D676C921D5FBB3300F3B088 /* Release */ = { 349 | isa = XCBuildConfiguration; 350 | buildSettings = { 351 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 352 | DEVELOPMENT_TEAM = JK33ZA2AUD; 353 | INFOPLIST_FILE = "$(SRCROOT)/MVVM Contacts Final/Info.plist"; 354 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 355 | PRODUCT_BUNDLE_IDENTIFIER = "com.rsacchi.MVVM-Contacts-Final"; 356 | PRODUCT_NAME = "MVVM Contacts Final"; 357 | SWIFT_VERSION = 3.0; 358 | }; 359 | name = Release; 360 | }; 361 | /* End XCBuildConfiguration section */ 362 | 363 | /* Begin XCConfigurationList section */ 364 | 8D676C791D5FBB3200F3B088 /* Build configuration list for PBXProject "MVVM Contacts Final" */ = { 365 | isa = XCConfigurationList; 366 | buildConfigurations = ( 367 | 8D676C8E1D5FBB3300F3B088 /* Debug */, 368 | 8D676C8F1D5FBB3300F3B088 /* Release */, 369 | ); 370 | defaultConfigurationIsVisible = 0; 371 | defaultConfigurationName = Release; 372 | }; 373 | 8D676C901D5FBB3300F3B088 /* Build configuration list for PBXNativeTarget "MVVM Contacts Final" */ = { 374 | isa = XCConfigurationList; 375 | buildConfigurations = ( 376 | 8D676C911D5FBB3300F3B088 /* Debug */, 377 | 8D676C921D5FBB3300F3B088 /* Release */, 378 | ); 379 | defaultConfigurationIsVisible = 0; 380 | defaultConfigurationName = Release; 381 | }; 382 | /* End XCConfigurationList section */ 383 | 384 | /* Begin XCVersionGroup section */ 385 | 8D24E84C1D767228006CB8CB /* MVVM_Contacts.xcdatamodeld */ = { 386 | isa = XCVersionGroup; 387 | children = ( 388 | 8D24E84D1D767228006CB8CB /* MVVM_Contacts.xcdatamodel */, 389 | ); 390 | currentVersion = 8D24E84D1D767228006CB8CB /* MVVM_Contacts.xcdatamodel */; 391 | path = MVVM_Contacts.xcdatamodeld; 392 | sourceTree = ""; 393 | versionGroupType = wrapper.xcdatamodel; 394 | }; 395 | /* End XCVersionGroup section */ 396 | }; 397 | rootObject = 8D676C761D5FBB3200F3B088 /* Project object */; 398 | } 399 | -------------------------------------------------------------------------------- /MVVM Contacts Starter/MVVM Contacts Starter.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 8D6609E11D5FCCE900F3D26B /* AddContactViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D6609DB1D5FCCE900F3D26B /* AddContactViewController.swift */; }; 11 | 8D660A1E1D61566B00F3D26B /* Array+Insertion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D660A1D1D61566B00F3D26B /* Array+Insertion.swift */; }; 12 | 8D676C821D5FBB3300F3B088 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D676C811D5FBB3300F3B088 /* AppDelegate.swift */; }; 13 | 8D676C841D5FBB3300F3B088 /* ContactsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D676C831D5FBB3300F3B088 /* ContactsViewController.swift */; }; 14 | 8D676C871D5FBB3300F3B088 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8D676C851D5FBB3300F3B088 /* Main.storyboard */; }; 15 | 8D676C891D5FBB3300F3B088 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8D676C881D5FBB3300F3B088 /* Assets.xcassets */; }; 16 | 8D676C8C1D5FBB3300F3B088 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8D676C8A1D5FBB3300F3B088 /* LaunchScreen.storyboard */; }; 17 | 8DF722141D804075007F6129 /* ContactViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DF722121D804075007F6129 /* ContactViewModel.swift */; }; 18 | 8DF722161D804091007F6129 /* ContactViewModelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DF722151D804091007F6129 /* ContactViewModelController.swift */; }; 19 | 8DF722181D80409B007F6129 /* CoreDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DF722171D80409B007F6129 /* CoreDataStore.swift */; }; 20 | 8DF7221C1D8040BC007F6129 /* Contact.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DF7221A1D8040BC007F6129 /* Contact.swift */; }; 21 | 8DF7221D1D8040BC007F6129 /* Contact+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DF7221B1D8040BC007F6129 /* Contact+CoreDataProperties.swift */; }; 22 | 8DF722201D8040D9007F6129 /* ContactLocalDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DF7221F1D8040D9007F6129 /* ContactLocalDataManager.swift */; }; 23 | 8DF722221D8040EF007F6129 /* ContactsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8DF722211D8040EF007F6129 /* ContactsTableViewCell.swift */; }; 24 | 8DF722251D804116007F6129 /* MVVM_Contacts.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 8DF722231D804116007F6129 /* MVVM_Contacts.xcdatamodeld */; }; 25 | /* End PBXBuildFile section */ 26 | 27 | /* Begin PBXFileReference section */ 28 | 8D6609DB1D5FCCE900F3D26B /* AddContactViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddContactViewController.swift; sourceTree = ""; }; 29 | 8D660A1D1D61566B00F3D26B /* Array+Insertion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Array+Insertion.swift"; sourceTree = ""; }; 30 | 8D676C7E1D5FBB3300F3B088 /* MVVM Contacts Starter.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "MVVM Contacts Starter.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 31 | 8D676C811D5FBB3300F3B088 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 32 | 8D676C831D5FBB3300F3B088 /* ContactsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsViewController.swift; sourceTree = ""; }; 33 | 8D676C861D5FBB3300F3B088 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 34 | 8D676C881D5FBB3300F3B088 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 35 | 8D676C8B1D5FBB3300F3B088 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 36 | 8D676C8D1D5FBB3300F3B088 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 37 | 8DF722121D804075007F6129 /* ContactViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactViewModel.swift; sourceTree = ""; }; 38 | 8DF722151D804091007F6129 /* ContactViewModelController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactViewModelController.swift; sourceTree = ""; }; 39 | 8DF722171D80409B007F6129 /* CoreDataStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreDataStore.swift; sourceTree = ""; }; 40 | 8DF7221A1D8040BC007F6129 /* Contact.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Contact.swift; sourceTree = ""; }; 41 | 8DF7221B1D8040BC007F6129 /* Contact+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Contact+CoreDataProperties.swift"; sourceTree = ""; }; 42 | 8DF7221F1D8040D9007F6129 /* ContactLocalDataManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactLocalDataManager.swift; sourceTree = ""; }; 43 | 8DF722211D8040EF007F6129 /* ContactsTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactsTableViewCell.swift; sourceTree = ""; }; 44 | 8DF722241D804116007F6129 /* MVVM_Contacts.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MVVM_Contacts.xcdatamodel; sourceTree = ""; }; 45 | /* End PBXFileReference section */ 46 | 47 | /* Begin PBXFrameworksBuildPhase section */ 48 | 8D676C7B1D5FBB3200F3B088 /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | ); 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | /* End PBXFrameworksBuildPhase section */ 56 | 57 | /* Begin PBXGroup section */ 58 | 8D6609D71D5FCCBD00F3D26B /* ContactList View */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 8DF722211D8040EF007F6129 /* ContactsTableViewCell.swift */, 62 | 8D676C831D5FBB3300F3B088 /* ContactsViewController.swift */, 63 | ); 64 | name = "ContactList View"; 65 | sourceTree = ""; 66 | }; 67 | 8D6609D81D5FCCC400F3D26B /* AddContact View */ = { 68 | isa = PBXGroup; 69 | children = ( 70 | 8D6609DB1D5FCCE900F3D26B /* AddContactViewController.swift */, 71 | ); 72 | name = "AddContact View"; 73 | sourceTree = ""; 74 | }; 75 | 8D676C751D5FBB3200F3B088 = { 76 | isa = PBXGroup; 77 | children = ( 78 | 8D676C801D5FBB3300F3B088 /* MVVM Contacts Starter */, 79 | 8D676C7F1D5FBB3300F3B088 /* Products */, 80 | ); 81 | sourceTree = ""; 82 | }; 83 | 8D676C7F1D5FBB3300F3B088 /* Products */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | 8D676C7E1D5FBB3300F3B088 /* MVVM Contacts Starter.app */, 87 | ); 88 | name = Products; 89 | sourceTree = ""; 90 | }; 91 | 8D676C801D5FBB3300F3B088 /* MVVM Contacts Starter */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | 8DF7221E1D8040CD007F6129 /* Data Manager */, 95 | 8DF722191D8040A1007F6129 /* Contact Model */, 96 | 8DF722101D80405D007F6129 /* Contact ViewModel */, 97 | 8D6609D81D5FCCC400F3D26B /* AddContact View */, 98 | 8D6609D71D5FCCBD00F3D26B /* ContactList View */, 99 | 8D660A1D1D61566B00F3D26B /* Array+Insertion.swift */, 100 | 8D676C811D5FBB3300F3B088 /* AppDelegate.swift */, 101 | 8D676C851D5FBB3300F3B088 /* Main.storyboard */, 102 | 8D676C881D5FBB3300F3B088 /* Assets.xcassets */, 103 | 8D676C8A1D5FBB3300F3B088 /* LaunchScreen.storyboard */, 104 | 8D676C8D1D5FBB3300F3B088 /* Info.plist */, 105 | ); 106 | path = "MVVM Contacts Starter"; 107 | sourceTree = ""; 108 | }; 109 | 8DF722101D80405D007F6129 /* Contact ViewModel */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | 8DF722151D804091007F6129 /* ContactViewModelController.swift */, 113 | 8DF722121D804075007F6129 /* ContactViewModel.swift */, 114 | ); 115 | name = "Contact ViewModel"; 116 | sourceTree = ""; 117 | }; 118 | 8DF722191D8040A1007F6129 /* Contact Model */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | 8DF7221A1D8040BC007F6129 /* Contact.swift */, 122 | 8DF7221B1D8040BC007F6129 /* Contact+CoreDataProperties.swift */, 123 | ); 124 | name = "Contact Model"; 125 | sourceTree = ""; 126 | }; 127 | 8DF7221E1D8040CD007F6129 /* Data Manager */ = { 128 | isa = PBXGroup; 129 | children = ( 130 | 8DF722231D804116007F6129 /* MVVM_Contacts.xcdatamodeld */, 131 | 8DF7221F1D8040D9007F6129 /* ContactLocalDataManager.swift */, 132 | 8DF722171D80409B007F6129 /* CoreDataStore.swift */, 133 | ); 134 | name = "Data Manager"; 135 | sourceTree = ""; 136 | }; 137 | /* End PBXGroup section */ 138 | 139 | /* Begin PBXNativeTarget section */ 140 | 8D676C7D1D5FBB3200F3B088 /* MVVM Contacts Starter */ = { 141 | isa = PBXNativeTarget; 142 | buildConfigurationList = 8D676C901D5FBB3300F3B088 /* Build configuration list for PBXNativeTarget "MVVM Contacts Starter" */; 143 | buildPhases = ( 144 | 8D676C7A1D5FBB3200F3B088 /* Sources */, 145 | 8D676C7B1D5FBB3200F3B088 /* Frameworks */, 146 | 8D676C7C1D5FBB3200F3B088 /* Resources */, 147 | ); 148 | buildRules = ( 149 | ); 150 | dependencies = ( 151 | ); 152 | name = "MVVM Contacts Starter"; 153 | productName = "MVVM Contacts Starter"; 154 | productReference = 8D676C7E1D5FBB3300F3B088 /* MVVM Contacts Starter.app */; 155 | productType = "com.apple.product-type.application"; 156 | }; 157 | /* End PBXNativeTarget section */ 158 | 159 | /* Begin PBXProject section */ 160 | 8D676C761D5FBB3200F3B088 /* Project object */ = { 161 | isa = PBXProject; 162 | attributes = { 163 | LastSwiftUpdateCheck = 0730; 164 | LastUpgradeCheck = 0800; 165 | ORGANIZATIONNAME = "Rafael Sacchi"; 166 | TargetAttributes = { 167 | 8D676C7D1D5FBB3200F3B088 = { 168 | CreatedOnToolsVersion = 7.3.1; 169 | DevelopmentTeam = JK33ZA2AUD; 170 | LastSwiftMigration = 0800; 171 | }; 172 | }; 173 | }; 174 | buildConfigurationList = 8D676C791D5FBB3200F3B088 /* Build configuration list for PBXProject "MVVM Contacts Starter" */; 175 | compatibilityVersion = "Xcode 3.2"; 176 | developmentRegion = English; 177 | hasScannedForEncodings = 0; 178 | knownRegions = ( 179 | en, 180 | Base, 181 | ); 182 | mainGroup = 8D676C751D5FBB3200F3B088; 183 | productRefGroup = 8D676C7F1D5FBB3300F3B088 /* Products */; 184 | projectDirPath = ""; 185 | projectRoot = ""; 186 | targets = ( 187 | 8D676C7D1D5FBB3200F3B088 /* MVVM Contacts Starter */, 188 | ); 189 | }; 190 | /* End PBXProject section */ 191 | 192 | /* Begin PBXResourcesBuildPhase section */ 193 | 8D676C7C1D5FBB3200F3B088 /* Resources */ = { 194 | isa = PBXResourcesBuildPhase; 195 | buildActionMask = 2147483647; 196 | files = ( 197 | 8D676C8C1D5FBB3300F3B088 /* LaunchScreen.storyboard in Resources */, 198 | 8D676C891D5FBB3300F3B088 /* Assets.xcassets in Resources */, 199 | 8D676C871D5FBB3300F3B088 /* Main.storyboard in Resources */, 200 | ); 201 | runOnlyForDeploymentPostprocessing = 0; 202 | }; 203 | /* End PBXResourcesBuildPhase section */ 204 | 205 | /* Begin PBXSourcesBuildPhase section */ 206 | 8D676C7A1D5FBB3200F3B088 /* Sources */ = { 207 | isa = PBXSourcesBuildPhase; 208 | buildActionMask = 2147483647; 209 | files = ( 210 | 8DF722221D8040EF007F6129 /* ContactsTableViewCell.swift in Sources */, 211 | 8D660A1E1D61566B00F3D26B /* Array+Insertion.swift in Sources */, 212 | 8DF722251D804116007F6129 /* MVVM_Contacts.xcdatamodeld in Sources */, 213 | 8D6609E11D5FCCE900F3D26B /* AddContactViewController.swift in Sources */, 214 | 8DF722161D804091007F6129 /* ContactViewModelController.swift in Sources */, 215 | 8DF7221D1D8040BC007F6129 /* Contact+CoreDataProperties.swift in Sources */, 216 | 8DF722201D8040D9007F6129 /* ContactLocalDataManager.swift in Sources */, 217 | 8D676C841D5FBB3300F3B088 /* ContactsViewController.swift in Sources */, 218 | 8DF7221C1D8040BC007F6129 /* Contact.swift in Sources */, 219 | 8DF722141D804075007F6129 /* ContactViewModel.swift in Sources */, 220 | 8DF722181D80409B007F6129 /* CoreDataStore.swift in Sources */, 221 | 8D676C821D5FBB3300F3B088 /* AppDelegate.swift in Sources */, 222 | ); 223 | runOnlyForDeploymentPostprocessing = 0; 224 | }; 225 | /* End PBXSourcesBuildPhase section */ 226 | 227 | /* Begin PBXVariantGroup section */ 228 | 8D676C851D5FBB3300F3B088 /* Main.storyboard */ = { 229 | isa = PBXVariantGroup; 230 | children = ( 231 | 8D676C861D5FBB3300F3B088 /* Base */, 232 | ); 233 | name = Main.storyboard; 234 | sourceTree = ""; 235 | }; 236 | 8D676C8A1D5FBB3300F3B088 /* LaunchScreen.storyboard */ = { 237 | isa = PBXVariantGroup; 238 | children = ( 239 | 8D676C8B1D5FBB3300F3B088 /* Base */, 240 | ); 241 | name = LaunchScreen.storyboard; 242 | sourceTree = ""; 243 | }; 244 | /* End PBXVariantGroup section */ 245 | 246 | /* Begin XCBuildConfiguration section */ 247 | 8D676C8E1D5FBB3300F3B088 /* Debug */ = { 248 | isa = XCBuildConfiguration; 249 | buildSettings = { 250 | ALWAYS_SEARCH_USER_PATHS = NO; 251 | CLANG_ANALYZER_NONNULL = YES; 252 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 253 | CLANG_CXX_LIBRARY = "libc++"; 254 | CLANG_ENABLE_MODULES = YES; 255 | CLANG_ENABLE_OBJC_ARC = YES; 256 | CLANG_WARN_BOOL_CONVERSION = YES; 257 | CLANG_WARN_CONSTANT_CONVERSION = YES; 258 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 259 | CLANG_WARN_EMPTY_BODY = YES; 260 | CLANG_WARN_ENUM_CONVERSION = YES; 261 | CLANG_WARN_INFINITE_RECURSION = YES; 262 | CLANG_WARN_INT_CONVERSION = YES; 263 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 264 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 265 | CLANG_WARN_UNREACHABLE_CODE = YES; 266 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 267 | CODE_SIGN_IDENTITY = "iPhone Developer"; 268 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 269 | COPY_PHASE_STRIP = NO; 270 | DEBUG_INFORMATION_FORMAT = dwarf; 271 | ENABLE_STRICT_OBJC_MSGSEND = YES; 272 | ENABLE_TESTABILITY = YES; 273 | GCC_C_LANGUAGE_STANDARD = gnu99; 274 | GCC_DYNAMIC_NO_PIC = NO; 275 | GCC_NO_COMMON_BLOCKS = YES; 276 | GCC_OPTIMIZATION_LEVEL = 0; 277 | GCC_PREPROCESSOR_DEFINITIONS = ( 278 | "DEBUG=1", 279 | "$(inherited)", 280 | ); 281 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 282 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 283 | GCC_WARN_UNDECLARED_SELECTOR = YES; 284 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 285 | GCC_WARN_UNUSED_FUNCTION = YES; 286 | GCC_WARN_UNUSED_VARIABLE = YES; 287 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 288 | MTL_ENABLE_DEBUG_INFO = YES; 289 | ONLY_ACTIVE_ARCH = YES; 290 | SDKROOT = iphoneos; 291 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 292 | }; 293 | name = Debug; 294 | }; 295 | 8D676C8F1D5FBB3300F3B088 /* Release */ = { 296 | isa = XCBuildConfiguration; 297 | buildSettings = { 298 | ALWAYS_SEARCH_USER_PATHS = NO; 299 | CLANG_ANALYZER_NONNULL = YES; 300 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 301 | CLANG_CXX_LIBRARY = "libc++"; 302 | CLANG_ENABLE_MODULES = YES; 303 | CLANG_ENABLE_OBJC_ARC = YES; 304 | CLANG_WARN_BOOL_CONVERSION = YES; 305 | CLANG_WARN_CONSTANT_CONVERSION = YES; 306 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 307 | CLANG_WARN_EMPTY_BODY = YES; 308 | CLANG_WARN_ENUM_CONVERSION = YES; 309 | CLANG_WARN_INFINITE_RECURSION = YES; 310 | CLANG_WARN_INT_CONVERSION = YES; 311 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 312 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 313 | CLANG_WARN_UNREACHABLE_CODE = YES; 314 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 315 | CODE_SIGN_IDENTITY = "iPhone Developer"; 316 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 317 | COPY_PHASE_STRIP = NO; 318 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 319 | ENABLE_NS_ASSERTIONS = NO; 320 | ENABLE_STRICT_OBJC_MSGSEND = YES; 321 | GCC_C_LANGUAGE_STANDARD = gnu99; 322 | GCC_NO_COMMON_BLOCKS = YES; 323 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 324 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 325 | GCC_WARN_UNDECLARED_SELECTOR = YES; 326 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 327 | GCC_WARN_UNUSED_FUNCTION = YES; 328 | GCC_WARN_UNUSED_VARIABLE = YES; 329 | IPHONEOS_DEPLOYMENT_TARGET = 9.3; 330 | MTL_ENABLE_DEBUG_INFO = NO; 331 | SDKROOT = iphoneos; 332 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 333 | VALIDATE_PRODUCT = YES; 334 | }; 335 | name = Release; 336 | }; 337 | 8D676C911D5FBB3300F3B088 /* Debug */ = { 338 | isa = XCBuildConfiguration; 339 | buildSettings = { 340 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 341 | DEVELOPMENT_TEAM = JK33ZA2AUD; 342 | INFOPLIST_FILE = "MVVM Contacts Starter/Info.plist"; 343 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 344 | PRODUCT_BUNDLE_IDENTIFIER = "com.rsacchi.MVVM-Contacts-Starter"; 345 | PRODUCT_NAME = "$(TARGET_NAME)"; 346 | SWIFT_VERSION = 3.0; 347 | }; 348 | name = Debug; 349 | }; 350 | 8D676C921D5FBB3300F3B088 /* Release */ = { 351 | isa = XCBuildConfiguration; 352 | buildSettings = { 353 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 354 | DEVELOPMENT_TEAM = JK33ZA2AUD; 355 | INFOPLIST_FILE = "MVVM Contacts Starter/Info.plist"; 356 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 357 | PRODUCT_BUNDLE_IDENTIFIER = "com.rsacchi.MVVM-Contacts-Starter"; 358 | PRODUCT_NAME = "$(TARGET_NAME)"; 359 | SWIFT_VERSION = 3.0; 360 | }; 361 | name = Release; 362 | }; 363 | /* End XCBuildConfiguration section */ 364 | 365 | /* Begin XCConfigurationList section */ 366 | 8D676C791D5FBB3200F3B088 /* Build configuration list for PBXProject "MVVM Contacts Starter" */ = { 367 | isa = XCConfigurationList; 368 | buildConfigurations = ( 369 | 8D676C8E1D5FBB3300F3B088 /* Debug */, 370 | 8D676C8F1D5FBB3300F3B088 /* Release */, 371 | ); 372 | defaultConfigurationIsVisible = 0; 373 | defaultConfigurationName = Release; 374 | }; 375 | 8D676C901D5FBB3300F3B088 /* Build configuration list for PBXNativeTarget "MVVM Contacts Starter" */ = { 376 | isa = XCConfigurationList; 377 | buildConfigurations = ( 378 | 8D676C911D5FBB3300F3B088 /* Debug */, 379 | 8D676C921D5FBB3300F3B088 /* Release */, 380 | ); 381 | defaultConfigurationIsVisible = 0; 382 | defaultConfigurationName = Release; 383 | }; 384 | /* End XCConfigurationList section */ 385 | 386 | /* Begin XCVersionGroup section */ 387 | 8DF722231D804116007F6129 /* MVVM_Contacts.xcdatamodeld */ = { 388 | isa = XCVersionGroup; 389 | children = ( 390 | 8DF722241D804116007F6129 /* MVVM_Contacts.xcdatamodel */, 391 | ); 392 | currentVersion = 8DF722241D804116007F6129 /* MVVM_Contacts.xcdatamodel */; 393 | path = MVVM_Contacts.xcdatamodeld; 394 | sourceTree = ""; 395 | versionGroupType = wrapper.xcdatamodel; 396 | }; 397 | /* End XCVersionGroup section */ 398 | }; 399 | rootObject = 8D676C761D5FBB3200F3B088 /* Project object */; 400 | } 401 | -------------------------------------------------------------------------------- /MVVM Contacts Final/MVVM Contacts Final/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 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 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 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 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 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | -------------------------------------------------------------------------------- /VIPER Contacts Final/VIPER Contacts Final/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 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 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 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 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 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | -------------------------------------------------------------------------------- /MVVM Contacts Starter/MVVM Contacts Starter/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 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 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 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 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 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | --------------------------------------------------------------------------------