├── Notes ├── Resources │ └── Assets.xcassets │ │ ├── Contents.json │ │ ├── AppIcon.appiconset │ │ ├── Icon.png │ │ ├── Icon-40.png │ │ ├── Icon-72.png │ │ ├── Icon-76.png │ │ ├── Icon@2x.png │ │ ├── Icon-40@2x.png │ │ ├── Icon-40@3x.png │ │ ├── Icon-60@2x.png │ │ ├── Icon-60@3x.png │ │ ├── Icon-72@2x.png │ │ ├── Icon-76@2x.png │ │ ├── Icon-Small.png │ │ ├── Icon-83.5@2x.png │ │ ├── Icon-Small-50.png │ │ ├── Icon-Small@2x.png │ │ ├── Icon-Small@3x.png │ │ ├── Icon-Small-50@2x.png │ │ ├── NotificationIcon@2x.png │ │ ├── NotificationIcon@3x.png │ │ ├── NotificationIcon~ipad.png │ │ ├── NotificationIcon~ipad@2x.png │ │ └── Contents.json │ │ └── icon_notes.imageset │ │ ├── Icon-60@2x.png │ │ ├── Icon-60@3x.png │ │ ├── NotificationIcon@3x.png │ │ └── Contents.json ├── Core Data │ ├── Notes.xcdatamodeld │ │ ├── .xccurrentversion │ │ ├── Notes.xcdatamodel │ │ │ └── contents │ │ └── Notes 2.xcdatamodel │ │ │ └── contents │ └── Extensions │ │ ├── Category.swift │ │ └── Note.swift ├── Application Delegate │ └── AppDelegate.swift ├── View Controllers │ ├── Tags │ │ ├── Tags View Controller │ │ │ ├── Table View Cells │ │ │ │ └── TagTableViewCell.swift │ │ │ └── TagsViewController.swift │ │ ├── Tag View Controller │ │ │ └── TagViewController.swift │ │ └── Add Tag View Controller │ │ │ └── AddTagViewController.swift │ ├── Categories │ │ ├── Categories View Controller │ │ │ ├── Table View Cells │ │ │ │ └── CategoryTableViewCell.swift │ │ │ └── CategoriesViewController.swift │ │ ├── Add Category View Controller │ │ │ └── AddCategoryViewController.swift │ │ └── Category View Controller │ │ │ ├── CategoryViewController.swift │ │ │ └── Color View Controller │ │ │ └── ColorViewController.swift │ └── Notes │ │ ├── Notes View Controller │ │ ├── Table View Cells │ │ │ └── NoteTableViewCell.swift │ │ └── NotesViewController.swift │ │ ├── Add Note View Controller │ │ └── AddNoteViewController.swift │ │ └── Note View Controller │ │ └── NoteViewController.swift ├── Extensions │ ├── UIViewController.swift │ └── UIColor.swift ├── Supporting Files │ └── Info.plist ├── Storyboards │ ├── LaunchScreen.storyboard │ ├── Tags.storyboard │ ├── Categories.storyboard │ └── Notes.storyboard └── Managers │ └── CoreDataManager.swift ├── Notes.xcodeproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── project.pbxproj ├── README.md └── .gitignore /Notes/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Notes/Resources/Assets.xcassets/AppIcon.appiconset/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartjacobs/Notes/HEAD/Notes/Resources/Assets.xcassets/AppIcon.appiconset/Icon.png -------------------------------------------------------------------------------- /Notes/Resources/Assets.xcassets/AppIcon.appiconset/Icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartjacobs/Notes/HEAD/Notes/Resources/Assets.xcassets/AppIcon.appiconset/Icon-40.png -------------------------------------------------------------------------------- /Notes/Resources/Assets.xcassets/AppIcon.appiconset/Icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartjacobs/Notes/HEAD/Notes/Resources/Assets.xcassets/AppIcon.appiconset/Icon-72.png -------------------------------------------------------------------------------- /Notes/Resources/Assets.xcassets/AppIcon.appiconset/Icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartjacobs/Notes/HEAD/Notes/Resources/Assets.xcassets/AppIcon.appiconset/Icon-76.png -------------------------------------------------------------------------------- /Notes/Resources/Assets.xcassets/AppIcon.appiconset/Icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartjacobs/Notes/HEAD/Notes/Resources/Assets.xcassets/AppIcon.appiconset/Icon@2x.png -------------------------------------------------------------------------------- /Notes/Resources/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartjacobs/Notes/HEAD/Notes/Resources/Assets.xcassets/AppIcon.appiconset/Icon-40@2x.png -------------------------------------------------------------------------------- /Notes/Resources/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartjacobs/Notes/HEAD/Notes/Resources/Assets.xcassets/AppIcon.appiconset/Icon-40@3x.png -------------------------------------------------------------------------------- /Notes/Resources/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartjacobs/Notes/HEAD/Notes/Resources/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /Notes/Resources/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartjacobs/Notes/HEAD/Notes/Resources/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /Notes/Resources/Assets.xcassets/AppIcon.appiconset/Icon-72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartjacobs/Notes/HEAD/Notes/Resources/Assets.xcassets/AppIcon.appiconset/Icon-72@2x.png -------------------------------------------------------------------------------- /Notes/Resources/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartjacobs/Notes/HEAD/Notes/Resources/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png -------------------------------------------------------------------------------- /Notes/Resources/Assets.xcassets/AppIcon.appiconset/Icon-Small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartjacobs/Notes/HEAD/Notes/Resources/Assets.xcassets/AppIcon.appiconset/Icon-Small.png -------------------------------------------------------------------------------- /Notes/Resources/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartjacobs/Notes/HEAD/Notes/Resources/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png -------------------------------------------------------------------------------- /Notes/Resources/Assets.xcassets/AppIcon.appiconset/Icon-Small-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartjacobs/Notes/HEAD/Notes/Resources/Assets.xcassets/AppIcon.appiconset/Icon-Small-50.png -------------------------------------------------------------------------------- /Notes/Resources/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartjacobs/Notes/HEAD/Notes/Resources/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png -------------------------------------------------------------------------------- /Notes/Resources/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartjacobs/Notes/HEAD/Notes/Resources/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png -------------------------------------------------------------------------------- /Notes/Resources/Assets.xcassets/icon_notes.imageset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartjacobs/Notes/HEAD/Notes/Resources/Assets.xcassets/icon_notes.imageset/Icon-60@2x.png -------------------------------------------------------------------------------- /Notes/Resources/Assets.xcassets/icon_notes.imageset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartjacobs/Notes/HEAD/Notes/Resources/Assets.xcassets/icon_notes.imageset/Icon-60@3x.png -------------------------------------------------------------------------------- /Notes/Resources/Assets.xcassets/AppIcon.appiconset/Icon-Small-50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartjacobs/Notes/HEAD/Notes/Resources/Assets.xcassets/AppIcon.appiconset/Icon-Small-50@2x.png -------------------------------------------------------------------------------- /Notes/Resources/Assets.xcassets/AppIcon.appiconset/NotificationIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartjacobs/Notes/HEAD/Notes/Resources/Assets.xcassets/AppIcon.appiconset/NotificationIcon@2x.png -------------------------------------------------------------------------------- /Notes/Resources/Assets.xcassets/AppIcon.appiconset/NotificationIcon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartjacobs/Notes/HEAD/Notes/Resources/Assets.xcassets/AppIcon.appiconset/NotificationIcon@3x.png -------------------------------------------------------------------------------- /Notes/Resources/Assets.xcassets/icon_notes.imageset/NotificationIcon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartjacobs/Notes/HEAD/Notes/Resources/Assets.xcassets/icon_notes.imageset/NotificationIcon@3x.png -------------------------------------------------------------------------------- /Notes/Resources/Assets.xcassets/AppIcon.appiconset/NotificationIcon~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartjacobs/Notes/HEAD/Notes/Resources/Assets.xcassets/AppIcon.appiconset/NotificationIcon~ipad.png -------------------------------------------------------------------------------- /Notes/Resources/Assets.xcassets/AppIcon.appiconset/NotificationIcon~ipad@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartjacobs/Notes/HEAD/Notes/Resources/Assets.xcassets/AppIcon.appiconset/NotificationIcon~ipad@2x.png -------------------------------------------------------------------------------- /Notes.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Notes/Core Data/Notes.xcdatamodeld/.xccurrentversion: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | _XCCurrentVersionName 6 | Notes 2.xcdatamodel 7 | 8 | 9 | -------------------------------------------------------------------------------- /Notes/Resources/Assets.xcassets/icon_notes.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "filename" : "NotificationIcon@3x.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "filename" : "Icon-60@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "filename" : "Icon-60@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Notes/Core Data/Extensions/Category.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Category.swift 3 | // Notes 4 | // 5 | // Created by Bart Jacobs on 04/11/16. 6 | // Copyright © 2016 Cocoacasts. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Foundation 11 | 12 | extension Category { 13 | 14 | var color: UIColor? { 15 | get { 16 | guard let hex = colorAsHex else { return nil } 17 | return UIColor(hex: hex) 18 | } 19 | set(newColor) { 20 | if let newColor = newColor { 21 | colorAsHex = newColor.toHex 22 | } 23 | } 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /Notes/Application Delegate/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Notes 4 | // 5 | // Created by Bart Jacobs on 31/10/16. 6 | // Copyright © 2016 Cocoacasts. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | // MARK: - Properties 15 | 16 | var window: UIWindow? 17 | 18 | // MARK: - Application Life Cycle 19 | 20 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 21 | return true 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /Notes/View Controllers/Tags/Tags View Controller/Table View Cells/TagTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TagTableViewCell.swift 3 | // Notes 4 | // 5 | // Created by Bart Jacobs on 05/11/16. 6 | // Copyright © 2016 Cocoacasts. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class TagTableViewCell: UITableViewCell { 12 | 13 | // MARK: - Properties 14 | 15 | static let reuseIdentifier = "TagTableViewCell" 16 | 17 | // MARK: - 18 | 19 | @IBOutlet var nameLabel: UILabel! 20 | 21 | // MARK: - Initialization 22 | 23 | override func awakeFromNib() { 24 | super.awakeFromNib() 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /Notes/View Controllers/Categories/Categories View Controller/Table View Cells/CategoryTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CategoryTableViewCell.swift 3 | // Notes 4 | // 5 | // Created by Bart Jacobs on 03/11/16. 6 | // Copyright © 2016 Cocoacasts. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CategoryTableViewCell: UITableViewCell { 12 | 13 | // MARK: - Properties 14 | 15 | static let reuseIdentifier = "CategoryTableViewCell" 16 | 17 | // MARK: - 18 | 19 | @IBOutlet var nameLabel: UILabel! 20 | 21 | // MARK: - Initialization 22 | 23 | override func awakeFromNib() { 24 | super.awakeFromNib() 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /Notes/Extensions/UIViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController.swift 3 | // Notes 4 | // 5 | // Created by Bart Jacobs on 03/11/16. 6 | // Copyright © 2016 Cocoacasts. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIViewController { 12 | 13 | // MARK: - Alerts 14 | 15 | func showAlert(with title: String, and message: String) { 16 | // Initialize Alert Controller 17 | let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) 18 | 19 | // Configure Alert Controller 20 | alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) 21 | 22 | // Present Alert Controller 23 | present(alertController, animated: true, completion: nil) 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /Notes/View Controllers/Notes/Notes View Controller/Table View Cells/NoteTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NoteTableViewCell.swift 3 | // Notes 4 | // 5 | // Created by Bart Jacobs on 02/11/16. 6 | // Copyright © 2016 Cocoacasts. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class NoteTableViewCell: UITableViewCell { 12 | 13 | // MARK: - Properties 14 | 15 | static let reuseIdentifier = "NoteTableViewCell" 16 | 17 | // MARK: - 18 | 19 | @IBOutlet var tagsLabel: UILabel! 20 | @IBOutlet var titleLabel: UILabel! 21 | @IBOutlet var contentsLabel: UILabel! 22 | @IBOutlet var updatedAtLabel: UILabel! 23 | @IBOutlet var categoryColorView: UIView! 24 | 25 | // MARK: - Initialization 26 | 27 | override func awakeFromNib() { 28 | super.awakeFromNib() 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /Notes/View Controllers/Tags/Tag View Controller/TagViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TagViewController.swift 3 | // Notes 4 | // 5 | // Created by Bart Jacobs on 05/11/16. 6 | // Copyright © 2016 Cocoacasts. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class TagViewController: UIViewController { 12 | 13 | // MARK: - Properties 14 | 15 | @IBOutlet var nameTextField: UITextField! 16 | 17 | // MARK: - 18 | 19 | var tag: Tag? 20 | 21 | // MARK: - View Life Cycle 22 | 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | 26 | title = "Edit Tag" 27 | 28 | setupView() 29 | } 30 | 31 | override func viewWillDisappear(_ animated: Bool) { 32 | super.viewWillDisappear(animated) 33 | 34 | // Update Tag 35 | if let name = nameTextField.text, !name.isEmpty { 36 | tag?.name = name 37 | } 38 | } 39 | 40 | // MARK: - View Methods 41 | 42 | fileprivate func setupView() { 43 | setupNameTextField() 44 | } 45 | 46 | // MARK: - 47 | 48 | private func setupNameTextField() { 49 | // Configure Name Text Field 50 | nameTextField.text = tag?.name 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /Notes/Supporting Files/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 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Notes 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Notes/Core Data/Extensions/Note.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Note.swift 3 | // Notes 4 | // 5 | // Created by Bart Jacobs on 02/11/16. 6 | // Copyright © 2016 Cocoacasts. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Note { 12 | 13 | // MARK: - Dates 14 | 15 | var updatedAtAsDate: Date { 16 | guard let updatedAt = updatedAt else { return Date() } 17 | return Date(timeIntervalSince1970: updatedAt.timeIntervalSince1970) 18 | } 19 | 20 | var createdAtAsDate: Date { 21 | guard let createdAt = createdAt else { return Date() } 22 | return Date(timeIntervalSince1970: createdAt.timeIntervalSince1970) 23 | } 24 | 25 | // MARK: - Tags 26 | 27 | var alphabetizedTags: [Tag]? { 28 | guard let tags = tags as? Set else { 29 | return nil 30 | } 31 | 32 | return tags.sorted(by: { 33 | guard let tag0 = $0.name else { return true } 34 | guard let tag1 = $1.name else { return true } 35 | return tag0 < tag1 36 | }) 37 | } 38 | 39 | var alphabetizedTagsAsString: String? { 40 | guard let tags = alphabetizedTags, tags.count > 0 else { 41 | return nil 42 | } 43 | 44 | let names = tags.flatMap { $0.name } 45 | return names.joined(separator: ", ") 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Mastering Core Data With Swift 3 2 | 3 | #### Author: Bart Jacobs 4 | 5 | Whenever I teach developers Core Data, I emphasize how important it is to focus on the fundamentals first. The vast majority of issues developers run into are caused by a lack of knowledge about the ins and outs of the framework. Core Data is not difficult if you understand how the framework works. 6 | 7 | In [Mastering Core Data With Swift 3](http://courses.cocoacasts.com/p/mastering-core-data-with-swift-3), you learn everything you need to know to integrate Core Data in a Swift application. We focus on the key players of the framework and build an application that takes advantage of the core features of the framework. 8 | 9 | We start the course with an exploration of the heart of every Core Data application, the Core Data stack. We then take a closer look at the data model of a Core Data application. This teaches you about entities, attributes, and relationships. These are the ingredients of every Core Data application. 10 | 11 | During the course, we build an application that manages notes. We start from scratch, build a Core Data stack, create a data model, and add several features, such as categories and tags. Every feature teaches you a new concept of the Core Data framework. 12 | 13 | We do not skip the more advanced features of the framework. We discuss data model migrations, working with multiple managed object contexts, and using Core Data in a multithreaded environment. 14 | 15 | **[Take me to the course.](http://courses.cocoacasts.com/p/mastering-core-data-with-swift-3)** 16 | -------------------------------------------------------------------------------- /Notes/View Controllers/Notes/Add Note View Controller/AddNoteViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AddNoteViewController.swift 3 | // Notes 4 | // 5 | // Created by Bart Jacobs on 02/11/16. 6 | // Copyright © 2016 Cocoacasts. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreData 11 | 12 | class AddNoteViewController: UIViewController { 13 | 14 | // MARK: - Properties 15 | 16 | @IBOutlet var titleTextField: UITextField! 17 | @IBOutlet var contentsTextView: UITextView! 18 | 19 | // MARK: - 20 | 21 | var managedObjectContext: NSManagedObjectContext? 22 | 23 | // MARK: - View Life Cycle 24 | 25 | override func viewDidLoad() { 26 | super.viewDidLoad() 27 | 28 | title = "Add Note" 29 | } 30 | 31 | override func viewDidAppear(_ animated: Bool) { 32 | super.viewDidAppear(animated) 33 | 34 | // Show Keyboard 35 | titleTextField.becomeFirstResponder() 36 | } 37 | 38 | // MARK: - Actions 39 | 40 | @IBAction func save(sender: UIBarButtonItem) { 41 | guard let managedObjectContext = managedObjectContext else { return } 42 | guard let title = titleTextField.text, !title.isEmpty else { 43 | showAlert(with: "Title Missing", and: "Your note doesn't have a title.") 44 | return 45 | } 46 | 47 | // Create Note 48 | let note = Note(context: managedObjectContext) 49 | 50 | // Configure Note 51 | note.createdAt = NSDate() 52 | note.updatedAt = NSDate() 53 | note.title = titleTextField.text 54 | note.contents = contentsTextView.text 55 | 56 | // Pop View Controller 57 | _ = navigationController?.popViewController(animated: true) 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /Notes/View Controllers/Tags/Add Tag View Controller/AddTagViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AddTagViewController.swift 3 | // Notes 4 | // 5 | // Created by Bart Jacobs on 05/11/16. 6 | // Copyright © 2016 Cocoacasts. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreData 11 | 12 | class AddTagViewController: UIViewController { 13 | 14 | // MARK: - Properties 15 | 16 | @IBOutlet var nameTextField: UITextField! 17 | 18 | // MARK: - 19 | 20 | var managedObjectContext: NSManagedObjectContext? 21 | 22 | // MARK: - View Life Cycle 23 | 24 | override func viewDidLoad() { 25 | super.viewDidLoad() 26 | 27 | title = "Add Tag" 28 | 29 | setupView() 30 | } 31 | 32 | override func viewDidAppear(_ animated: Bool) { 33 | super.viewDidAppear(animated) 34 | 35 | // Show Keyboard 36 | nameTextField.becomeFirstResponder() 37 | } 38 | 39 | // MARK: - View Methods 40 | 41 | fileprivate func setupView() { 42 | setupBarButtonItems() 43 | } 44 | 45 | // MARK: - 46 | 47 | private func setupBarButtonItems() { 48 | navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .save, target: self, action: #selector(save(sender:))) 49 | } 50 | 51 | // MARK: - Actions 52 | 53 | func save(sender: UIBarButtonItem) { 54 | guard let managedObjectContext = managedObjectContext else { return } 55 | guard let name = nameTextField.text, !name.isEmpty else { 56 | showAlert(with: "Name Missing", and: "Your tag doesn't have a name.") 57 | return 58 | } 59 | 60 | // Create Tag 61 | let tag = Tag(context: managedObjectContext) 62 | 63 | // Configure Tag 64 | tag.name = nameTextField.text 65 | 66 | // Pop View Controller 67 | _ = navigationController?.popViewController(animated: true) 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /Notes/View Controllers/Categories/Add Category View Controller/AddCategoryViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AddCategoryViewController.swift 3 | // Notes 4 | // 5 | // Created by Bart Jacobs on 03/11/16. 6 | // Copyright © 2016 Cocoacasts. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreData 11 | 12 | class AddCategoryViewController: UIViewController { 13 | 14 | // MARK: - Properties 15 | 16 | @IBOutlet var nameTextField: UITextField! 17 | 18 | // MARK: - 19 | 20 | var managedObjectContext: NSManagedObjectContext? 21 | 22 | // MARK: - View Life Cycle 23 | 24 | override func viewDidLoad() { 25 | super.viewDidLoad() 26 | 27 | title = "Add Category" 28 | 29 | setupView() 30 | } 31 | 32 | override func viewDidAppear(_ animated: Bool) { 33 | super.viewDidAppear(animated) 34 | 35 | // Show Keyboard 36 | nameTextField.becomeFirstResponder() 37 | } 38 | 39 | // MARK: - View Methods 40 | 41 | fileprivate func setupView() { 42 | setupBarButtonItems() 43 | } 44 | 45 | // MARK: - 46 | 47 | private func setupBarButtonItems() { 48 | navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .save, target: self, action: #selector(save(sender:))) 49 | } 50 | 51 | // MARK: - Actions 52 | 53 | func save(sender: UIBarButtonItem) { 54 | guard let managedObjectContext = managedObjectContext else { return } 55 | guard let name = nameTextField.text, !name.isEmpty else { 56 | showAlert(with: "Name Missing", and: "Your category doesn't have a name.") 57 | return 58 | } 59 | 60 | // Create Category 61 | let category = Category(context: managedObjectContext) 62 | 63 | // Configure Category 64 | category.name = nameTextField.text 65 | 66 | // Pop View Controller 67 | _ = navigationController?.popViewController(animated: true) 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /Notes/Core Data/Notes.xcdatamodeld/Notes.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Notes/Core Data/Notes.xcdatamodeld/Notes 2.xcdatamodel/contents: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /Notes/Extensions/UIColor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor.swift 3 | // Notes 4 | // 5 | // Created by Bart Jacobs on 03/11/16. 6 | // Copyright © 2016 Cocoacasts. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIColor { 12 | 13 | class func bitterSweet() -> UIColor { 14 | return UIColor(red:0.99, green:0.47, blue:0.44, alpha:1.0) 15 | } 16 | 17 | } 18 | 19 | extension UIColor { 20 | 21 | // MARK: - Initialization 22 | 23 | convenience init?(hex: String) { 24 | var hexNormalized = hex.trimmingCharacters(in: .whitespacesAndNewlines) 25 | hexNormalized = hexNormalized.replacingOccurrences(of: "#", with: "") 26 | 27 | // Helpers 28 | var rgb: UInt32 = 0 29 | var r: CGFloat = 0.0 30 | var g: CGFloat = 0.0 31 | var b: CGFloat = 0.0 32 | var a: CGFloat = 1.0 33 | let length = hexNormalized.characters.count 34 | 35 | // Create Scanner 36 | Scanner(string: hexNormalized).scanHexInt32(&rgb) 37 | 38 | if length == 6 { 39 | r = CGFloat((rgb & 0xFF0000) >> 16) / 255.0 40 | g = CGFloat((rgb & 0x00FF00) >> 8) / 255.0 41 | b = CGFloat(rgb & 0x0000FF) / 255.0 42 | 43 | } else if length == 8 { 44 | r = CGFloat((rgb & 0xFF000000) >> 24) / 255.0 45 | g = CGFloat((rgb & 0x00FF0000) >> 16) / 255.0 46 | b = CGFloat((rgb & 0x0000FF00) >> 8) / 255.0 47 | a = CGFloat(rgb & 0x000000FF) / 255.0 48 | 49 | } else { 50 | return nil 51 | } 52 | 53 | self.init(red: r, green: g, blue: b, alpha: a) 54 | } 55 | 56 | // MARK: - Convenience Methods 57 | 58 | var toHex: String? { 59 | // Extract Components 60 | guard let components = cgColor.components, components.count >= 3 else { 61 | return nil 62 | } 63 | 64 | // Helpers 65 | let r = Float(components[0]) 66 | let g = Float(components[1]) 67 | let b = Float(components[2]) 68 | var a = Float(1.0) 69 | 70 | if components.count >= 4 { 71 | a = Float(components[3]) 72 | } 73 | 74 | // Create Hex String 75 | let hex = String(format: "%02lX%02lX%02lX%02lX", lroundf(r * 255), lroundf(g * 255), lroundf(b * 255), lroundf(a * 255)) 76 | 77 | return hex 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /Notes/View Controllers/Categories/Category View Controller/CategoryViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CategoryViewController.swift 3 | // Notes 4 | // 5 | // Created by Bart Jacobs on 03/11/16. 6 | // Copyright © 2016 Cocoacasts. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CategoryViewController: UIViewController { 12 | 13 | // MARK: - Properties 14 | 15 | let segueColorViewController = "SegueColorViewController" 16 | 17 | // MARK: - 18 | 19 | @IBOutlet var colorView: UIView! 20 | @IBOutlet var nameTextField: UITextField! 21 | 22 | // MARK: - 23 | 24 | var category: Category? 25 | 26 | // MARK: - View Life Cycle 27 | 28 | override func viewDidLoad() { 29 | super.viewDidLoad() 30 | 31 | title = "Edit Category" 32 | 33 | setupView() 34 | } 35 | 36 | override func viewWillDisappear(_ animated: Bool) { 37 | super.viewWillDisappear(animated) 38 | 39 | // Update Category 40 | if let name = nameTextField.text, !name.isEmpty { 41 | category?.name = name 42 | } 43 | } 44 | 45 | // MARK: - Navigation 46 | 47 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 48 | guard segue.identifier == segueColorViewController else { return } 49 | 50 | if let destinationViewController = segue.destination as? ColorViewController { 51 | // Configure Destination View Controller 52 | destinationViewController.delegate = self 53 | destinationViewController.color = category?.color ?? .white 54 | } 55 | } 56 | 57 | // MARK: - View Methods 58 | 59 | fileprivate func setupView() { 60 | setupColorView() 61 | setupNameTextField() 62 | } 63 | 64 | // MARK: - 65 | 66 | private func setupColorView() { 67 | // Configure Layer Color View 68 | colorView.layer.cornerRadius = CGFloat(colorView.frame.width / 2.0) 69 | 70 | updateColorView() 71 | } 72 | 73 | fileprivate func updateColorView() { 74 | // Configure Color View 75 | colorView.backgroundColor = category?.color 76 | } 77 | 78 | // MARK: - 79 | 80 | private func setupNameTextField() { 81 | // Configure Name Text Field 82 | nameTextField.text = category?.name 83 | } 84 | 85 | } 86 | 87 | extension CategoryViewController: ColorViewControllerDelegate { 88 | 89 | func controller(_ controller: ColorViewController, didPick color: UIColor) { 90 | // Update Category 91 | category?.color = color 92 | 93 | // Update View 94 | updateColorView() 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /Notes/View Controllers/Categories/Category View Controller/Color View Controller/ColorViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorViewController.swift 3 | // Notes 4 | // 5 | // Created by Bart Jacobs on 04/11/16. 6 | // Copyright © 2016 Cocoacasts. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol ColorViewControllerDelegate { 12 | 13 | func controller(_ controller: ColorViewController, didPick color: UIColor) 14 | 15 | } 16 | 17 | class ColorViewController: UIViewController { 18 | 19 | // MARK: - Properties 20 | 21 | @IBOutlet var colorView: UIView! 22 | 23 | @IBOutlet var redSlider: UISlider! 24 | @IBOutlet var blueSlider: UISlider! 25 | @IBOutlet var greenSlider: UISlider! 26 | 27 | // MARK: - 28 | 29 | var delegate: ColorViewControllerDelegate? 30 | 31 | // MARK: - 32 | 33 | var color: UIColor = .bitterSweet() 34 | 35 | // MARK: - View Life Cycle 36 | 37 | override func viewDidLoad() { 38 | super.viewDidLoad() 39 | 40 | title = "Choose Color" 41 | 42 | setupView() 43 | } 44 | 45 | override func viewWillDisappear(_ animated: Bool) { 46 | super.viewWillDisappear(animated) 47 | 48 | // Notify Delegate 49 | delegate?.controller(self, didPick: (colorView.backgroundColor ?? .white)) 50 | } 51 | 52 | // MARK: - View Methods 53 | 54 | fileprivate func setupView() { 55 | setupSliders() 56 | setupColorView() 57 | } 58 | 59 | // MARK: - 60 | 61 | private func setupSliders() { 62 | // Helpers 63 | var r: CGFloat = 0.0 64 | var g: CGFloat = 0.0 65 | var b: CGFloat = 0.0 66 | var a: CGFloat = 0.0 67 | 68 | // Extract Components 69 | color.getRed(&r, green: &g, blue: &b, alpha: &a) 70 | 71 | // Configure Sliders 72 | redSlider.value = Float(r) 73 | blueSlider.value = Float(g) 74 | greenSlider.value = Float(b) 75 | } 76 | 77 | private func setupColorView() { 78 | // Configure Layer Color View 79 | colorView.layer.cornerRadius = CGFloat(colorView.frame.height / 2.0) 80 | 81 | updateColorView() 82 | } 83 | 84 | private func updateColorView() { 85 | // Create Color 86 | let color = UIColor(red: CGFloat(redSlider.value), green: CGFloat(greenSlider.value), blue: CGFloat(blueSlider.value), alpha: 1.0) 87 | 88 | // Configure Color View 89 | colorView.backgroundColor = color 90 | } 91 | 92 | // MARK: - Actions 93 | 94 | @IBAction func colorDidChange(sender: UISlider) { 95 | // Update View 96 | updateColorView() 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ######################### 2 | # .gitignore file for Xcode4 / OS X Source projects 3 | # 4 | # NB: if you are storing "built" products, this WILL NOT WORK, 5 | # and you should use a different .gitignore (or none at all) 6 | # This file is for SOURCE projects, where there are many extra 7 | # files that we want to exclude 8 | # 9 | # For updates, see: http://stackoverflow.com/questions/49478/git-ignore-file-for-xcode-projects 10 | ######################### 11 | 12 | ##### 13 | # OS X temporary files that should never be committed 14 | 15 | .DS_Store 16 | *.swp 17 | *.lock 18 | profile 19 | 20 | 21 | #### 22 | # Xcode temporary files that should never be committed 23 | # 24 | # NB: NIB/XIB files still exist even on Storyboard projects, so we want this... 25 | 26 | *~.nib 27 | 28 | 29 | #### 30 | # Xcode build files - 31 | # 32 | # NB: slash on the end, so we only remove the FOLDER, not any files that were badly named "DerivedData" 33 | 34 | DerivedData/ 35 | 36 | # NB: slash on the end, so we only remove the FOLDER, not any files that were badly named "build" 37 | 38 | build/ 39 | 40 | 41 | ##### 42 | # Xcode private settings (window sizes, bookmarks, breakpoints, custom executables, smart groups) 43 | # 44 | # This is complicated: 45 | # 46 | # SOMETIMES you need to put this file in version control. 47 | # Apple designed it poorly - if you use "custom executables", they are 48 | # saved in this file. 49 | # 99% of projects do NOT use those, so they do NOT want to version control this file. 50 | # ..but if you're in the 1%, comment out the line "*.pbxuser" 51 | 52 | *.pbxuser 53 | *.mode1v3 54 | *.mode2v3 55 | *.perspectivev3 56 | # NB: also, whitelist the default ones, some projects need to use these 57 | !default.pbxuser 58 | !default.mode1v3 59 | !default.mode2v3 60 | !default.perspectivev3 61 | 62 | 63 | #### 64 | # Xcode 4 - semi-personal settings, often included in workspaces 65 | # 66 | # You can safely ignore the xcuserdata files - but do NOT ignore the files next to them 67 | # 68 | 69 | xcuserdata 70 | 71 | #### 72 | # XCode 4 workspaces - more detailed 73 | # 74 | # Workspaces are important! They are a core feature of Xcode - don't exclude them :) 75 | # 76 | # Workspace layout is quite spammy. For reference: 77 | # 78 | # (root)/ 79 | # (project-name).xcodeproj/ 80 | # project.pbxproj 81 | # project.xcworkspace/ 82 | # contents.xcworkspacedata 83 | # xcuserdata/ 84 | # (your name)/xcuserdatad/ 85 | # xcuserdata/ 86 | # (your name)/xcuserdatad/ 87 | # 88 | # 89 | # 90 | # Xcode 4 workspaces - SHARED 91 | # 92 | # This is UNDOCUMENTED (google: "developer.apple.com xcshareddata" - 0 results 93 | # But if you're going to kill personal workspaces, at least keep the shared ones... 94 | # 95 | # 96 | !xcshareddata 97 | 98 | #### 99 | # XCode 4 build-schemes 100 | # 101 | # PRIVATE ones are stored inside xcuserdata 102 | !xcschemes 103 | 104 | #### 105 | # Xcode 4 - Deprecated classes 106 | # 107 | # Allegedly, if you manually "deprecate" your classes, they get moved here. 108 | # 109 | # We're using source-control, so this is a "feature" that we do not want! 110 | 111 | *.moved-aside 112 | 113 | # CocoaPods 114 | /Pods 115 | -------------------------------------------------------------------------------- /Notes/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "40x40", 5 | "idiom" : "ipad", 6 | "scale" : "1x", 7 | "filename" : "Icon-40.png" 8 | }, 9 | { 10 | "size" : "40x40", 11 | "idiom" : "ipad", 12 | "scale" : "2x", 13 | "filename" : "Icon-40@2x.png" 14 | }, 15 | { 16 | "size" : "60x60", 17 | "idiom" : "iphone", 18 | "scale" : "2x", 19 | "filename" : "Icon-60@2x.png" 20 | }, 21 | { 22 | "size" : "72x72", 23 | "idiom" : "ipad", 24 | "scale" : "1x", 25 | "filename" : "Icon-72.png" 26 | }, 27 | { 28 | "size" : "72x72", 29 | "idiom" : "ipad", 30 | "scale" : "2x", 31 | "filename" : "Icon-72@2x.png" 32 | }, 33 | { 34 | "size" : "76x76", 35 | "idiom" : "ipad", 36 | "scale" : "1x", 37 | "filename" : "Icon-76.png" 38 | }, 39 | { 40 | "size" : "76x76", 41 | "idiom" : "ipad", 42 | "scale" : "2x", 43 | "filename" : "Icon-76@2x.png" 44 | }, 45 | { 46 | "size" : "50x50", 47 | "idiom" : "ipad", 48 | "scale" : "1x", 49 | "filename" : "Icon-Small-50.png" 50 | }, 51 | { 52 | "size" : "50x50", 53 | "idiom" : "ipad", 54 | "scale" : "2x", 55 | "filename" : "Icon-Small-50@2x.png" 56 | }, 57 | { 58 | "size" : "29x29", 59 | "idiom" : "iphone", 60 | "scale" : "1x", 61 | "filename" : "Icon-Small.png" 62 | }, 63 | { 64 | "size" : "29x29", 65 | "idiom" : "iphone", 66 | "scale" : "2x", 67 | "filename" : "Icon-Small@2x.png" 68 | }, 69 | { 70 | "size" : "57x57", 71 | "idiom" : "iphone", 72 | "scale" : "1x", 73 | "filename" : "Icon.png" 74 | }, 75 | { 76 | "size" : "57x57", 77 | "idiom" : "iphone", 78 | "scale" : "2x", 79 | "filename" : "Icon@2x.png" 80 | }, 81 | { 82 | "size" : "29x29", 83 | "idiom" : "iphone", 84 | "scale" : "3x", 85 | "filename" : "Icon-Small@3x.png" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "iphone", 90 | "scale" : "3x", 91 | "filename" : "Icon-40@3x.png" 92 | }, 93 | { 94 | "size" : "60x60", 95 | "idiom" : "iphone", 96 | "scale" : "3x", 97 | "filename" : "Icon-60@3x.png" 98 | }, 99 | { 100 | "size" : "40x40", 101 | "idiom" : "iphone", 102 | "scale" : "2x", 103 | "filename" : "Icon-40@2x.png" 104 | }, 105 | { 106 | "size" : "29x29", 107 | "idiom" : "ipad", 108 | "scale" : "1x", 109 | "filename" : "Icon-Small.png" 110 | }, 111 | { 112 | "size" : "29x29", 113 | "idiom" : "ipad", 114 | "scale" : "2x", 115 | "filename" : "Icon-Small@2x.png" 116 | }, 117 | { 118 | "size" : "83.5x83.5", 119 | "idiom" : "ipad", 120 | "scale" : "2x", 121 | "filename" : "Icon-83.5@2x.png" 122 | }, 123 | { 124 | "size" : "20x20", 125 | "idiom" : "iphone", 126 | "scale" : "2x", 127 | "filename" : "NotificationIcon@2x.png" 128 | }, 129 | { 130 | "size" : "20x20", 131 | "idiom" : "iphone", 132 | "scale" : "3x", 133 | "filename" : "NotificationIcon@3x.png" 134 | }, 135 | { 136 | "size" : "20x20", 137 | "idiom" : "ipad", 138 | "scale" : "1x", 139 | "filename" : "NotificationIcon~ipad.png" 140 | }, 141 | { 142 | "size" : "20x20", 143 | "idiom" : "ipad", 144 | "scale" : "2x", 145 | "filename" : "NotificationIcon~ipad@2x.png" 146 | } 147 | ], 148 | "info" : { 149 | "author" : "xcode", 150 | "version" : 1 151 | } 152 | } -------------------------------------------------------------------------------- /Notes/View Controllers/Notes/Note View Controller/NoteViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NoteViewController.swift 3 | // Notes 4 | // 5 | // Created by Bart Jacobs on 02/11/16. 6 | // Copyright © 2016 Cocoacasts. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreData 11 | 12 | class NoteViewController: UIViewController { 13 | 14 | // MARK: - Properties 15 | 16 | let segueTagsViewController = "SegueTagsViewController" 17 | let segueCategoriesViewController = "SegueCategoriesViewController" 18 | 19 | // MARK: - 20 | 21 | @IBOutlet var tagsLabel: UILabel! 22 | @IBOutlet var categoryLabel: UILabel! 23 | @IBOutlet var titleTextField: UITextField! 24 | @IBOutlet var contentsTextView: UITextView! 25 | 26 | // MARK: - 27 | 28 | var note: Note? 29 | 30 | // MARK: - View Life Cycle 31 | 32 | override func viewDidLoad() { 33 | super.viewDidLoad() 34 | 35 | title = "Edit Note" 36 | 37 | setupView() 38 | 39 | setupNotificationHandling() 40 | } 41 | 42 | override func viewWillDisappear(_ animated: Bool) { 43 | super.viewWillDisappear(animated) 44 | 45 | // Update Note 46 | if let title = titleTextField.text, !title.isEmpty { 47 | note?.title = title 48 | } 49 | 50 | note?.updatedAt = NSDate() 51 | note?.contents = contentsTextView.text 52 | } 53 | 54 | // MARK: - Navigation 55 | 56 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 57 | if segue.identifier == segueCategoriesViewController { 58 | if let destinationViewController = segue.destination as? CategoriesViewController { 59 | // Configure View Controller 60 | destinationViewController.note = note 61 | } 62 | 63 | } else if segue.identifier == segueTagsViewController { 64 | if let destinationViewController = segue.destination as? TagsViewController { 65 | // Configure View Controller 66 | destinationViewController.note = note 67 | } 68 | } 69 | } 70 | 71 | // MARK: - View Methods 72 | 73 | fileprivate func setupView() { 74 | setupTagsLabel() 75 | setupCategoryLabel() 76 | setupTitleTextField() 77 | setupContentsTextView() 78 | } 79 | 80 | // MARK: - 81 | 82 | private func setupTagsLabel() { 83 | updateTagsLabel() 84 | } 85 | 86 | private func updateTagsLabel() { 87 | // Configure Tags Label 88 | tagsLabel.text = note?.alphabetizedTagsAsString ?? "No Tags" 89 | } 90 | 91 | private func setupCategoryLabel() { 92 | updateCategoryLabel() 93 | } 94 | 95 | private func updateCategoryLabel() { 96 | // Configure Category Label 97 | categoryLabel.text = note?.category?.name ?? "No Category" 98 | } 99 | 100 | private func setupTitleTextField() { 101 | // Configure Title Text Field 102 | titleTextField.text = note?.title 103 | } 104 | 105 | private func setupContentsTextView() { 106 | // Configure Contents Text View 107 | contentsTextView.text = note?.contents 108 | } 109 | 110 | // MARK: - Notification Handling 111 | 112 | func managedObjectContextObjectsDidChange(_ notification: Notification) { 113 | guard let userInfo = notification.userInfo else { return } 114 | guard let updates = userInfo[NSUpdatedObjectsKey] as? Set else { return } 115 | 116 | if (updates.filter { return $0 == note }).count > 0 { 117 | updateTagsLabel() 118 | updateCategoryLabel() 119 | } 120 | } 121 | 122 | // MARK: - Helper Methods 123 | 124 | private func setupNotificationHandling() { 125 | let notificationCenter = NotificationCenter.default 126 | notificationCenter.addObserver(self, selector: #selector(managedObjectContextObjectsDidChange(_:)), name: Notification.Name.NSManagedObjectContextObjectsDidChange, object: note?.managedObjectContext) 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /Notes/Storyboards/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /Notes/Managers/CoreDataManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoreDataManager.swift 3 | // Notes 4 | // 5 | // Created by Bart Jacobs on 31/10/16. 6 | // Copyright © 2016 Cocoacasts. All rights reserved. 7 | // 8 | 9 | import CoreData 10 | 11 | final class CoreDataManager { 12 | 13 | // MARK: - Properties 14 | 15 | let modelName: String 16 | 17 | // MARK: - 18 | 19 | private(set) lazy var mainManagedObjectContext: NSManagedObjectContext = { 20 | // Initialize Managed Object Context 21 | let managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType) 22 | 23 | // Configure Managed Object Context 24 | managedObjectContext.parent = self.privateManagedObjectContext 25 | 26 | return managedObjectContext 27 | }() 28 | 29 | private lazy var privateManagedObjectContext: NSManagedObjectContext = { 30 | // Initialize Managed Object Context 31 | let managedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) 32 | 33 | // Configure Managed Object Context 34 | managedObjectContext.persistentStoreCoordinator = self.persistentStoreCoordinator 35 | 36 | return managedObjectContext 37 | }() 38 | 39 | private lazy var managedObjectModel: NSManagedObjectModel = { 40 | // Fetch Model URL 41 | guard let modelURL = Bundle.main.url(forResource: self.modelName, withExtension: "momd") else { 42 | fatalError("Unable to Find Data Model") 43 | } 44 | 45 | // Initialize Managed Object Model 46 | guard let managedObjectModel = NSManagedObjectModel(contentsOf: modelURL) else { 47 | fatalError("Unable to Load Data Model") 48 | } 49 | 50 | return managedObjectModel 51 | }() 52 | 53 | private lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = { 54 | // Initialize Persistent Store Coordinator 55 | let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel) 56 | 57 | // Helpers 58 | let fileManager = FileManager.default 59 | let storeName = "\(self.modelName).sqlite" 60 | 61 | // URL Documents Directory 62 | let documentsDirectoryURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0] 63 | 64 | // URL Persistent Store 65 | let persistentStoreURL = documentsDirectoryURL.appendingPathComponent(storeName) 66 | 67 | do { 68 | // Add Persistent Store 69 | let options = [ NSMigratePersistentStoresAutomaticallyOption : true, NSInferMappingModelAutomaticallyOption : true ] 70 | try persistentStoreCoordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: persistentStoreURL, options: options) 71 | 72 | } catch { 73 | fatalError("Unable to Add Persistent Store") 74 | } 75 | 76 | return persistentStoreCoordinator 77 | }() 78 | 79 | // MARK: - Initialization 80 | 81 | init(modelName: String) { 82 | self.modelName = modelName 83 | 84 | setupNotificationHandling() 85 | } 86 | 87 | // MARK: - Notification Handling 88 | 89 | @objc func saveChanges(_ notification: Notification) { 90 | saveChanges() 91 | } 92 | 93 | // MARK: - Helper Methods 94 | 95 | private func setupNotificationHandling() { 96 | let notificationCenter = NotificationCenter.default 97 | notificationCenter.addObserver(self, selector: #selector(saveChanges(_:)), name: Notification.Name.UIApplicationWillTerminate, object: nil) 98 | notificationCenter.addObserver(self, selector: #selector(saveChanges(_:)), name: Notification.Name.UIApplicationDidEnterBackground, object: nil) 99 | } 100 | 101 | // MARK: - 102 | 103 | private func saveChanges() { 104 | mainManagedObjectContext.performAndWait({ 105 | do { 106 | if self.mainManagedObjectContext.hasChanges { 107 | try self.mainManagedObjectContext.save() 108 | } 109 | } catch { 110 | let saveError = error as NSError 111 | print("Unable to Save Changes of Main Managed Object Context") 112 | print("\(saveError), \(saveError.localizedDescription)") 113 | } 114 | 115 | self.privateManagedObjectContext.perform({ 116 | do { 117 | if self.privateManagedObjectContext.hasChanges { 118 | try self.privateManagedObjectContext.save() 119 | } 120 | } catch { 121 | let saveError = error as NSError 122 | print("Unable to Save Changes of Private Managed Object Context") 123 | print("\(saveError), \(saveError.localizedDescription)") 124 | } 125 | }) 126 | }) 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /Notes/View Controllers/Notes/Notes View Controller/NotesViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NotesViewController.swift 3 | // Notes 4 | // 5 | // Created by Bart Jacobs on 31/10/16. 6 | // Copyright © 2016 Cocoacasts. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreData 11 | 12 | class NotesViewController: UIViewController { 13 | 14 | // MARK: - Properties 15 | 16 | let segueNoteViewController = "SegueNoteViewController" 17 | let segueAddNoteViewController = "SegueAddNoteViewController" 18 | 19 | // MARK: - 20 | 21 | @IBOutlet var notesView: UIView! 22 | @IBOutlet var messageLabel: UILabel! 23 | @IBOutlet var tableView: UITableView! 24 | 25 | // MARK: - 26 | 27 | private let estimatedRowHeight = CGFloat(44.0) 28 | 29 | fileprivate let coreDataManager = CoreDataManager(modelName: "Notes") 30 | 31 | // MARK: - 32 | 33 | fileprivate lazy var fetchedResultsController: NSFetchedResultsController = { 34 | // Create Fetch Request 35 | let fetchRequest: NSFetchRequest = Note.fetchRequest() 36 | 37 | // Configure Fetch Request 38 | fetchRequest.sortDescriptors = [NSSortDescriptor(key: #keyPath(Note.updatedAt), ascending: false)] 39 | 40 | // Create Fetched Results Controller 41 | let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.coreDataManager.mainManagedObjectContext, sectionNameKeyPath: nil, cacheName: nil) 42 | 43 | // Configure Fetched Results Controller 44 | fetchedResultsController.delegate = self 45 | 46 | return fetchedResultsController 47 | }() 48 | 49 | fileprivate var hasNotes: Bool { 50 | guard let fetchedObjects = fetchedResultsController.fetchedObjects else { return false } 51 | return fetchedObjects.count > 0 52 | } 53 | 54 | fileprivate lazy var updatedAtDateFormatter: DateFormatter = { 55 | let dateFormatter = DateFormatter() 56 | dateFormatter.dateFormat = "MMM d, HH:mm" 57 | return dateFormatter 58 | }() 59 | 60 | // MARK: - View Life Cycle 61 | 62 | override func viewDidLoad() { 63 | super.viewDidLoad() 64 | 65 | title = "Notes" 66 | 67 | setupView() 68 | fetchNotes() 69 | 70 | updateView() 71 | } 72 | 73 | // MARK: - Navigation 74 | 75 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 76 | if segue.identifier == segueAddNoteViewController { 77 | if let destinationViewController = segue.destination as? AddNoteViewController { 78 | destinationViewController.managedObjectContext = coreDataManager.mainManagedObjectContext 79 | } 80 | 81 | } else if segue.identifier == segueNoteViewController { 82 | if let destinationViewController = segue.destination as? NoteViewController, let indexPath = tableView.indexPathForSelectedRow { 83 | // Fetch Note 84 | let note = fetchedResultsController.object(at: indexPath) 85 | 86 | // Configure View Controller 87 | destinationViewController.note = note 88 | } 89 | } 90 | } 91 | 92 | // MARK: - View Methods 93 | 94 | fileprivate func setupView() { 95 | setupMessageLabel() 96 | setupTableView() 97 | } 98 | 99 | fileprivate func updateView() { 100 | tableView.isHidden = !hasNotes 101 | messageLabel.isHidden = hasNotes 102 | } 103 | 104 | // MARK: - 105 | 106 | private func setupMessageLabel() { 107 | messageLabel.text = "You don't have any notes yet." 108 | } 109 | 110 | // MARK: - 111 | 112 | private func setupTableView() { 113 | tableView.isHidden = true 114 | tableView.separatorInset = .zero 115 | tableView.estimatedRowHeight = estimatedRowHeight 116 | tableView.rowHeight = UITableViewAutomaticDimension 117 | } 118 | 119 | // MARK: - Helper Methods 120 | 121 | private func fetchNotes() { 122 | do { 123 | try self.fetchedResultsController.performFetch() 124 | } catch { 125 | print("Unable to Perform Fetch Request") 126 | print("\(error), \(error.localizedDescription)") 127 | } 128 | } 129 | 130 | } 131 | 132 | extension NotesViewController: NSFetchedResultsControllerDelegate { 133 | 134 | func controllerWillChangeContent(_ controller: NSFetchedResultsController) { 135 | tableView.beginUpdates() 136 | } 137 | 138 | func controllerDidChangeContent(_ controller: NSFetchedResultsController) { 139 | tableView.endUpdates() 140 | 141 | updateView() 142 | } 143 | 144 | func controller(_ controller: NSFetchedResultsController, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) { 145 | switch (type) { 146 | case .insert: 147 | if let indexPath = newIndexPath { 148 | tableView.insertRows(at: [indexPath], with: .fade) 149 | } 150 | case .delete: 151 | if let indexPath = indexPath { 152 | tableView.deleteRows(at: [indexPath], with: .fade) 153 | } 154 | case .update: 155 | if let indexPath = indexPath, let cell = tableView.cellForRow(at: indexPath) as? NoteTableViewCell { 156 | configure(cell, at: indexPath) 157 | } 158 | case .move: 159 | if let indexPath = indexPath { 160 | tableView.deleteRows(at: [indexPath], with: .fade) 161 | } 162 | 163 | if let newIndexPath = newIndexPath { 164 | tableView.insertRows(at: [newIndexPath], with: .fade) 165 | } 166 | } 167 | } 168 | 169 | } 170 | 171 | extension NotesViewController: UITableViewDataSource { 172 | 173 | func numberOfSections(in tableView: UITableView) -> Int { 174 | guard let sections = fetchedResultsController.sections else { return 0 } 175 | return sections.count 176 | } 177 | 178 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 179 | guard let section = fetchedResultsController.sections?[section] else { return 0 } 180 | return section.numberOfObjects 181 | } 182 | 183 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 184 | // Dequeue Reusable Cell 185 | guard let cell = tableView.dequeueReusableCell(withIdentifier: NoteTableViewCell.reuseIdentifier, for: indexPath) as? NoteTableViewCell else { 186 | fatalError("Unexpected Index Path") 187 | } 188 | 189 | // Configure Cell 190 | configure(cell, at: indexPath) 191 | 192 | return cell 193 | } 194 | 195 | func configure(_ cell: NoteTableViewCell, at indexPath: IndexPath) { 196 | // Fetch Note 197 | let note = fetchedResultsController.object(at: indexPath) 198 | 199 | // Configure Cell 200 | cell.titleLabel.text = note.title 201 | cell.contentsLabel.text = note.contents 202 | cell.tagsLabel.text = note.alphabetizedTagsAsString ?? "No Tags" 203 | cell.updatedAtLabel.text = updatedAtDateFormatter.string(from: note.updatedAtAsDate) 204 | 205 | if let color = note.category?.color { 206 | cell.categoryColorView.backgroundColor = color 207 | } else { 208 | cell.categoryColorView.backgroundColor = .white 209 | } 210 | } 211 | 212 | func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { 213 | guard editingStyle == .delete else { return } 214 | 215 | // Fetch Note 216 | let note = fetchedResultsController.object(at: indexPath) 217 | 218 | // Delete Note 219 | coreDataManager.mainManagedObjectContext.delete(note) 220 | } 221 | 222 | } 223 | 224 | extension NotesViewController: UITableViewDelegate { 225 | 226 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 227 | tableView.deselectRow(at: indexPath, animated: true) 228 | } 229 | 230 | } 231 | -------------------------------------------------------------------------------- /Notes/View Controllers/Tags/Tags View Controller/TagsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TagsViewController.swift 3 | // Notes 4 | // 5 | // Created by Bart Jacobs on 05/11/16. 6 | // Copyright © 2016 Cocoacasts. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreData 11 | 12 | class TagsViewController: UIViewController { 13 | 14 | // MARK: - Properties 15 | 16 | private let segueTagViewController = "SegueTagViewController" 17 | private let segueAddTagViewController = "SegueAddTagViewController" 18 | 19 | // MARK: - 20 | 21 | @IBOutlet var messageLabel: UILabel! 22 | @IBOutlet var tableView: UITableView! 23 | 24 | // MARK: - 25 | 26 | var note: Note? 27 | 28 | // MARK: - 29 | 30 | fileprivate lazy var fetchedResultsController: NSFetchedResultsController = { 31 | guard let managedObjectContext = self.note?.managedObjectContext else { 32 | fatalError("No Managed Object Context Found") 33 | } 34 | 35 | // Create Fetch Request 36 | let fetchRequest: NSFetchRequest = Tag.fetchRequest() 37 | 38 | // Configure Fetch Request 39 | fetchRequest.sortDescriptors = [NSSortDescriptor(key: #keyPath(Tag.name), ascending: true)] 40 | 41 | // Create Fetched Results Controller 42 | let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedObjectContext, sectionNameKeyPath: nil, cacheName: nil) 43 | 44 | // Configure Fetched Results Controller 45 | fetchedResultsController.delegate = self 46 | 47 | return fetchedResultsController 48 | }() 49 | 50 | // MARK: - View Life Cycle 51 | 52 | override func viewDidLoad() { 53 | super.viewDidLoad() 54 | 55 | title = "Tags" 56 | 57 | setupView() 58 | 59 | do { 60 | try fetchedResultsController.performFetch() 61 | } catch { 62 | let fetchError = error as NSError 63 | print("Unable to Perform Fetch Request") 64 | print("\(fetchError), \(fetchError.localizedDescription)") 65 | } 66 | 67 | updateView() 68 | } 69 | 70 | // MARK: - Navigation 71 | 72 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 73 | if segue.identifier == segueAddTagViewController { 74 | if let destinationViewController = segue.destination as? AddTagViewController { 75 | // Configure View Controller 76 | destinationViewController.managedObjectContext = note?.managedObjectContext 77 | } 78 | 79 | } else if segue.identifier == segueTagViewController { 80 | if let destinationViewController = segue.destination as? TagViewController { 81 | guard let cell = sender as? TagTableViewCell else { return } 82 | guard let indexPath = tableView.indexPath(for: cell) else { return } 83 | 84 | // Fetch Tag 85 | let tag = fetchedResultsController.object(at: indexPath) 86 | 87 | // Configure View Controller 88 | destinationViewController.tag = tag 89 | } 90 | } 91 | } 92 | 93 | // MARK: - View Methods 94 | 95 | fileprivate func setupView() { 96 | setupMessageLabel() 97 | setupBarButtonItems() 98 | } 99 | 100 | fileprivate func updateView() { 101 | var hasTags = false 102 | 103 | if let fetchedObjects = fetchedResultsController.fetchedObjects { 104 | hasTags = fetchedObjects.count > 0 105 | } 106 | 107 | tableView.isHidden = !hasTags 108 | messageLabel.isHidden = hasTags 109 | } 110 | 111 | // MARK: - 112 | 113 | private func setupMessageLabel() { 114 | // Configure Message Label 115 | messageLabel.text = "You don't have any tags yet." 116 | } 117 | 118 | // MARK: - 119 | 120 | private func setupBarButtonItems() { 121 | navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(add(sender:))) 122 | } 123 | 124 | // MARK: - Actions 125 | 126 | func add(sender: UIBarButtonItem) { 127 | performSegue(withIdentifier: segueAddTagViewController, sender: self) 128 | } 129 | 130 | } 131 | 132 | extension TagsViewController: NSFetchedResultsControllerDelegate { 133 | 134 | func controllerWillChangeContent(_ controller: NSFetchedResultsController) { 135 | tableView.beginUpdates() 136 | } 137 | 138 | func controllerDidChangeContent(_ controller: NSFetchedResultsController) { 139 | tableView.endUpdates() 140 | 141 | updateView() 142 | } 143 | 144 | func controller(_ controller: NSFetchedResultsController, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) { 145 | switch (type) { 146 | case .insert: 147 | if let indexPath = newIndexPath { 148 | tableView.insertRows(at: [indexPath], with: .fade) 149 | } 150 | case .delete: 151 | if let indexPath = indexPath { 152 | tableView.deleteRows(at: [indexPath], with: .fade) 153 | } 154 | case .update: 155 | if let indexPath = indexPath, let cell = tableView.cellForRow(at: indexPath) as? TagTableViewCell { 156 | configure(cell, at: indexPath) 157 | } 158 | case .move: 159 | if let indexPath = indexPath { 160 | tableView.deleteRows(at: [indexPath], with: .fade) 161 | } 162 | 163 | if let newIndexPath = newIndexPath { 164 | tableView.insertRows(at: [newIndexPath], with: .fade) 165 | } 166 | } 167 | } 168 | 169 | } 170 | 171 | extension TagsViewController: UITableViewDataSource { 172 | 173 | func numberOfSections(in tableView: UITableView) -> Int { 174 | guard let sections = fetchedResultsController.sections else { return 0 } 175 | return sections.count 176 | } 177 | 178 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 179 | guard let section = fetchedResultsController.sections?[section] else { return 0 } 180 | return section.numberOfObjects 181 | } 182 | 183 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 184 | // Dequeue Reusable Cell 185 | guard let cell = tableView.dequeueReusableCell(withIdentifier: TagTableViewCell.reuseIdentifier, for: indexPath) as? TagTableViewCell else { 186 | fatalError("Unexpected Index Path") 187 | } 188 | 189 | // Configure Cell 190 | configure(cell, at: indexPath) 191 | 192 | return cell 193 | } 194 | 195 | func configure(_ cell: TagTableViewCell, at indexPath: IndexPath) { 196 | // Fetch Tag 197 | let tag = fetchedResultsController.object(at: indexPath) 198 | 199 | // Configure Cell 200 | cell.nameLabel.text = tag.name 201 | 202 | if let containsTag = note?.tags?.contains(tag), containsTag == true { 203 | cell.nameLabel.textColor = .bitterSweet() 204 | } else { 205 | cell.nameLabel.textColor = .black 206 | } 207 | } 208 | 209 | func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { 210 | guard editingStyle == .delete else { return } 211 | 212 | // Fetch Tag 213 | let tag = fetchedResultsController.object(at: indexPath) 214 | 215 | // Delete Tag 216 | note?.managedObjectContext?.delete(tag) 217 | } 218 | 219 | } 220 | 221 | extension TagsViewController: UITableViewDelegate { 222 | 223 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 224 | tableView.deselectRow(at: indexPath, animated: true) 225 | 226 | // Fetch Tag 227 | let tag = fetchedResultsController.object(at: indexPath) 228 | 229 | if let containsTag = note?.tags?.contains(tag), containsTag == true { 230 | note?.removeFromTags(tag) 231 | } else { 232 | note?.addToTags(tag) 233 | } 234 | } 235 | 236 | } 237 | -------------------------------------------------------------------------------- /Notes/View Controllers/Categories/Categories View Controller/CategoriesViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CategoriesViewController.swift 3 | // Notes 4 | // 5 | // Created by Bart Jacobs on 03/11/16. 6 | // Copyright © 2016 Cocoacasts. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreData 11 | 12 | class CategoriesViewController: UIViewController { 13 | 14 | // MARK: - Properties 15 | 16 | private let segueCategoryViewController = "SegueCategoryViewController" 17 | private let segueAddCategoryViewController = "SegueAddCategoryViewController" 18 | 19 | // MARK: - 20 | 21 | @IBOutlet var messageLabel: UILabel! 22 | @IBOutlet var tableView: UITableView! 23 | 24 | // MARK: - 25 | 26 | var note: Note? 27 | 28 | // MARK: - 29 | 30 | fileprivate lazy var fetchedResultsController: NSFetchedResultsController = { 31 | guard let managedObjectContext = self.note?.managedObjectContext else { 32 | fatalError("No Managed Object Context Found") 33 | } 34 | 35 | // Create Fetch Request 36 | let fetchRequest: NSFetchRequest = Category.fetchRequest() 37 | 38 | // Configure Fetch Request 39 | fetchRequest.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)] 40 | 41 | // Create Fetched Results Controller 42 | let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedObjectContext, sectionNameKeyPath: nil, cacheName: nil) 43 | 44 | // Configure Fetched Results Controller 45 | fetchedResultsController.delegate = self 46 | 47 | return fetchedResultsController 48 | }() 49 | 50 | // MARK: - View Life Cycle 51 | 52 | override func viewDidLoad() { 53 | super.viewDidLoad() 54 | 55 | title = "Categories" 56 | 57 | setupView() 58 | 59 | do { 60 | try fetchedResultsController.performFetch() 61 | } catch { 62 | let fetchError = error as NSError 63 | print("Unable to Perform Fetch Request") 64 | print("\(fetchError), \(fetchError.localizedDescription)") 65 | } 66 | 67 | updateView() 68 | } 69 | 70 | // MARK: - Navigation 71 | 72 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 73 | if segue.identifier == segueAddCategoryViewController { 74 | if let destinationViewController = segue.destination as? AddCategoryViewController { 75 | // Configure Destination View Controller 76 | destinationViewController.managedObjectContext = note?.managedObjectContext 77 | } 78 | 79 | } else if segue.identifier == segueCategoryViewController { 80 | if let destinationViewController = segue.destination as? CategoryViewController { 81 | guard let cell = sender as? CategoryTableViewCell else { return } 82 | guard let indexPath = tableView.indexPath(for: cell) else { return } 83 | 84 | // Fetch Category 85 | let category = fetchedResultsController.object(at: indexPath) 86 | 87 | // Configure Destination View Controller 88 | destinationViewController.category = category 89 | } 90 | } 91 | } 92 | 93 | // MARK: - View Methods 94 | 95 | fileprivate func setupView() { 96 | setupMessageLabel() 97 | setupBarButtonItems() 98 | } 99 | 100 | fileprivate func updateView() { 101 | var hasCategories = false 102 | 103 | if let fetchedObjects = fetchedResultsController.fetchedObjects { 104 | hasCategories = fetchedObjects.count > 0 105 | } 106 | 107 | tableView.isHidden = !hasCategories 108 | messageLabel.isHidden = hasCategories 109 | } 110 | 111 | // MARK: - 112 | 113 | private func setupMessageLabel() { 114 | // Configure Message Label 115 | messageLabel.text = "You don't have any categories yet." 116 | } 117 | 118 | // MARK: - 119 | 120 | private func setupBarButtonItems() { 121 | navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(add(sender:))) 122 | } 123 | 124 | // MARK: - Actions 125 | 126 | func add(sender: UIBarButtonItem) { 127 | performSegue(withIdentifier: segueAddCategoryViewController, sender: self) 128 | } 129 | 130 | } 131 | 132 | extension CategoriesViewController: NSFetchedResultsControllerDelegate { 133 | 134 | func controllerWillChangeContent(_ controller: NSFetchedResultsController) { 135 | tableView.beginUpdates() 136 | } 137 | 138 | func controllerDidChangeContent(_ controller: NSFetchedResultsController) { 139 | tableView.endUpdates() 140 | 141 | updateView() 142 | } 143 | 144 | func controller(_ controller: NSFetchedResultsController, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) { 145 | switch (type) { 146 | case .insert: 147 | if let indexPath = newIndexPath { 148 | tableView.insertRows(at: [indexPath], with: .fade) 149 | } 150 | case .delete: 151 | if let indexPath = indexPath { 152 | tableView.deleteRows(at: [indexPath], with: .fade) 153 | } 154 | case .update: 155 | if let indexPath = indexPath, let cell = tableView.cellForRow(at: indexPath) as? CategoryTableViewCell { 156 | configure(cell, at: indexPath) 157 | } 158 | case .move: 159 | if let indexPath = indexPath { 160 | tableView.deleteRows(at: [indexPath], with: .fade) 161 | } 162 | 163 | if let newIndexPath = newIndexPath { 164 | tableView.insertRows(at: [newIndexPath], with: .fade) 165 | } 166 | } 167 | } 168 | 169 | } 170 | 171 | extension CategoriesViewController: UITableViewDataSource { 172 | 173 | func numberOfSections(in tableView: UITableView) -> Int { 174 | guard let sections = fetchedResultsController.sections else { return 0 } 175 | return sections.count 176 | } 177 | 178 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 179 | guard let section = fetchedResultsController.sections?[section] else { return 0 } 180 | return section.numberOfObjects 181 | } 182 | 183 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 184 | // Dequeue Reusable Cell 185 | guard let cell = tableView.dequeueReusableCell(withIdentifier: CategoryTableViewCell.reuseIdentifier, for: indexPath) as? CategoryTableViewCell else { 186 | fatalError("Unexpected Index Path") 187 | } 188 | 189 | // Configure Cell 190 | configure(cell, at: indexPath) 191 | 192 | return cell 193 | } 194 | 195 | func configure(_ cell: CategoryTableViewCell, at indexPath: IndexPath) { 196 | // Fetch Category 197 | let category = fetchedResultsController.object(at: indexPath) 198 | 199 | // Configure Cell 200 | cell.nameLabel.text = category.name 201 | 202 | if note?.category == category { 203 | cell.nameLabel.textColor = .bitterSweet() 204 | } else { 205 | cell.nameLabel.textColor = .black 206 | } 207 | } 208 | 209 | func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { 210 | guard editingStyle == .delete else { return } 211 | 212 | // Fetch Category 213 | let category = fetchedResultsController.object(at: indexPath) 214 | 215 | // Delete Category 216 | note?.managedObjectContext?.delete(category) 217 | } 218 | 219 | } 220 | 221 | extension CategoriesViewController: UITableViewDelegate { 222 | 223 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 224 | tableView.deselectRow(at: indexPath, animated: true) 225 | 226 | // Fetch Category 227 | let category = fetchedResultsController.object(at: indexPath) 228 | 229 | // Update Note 230 | note?.category = category 231 | 232 | // Pop View Controller From Navigation Stack 233 | let _ = navigationController?.popViewController(animated: true) 234 | } 235 | 236 | } 237 | -------------------------------------------------------------------------------- /Notes/Storyboards/Tags.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 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 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 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /Notes/Storyboards/Categories.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 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 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 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 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 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 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 | -------------------------------------------------------------------------------- /Notes.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | CC44B0A91DC7B72C0055E7EA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC44B09E1DC7B72C0055E7EA /* AppDelegate.swift */; }; 11 | CC44B0AB1DC7B72C0055E7EA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CC44B0A31DC7B72C0055E7EA /* Assets.xcassets */; }; 12 | CC44B0AC1DC7B72C0055E7EA /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CC44B0A51DC7B72C0055E7EA /* LaunchScreen.storyboard */; }; 13 | CC44B0AD1DC7B72C0055E7EA /* Notes.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CC44B0A61DC7B72C0055E7EA /* Notes.storyboard */; }; 14 | CC44B0B11DC7B7E10055E7EA /* CoreDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC44B0B01DC7B7E10055E7EA /* CoreDataManager.swift */; }; 15 | CC44B0B51DC7B9CA0055E7EA /* Notes.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = CC44B0B31DC7B9CA0055E7EA /* Notes.xcdatamodeld */; }; 16 | CC647E881DCB4D2A003C872A /* AddNoteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC647E811DCB4D2A003C872A /* AddNoteViewController.swift */; }; 17 | CC647E891DCB4D2A003C872A /* NoteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC647E831DCB4D2A003C872A /* NoteViewController.swift */; }; 18 | CC647E8A1DCB4D2A003C872A /* NotesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC647E851DCB4D2A003C872A /* NotesViewController.swift */; }; 19 | CC647E8B1DCB4D2A003C872A /* NoteTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC647E871DCB4D2A003C872A /* NoteTableViewCell.swift */; }; 20 | CC647E8F1DCB4E20003C872A /* CategoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC647E8E1DCB4E20003C872A /* CategoryViewController.swift */; }; 21 | CC647E921DCB4E4E003C872A /* CategoriesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC647E911DCB4E4E003C872A /* CategoriesViewController.swift */; }; 22 | CC647E951DCB4E95003C872A /* CategoryTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC647E941DCB4E95003C872A /* CategoryTableViewCell.swift */; }; 23 | CC647E9A1DCB4EF6003C872A /* Categories.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CC647E991DCB4EF6003C872A /* Categories.storyboard */; }; 24 | CC647E9D1DCB59E5003C872A /* UIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC647E9C1DCB59E5003C872A /* UIViewController.swift */; }; 25 | CC647E9F1DCB6E3B003C872A /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC647E9E1DCB6E3B003C872A /* UIColor.swift */; }; 26 | CC6C13E81DCC4A2A001FBC87 /* ColorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC6C13E71DCC4A2A001FBC87 /* ColorViewController.swift */; }; 27 | CC6C13EC1DCC82D4001FBC87 /* Category.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC6C13EB1DCC82D4001FBC87 /* Category.swift */; }; 28 | CC6C13EF1DCC9084001FBC87 /* AddCategoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC6C13EE1DCC9084001FBC87 /* AddCategoryViewController.swift */; }; 29 | CCED55FA1DCDB98400D3E951 /* TagsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCED55F91DCDB98400D3E951 /* TagsViewController.swift */; }; 30 | CCED55FD1DCDB9E400D3E951 /* TagViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCED55FC1DCDB9E400D3E951 /* TagViewController.swift */; }; 31 | CCED56001DCDBA4900D3E951 /* AddTagViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCED55FF1DCDBA4900D3E951 /* AddTagViewController.swift */; }; 32 | CCED56031DCDBAF800D3E951 /* TagTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCED56021DCDBAF800D3E951 /* TagTableViewCell.swift */; }; 33 | CCED56051DCDBD4100D3E951 /* Tags.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CCED56041DCDBD4100D3E951 /* Tags.storyboard */; }; 34 | CCF53E171DC9F80700B4450B /* Note.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCF53E161DC9F80700B4450B /* Note.swift */; }; 35 | /* End PBXBuildFile section */ 36 | 37 | /* Begin PBXFileReference section */ 38 | CC44B0881DC7B6DB0055E7EA /* Notes.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Notes.app; sourceTree = BUILT_PRODUCTS_DIR; }; 39 | CC44B09E1DC7B72C0055E7EA /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 40 | CC44B0A31DC7B72C0055E7EA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 41 | CC44B0A51DC7B72C0055E7EA /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; 42 | CC44B0A61DC7B72C0055E7EA /* Notes.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Notes.storyboard; sourceTree = ""; }; 43 | CC44B0A81DC7B72C0055E7EA /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 44 | CC44B0B01DC7B7E10055E7EA /* CoreDataManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreDataManager.swift; sourceTree = ""; }; 45 | CC44B0B41DC7B9CA0055E7EA /* Notes.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Notes.xcdatamodel; sourceTree = ""; }; 46 | CC647E811DCB4D2A003C872A /* AddNoteViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddNoteViewController.swift; sourceTree = ""; }; 47 | CC647E831DCB4D2A003C872A /* NoteViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoteViewController.swift; sourceTree = ""; }; 48 | CC647E851DCB4D2A003C872A /* NotesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotesViewController.swift; sourceTree = ""; }; 49 | CC647E871DCB4D2A003C872A /* NoteTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoteTableViewCell.swift; sourceTree = ""; }; 50 | CC647E8E1DCB4E20003C872A /* CategoryViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CategoryViewController.swift; sourceTree = ""; }; 51 | CC647E911DCB4E4E003C872A /* CategoriesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CategoriesViewController.swift; sourceTree = ""; }; 52 | CC647E941DCB4E95003C872A /* CategoryTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CategoryTableViewCell.swift; sourceTree = ""; }; 53 | CC647E991DCB4EF6003C872A /* Categories.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Categories.storyboard; sourceTree = ""; }; 54 | CC647E9C1DCB59E5003C872A /* UIViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewController.swift; sourceTree = ""; }; 55 | CC647E9E1DCB6E3B003C872A /* UIColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = ""; }; 56 | CC6C13E71DCC4A2A001FBC87 /* ColorViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorViewController.swift; sourceTree = ""; }; 57 | CC6C13E91DCC524C001FBC87 /* Notes 2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Notes 2.xcdatamodel"; sourceTree = ""; }; 58 | CC6C13EB1DCC82D4001FBC87 /* Category.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Category.swift; sourceTree = ""; }; 59 | CC6C13EE1DCC9084001FBC87 /* AddCategoryViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddCategoryViewController.swift; sourceTree = ""; }; 60 | CCED55F91DCDB98400D3E951 /* TagsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TagsViewController.swift; sourceTree = ""; }; 61 | CCED55FC1DCDB9E400D3E951 /* TagViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TagViewController.swift; sourceTree = ""; }; 62 | CCED55FF1DCDBA4900D3E951 /* AddTagViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddTagViewController.swift; sourceTree = ""; }; 63 | CCED56021DCDBAF800D3E951 /* TagTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TagTableViewCell.swift; sourceTree = ""; }; 64 | CCED56041DCDBD4100D3E951 /* Tags.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Tags.storyboard; sourceTree = ""; }; 65 | CCF53E161DC9F80700B4450B /* Note.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Note.swift; sourceTree = ""; }; 66 | /* End PBXFileReference section */ 67 | 68 | /* Begin PBXFrameworksBuildPhase section */ 69 | CC44B0851DC7B6DB0055E7EA /* Frameworks */ = { 70 | isa = PBXFrameworksBuildPhase; 71 | buildActionMask = 2147483647; 72 | files = ( 73 | ); 74 | runOnlyForDeploymentPostprocessing = 0; 75 | }; 76 | /* End PBXFrameworksBuildPhase section */ 77 | 78 | /* Begin PBXGroup section */ 79 | CC44B07F1DC7B6DB0055E7EA = { 80 | isa = PBXGroup; 81 | children = ( 82 | CC44B08A1DC7B6DB0055E7EA /* Notes */, 83 | CC44B0891DC7B6DB0055E7EA /* Products */, 84 | ); 85 | sourceTree = ""; 86 | }; 87 | CC44B0891DC7B6DB0055E7EA /* Products */ = { 88 | isa = PBXGroup; 89 | children = ( 90 | CC44B0881DC7B6DB0055E7EA /* Notes.app */, 91 | ); 92 | name = Products; 93 | sourceTree = ""; 94 | }; 95 | CC44B08A1DC7B6DB0055E7EA /* Notes */ = { 96 | isa = PBXGroup; 97 | children = ( 98 | CC44B09D1DC7B72C0055E7EA /* Application Delegate */, 99 | CC44B09F1DC7B72C0055E7EA /* View Controllers */, 100 | CC44B0A41DC7B72C0055E7EA /* Storyboards */, 101 | CC647E9B1DCB59A9003C872A /* Extensions */, 102 | CC44B0A21DC7B72C0055E7EA /* Resources */, 103 | CC44B0B21DC7B9B20055E7EA /* Core Data */, 104 | CC44B0AF1DC7B7C40055E7EA /* Managers */, 105 | CC44B0A71DC7B72C0055E7EA /* Supporting Files */, 106 | ); 107 | path = Notes; 108 | sourceTree = ""; 109 | }; 110 | CC44B09D1DC7B72C0055E7EA /* Application Delegate */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | CC44B09E1DC7B72C0055E7EA /* AppDelegate.swift */, 114 | ); 115 | path = "Application Delegate"; 116 | sourceTree = ""; 117 | }; 118 | CC44B09F1DC7B72C0055E7EA /* View Controllers */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | CCED55F71DCDB92200D3E951 /* Tags */, 122 | CC647E7F1DCB4D2A003C872A /* Notes */, 123 | CC647E8C1DCB4DC5003C872A /* Categories */, 124 | ); 125 | path = "View Controllers"; 126 | sourceTree = ""; 127 | }; 128 | CC44B0A21DC7B72C0055E7EA /* Resources */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | CC44B0A31DC7B72C0055E7EA /* Assets.xcassets */, 132 | ); 133 | path = Resources; 134 | sourceTree = ""; 135 | }; 136 | CC44B0A41DC7B72C0055E7EA /* Storyboards */ = { 137 | isa = PBXGroup; 138 | children = ( 139 | CCED56041DCDBD4100D3E951 /* Tags.storyboard */, 140 | CC44B0A61DC7B72C0055E7EA /* Notes.storyboard */, 141 | CC647E991DCB4EF6003C872A /* Categories.storyboard */, 142 | CC44B0A51DC7B72C0055E7EA /* LaunchScreen.storyboard */, 143 | ); 144 | path = Storyboards; 145 | sourceTree = ""; 146 | }; 147 | CC44B0A71DC7B72C0055E7EA /* Supporting Files */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | CC44B0A81DC7B72C0055E7EA /* Info.plist */, 151 | ); 152 | path = "Supporting Files"; 153 | sourceTree = ""; 154 | }; 155 | CC44B0AF1DC7B7C40055E7EA /* Managers */ = { 156 | isa = PBXGroup; 157 | children = ( 158 | CC44B0B01DC7B7E10055E7EA /* CoreDataManager.swift */, 159 | ); 160 | path = Managers; 161 | sourceTree = ""; 162 | }; 163 | CC44B0B21DC7B9B20055E7EA /* Core Data */ = { 164 | isa = PBXGroup; 165 | children = ( 166 | CC44B0B31DC7B9CA0055E7EA /* Notes.xcdatamodeld */, 167 | CCF53E151DC9F7EA00B4450B /* Extensions */, 168 | ); 169 | path = "Core Data"; 170 | sourceTree = ""; 171 | }; 172 | CC647E7F1DCB4D2A003C872A /* Notes */ = { 173 | isa = PBXGroup; 174 | children = ( 175 | CC647E821DCB4D2A003C872A /* Note View Controller */, 176 | CC647E841DCB4D2A003C872A /* Notes View Controller */, 177 | CC647E801DCB4D2A003C872A /* Add Note View Controller */, 178 | ); 179 | path = Notes; 180 | sourceTree = ""; 181 | }; 182 | CC647E801DCB4D2A003C872A /* Add Note View Controller */ = { 183 | isa = PBXGroup; 184 | children = ( 185 | CC647E811DCB4D2A003C872A /* AddNoteViewController.swift */, 186 | ); 187 | path = "Add Note View Controller"; 188 | sourceTree = ""; 189 | }; 190 | CC647E821DCB4D2A003C872A /* Note View Controller */ = { 191 | isa = PBXGroup; 192 | children = ( 193 | CC647E831DCB4D2A003C872A /* NoteViewController.swift */, 194 | ); 195 | path = "Note View Controller"; 196 | sourceTree = ""; 197 | }; 198 | CC647E841DCB4D2A003C872A /* Notes View Controller */ = { 199 | isa = PBXGroup; 200 | children = ( 201 | CC647E851DCB4D2A003C872A /* NotesViewController.swift */, 202 | CC647E861DCB4D2A003C872A /* Table View Cells */, 203 | ); 204 | path = "Notes View Controller"; 205 | sourceTree = ""; 206 | }; 207 | CC647E861DCB4D2A003C872A /* Table View Cells */ = { 208 | isa = PBXGroup; 209 | children = ( 210 | CC647E871DCB4D2A003C872A /* NoteTableViewCell.swift */, 211 | ); 212 | path = "Table View Cells"; 213 | sourceTree = ""; 214 | }; 215 | CC647E8C1DCB4DC5003C872A /* Categories */ = { 216 | isa = PBXGroup; 217 | children = ( 218 | CC647E8D1DCB4DF6003C872A /* Category View Controller */, 219 | CC647E901DCB4E2B003C872A /* Categories View Controller */, 220 | CC6C13ED1DCC9084001FBC87 /* Add Category View Controller */, 221 | ); 222 | path = Categories; 223 | sourceTree = ""; 224 | }; 225 | CC647E8D1DCB4DF6003C872A /* Category View Controller */ = { 226 | isa = PBXGroup; 227 | children = ( 228 | CC647E8E1DCB4E20003C872A /* CategoryViewController.swift */, 229 | CC6C13E61DCC4A07001FBC87 /* Color View Controller */, 230 | ); 231 | path = "Category View Controller"; 232 | sourceTree = ""; 233 | }; 234 | CC647E901DCB4E2B003C872A /* Categories View Controller */ = { 235 | isa = PBXGroup; 236 | children = ( 237 | CC647E911DCB4E4E003C872A /* CategoriesViewController.swift */, 238 | CC647E931DCB4E6F003C872A /* Table View Cells */, 239 | ); 240 | path = "Categories View Controller"; 241 | sourceTree = ""; 242 | }; 243 | CC647E931DCB4E6F003C872A /* Table View Cells */ = { 244 | isa = PBXGroup; 245 | children = ( 246 | CC647E941DCB4E95003C872A /* CategoryTableViewCell.swift */, 247 | ); 248 | path = "Table View Cells"; 249 | sourceTree = ""; 250 | }; 251 | CC647E9B1DCB59A9003C872A /* Extensions */ = { 252 | isa = PBXGroup; 253 | children = ( 254 | CC647E9E1DCB6E3B003C872A /* UIColor.swift */, 255 | CC647E9C1DCB59E5003C872A /* UIViewController.swift */, 256 | ); 257 | path = Extensions; 258 | sourceTree = ""; 259 | }; 260 | CC6C13E61DCC4A07001FBC87 /* Color View Controller */ = { 261 | isa = PBXGroup; 262 | children = ( 263 | CC6C13E71DCC4A2A001FBC87 /* ColorViewController.swift */, 264 | ); 265 | path = "Color View Controller"; 266 | sourceTree = ""; 267 | }; 268 | CC6C13ED1DCC9084001FBC87 /* Add Category View Controller */ = { 269 | isa = PBXGroup; 270 | children = ( 271 | CC6C13EE1DCC9084001FBC87 /* AddCategoryViewController.swift */, 272 | ); 273 | path = "Add Category View Controller"; 274 | sourceTree = ""; 275 | }; 276 | CCED55F71DCDB92200D3E951 /* Tags */ = { 277 | isa = PBXGroup; 278 | children = ( 279 | CCED55FB1DCDB9CD00D3E951 /* Tag View Controller */, 280 | CCED55F81DCDB96900D3E951 /* Tags View Controller */, 281 | CCED55FE1DCDBA2B00D3E951 /* Add Tag View Controller */, 282 | ); 283 | path = Tags; 284 | sourceTree = ""; 285 | }; 286 | CCED55F81DCDB96900D3E951 /* Tags View Controller */ = { 287 | isa = PBXGroup; 288 | children = ( 289 | CCED55F91DCDB98400D3E951 /* TagsViewController.swift */, 290 | CCED56011DCDBACD00D3E951 /* Table View Cells */, 291 | ); 292 | path = "Tags View Controller"; 293 | sourceTree = ""; 294 | }; 295 | CCED55FB1DCDB9CD00D3E951 /* Tag View Controller */ = { 296 | isa = PBXGroup; 297 | children = ( 298 | CCED55FC1DCDB9E400D3E951 /* TagViewController.swift */, 299 | ); 300 | path = "Tag View Controller"; 301 | sourceTree = ""; 302 | }; 303 | CCED55FE1DCDBA2B00D3E951 /* Add Tag View Controller */ = { 304 | isa = PBXGroup; 305 | children = ( 306 | CCED55FF1DCDBA4900D3E951 /* AddTagViewController.swift */, 307 | ); 308 | path = "Add Tag View Controller"; 309 | sourceTree = ""; 310 | }; 311 | CCED56011DCDBACD00D3E951 /* Table View Cells */ = { 312 | isa = PBXGroup; 313 | children = ( 314 | CCED56021DCDBAF800D3E951 /* TagTableViewCell.swift */, 315 | ); 316 | path = "Table View Cells"; 317 | sourceTree = ""; 318 | }; 319 | CCF53E151DC9F7EA00B4450B /* Extensions */ = { 320 | isa = PBXGroup; 321 | children = ( 322 | CCF53E161DC9F80700B4450B /* Note.swift */, 323 | CC6C13EB1DCC82D4001FBC87 /* Category.swift */, 324 | ); 325 | path = Extensions; 326 | sourceTree = ""; 327 | }; 328 | /* End PBXGroup section */ 329 | 330 | /* Begin PBXNativeTarget section */ 331 | CC44B0871DC7B6DB0055E7EA /* Notes */ = { 332 | isa = PBXNativeTarget; 333 | buildConfigurationList = CC44B09A1DC7B6DB0055E7EA /* Build configuration list for PBXNativeTarget "Notes" */; 334 | buildPhases = ( 335 | CC44B0841DC7B6DB0055E7EA /* Sources */, 336 | CC44B0851DC7B6DB0055E7EA /* Frameworks */, 337 | CC44B0861DC7B6DB0055E7EA /* Resources */, 338 | ); 339 | buildRules = ( 340 | ); 341 | dependencies = ( 342 | ); 343 | name = Notes; 344 | productName = Notes; 345 | productReference = CC44B0881DC7B6DB0055E7EA /* Notes.app */; 346 | productType = "com.apple.product-type.application"; 347 | }; 348 | /* End PBXNativeTarget section */ 349 | 350 | /* Begin PBXProject section */ 351 | CC44B0801DC7B6DB0055E7EA /* Project object */ = { 352 | isa = PBXProject; 353 | attributes = { 354 | LastSwiftUpdateCheck = 0810; 355 | LastUpgradeCheck = 0810; 356 | ORGANIZATIONNAME = Cocoacasts; 357 | TargetAttributes = { 358 | CC44B0871DC7B6DB0055E7EA = { 359 | CreatedOnToolsVersion = 8.1; 360 | DevelopmentTeam = 2493UGBPKJ; 361 | ProvisioningStyle = Automatic; 362 | }; 363 | }; 364 | }; 365 | buildConfigurationList = CC44B0831DC7B6DB0055E7EA /* Build configuration list for PBXProject "Notes" */; 366 | compatibilityVersion = "Xcode 3.2"; 367 | developmentRegion = English; 368 | hasScannedForEncodings = 0; 369 | knownRegions = ( 370 | en, 371 | Base, 372 | ); 373 | mainGroup = CC44B07F1DC7B6DB0055E7EA; 374 | productRefGroup = CC44B0891DC7B6DB0055E7EA /* Products */; 375 | projectDirPath = ""; 376 | projectRoot = ""; 377 | targets = ( 378 | CC44B0871DC7B6DB0055E7EA /* Notes */, 379 | ); 380 | }; 381 | /* End PBXProject section */ 382 | 383 | /* Begin PBXResourcesBuildPhase section */ 384 | CC44B0861DC7B6DB0055E7EA /* Resources */ = { 385 | isa = PBXResourcesBuildPhase; 386 | buildActionMask = 2147483647; 387 | files = ( 388 | CC44B0AD1DC7B72C0055E7EA /* Notes.storyboard in Resources */, 389 | CCED56051DCDBD4100D3E951 /* Tags.storyboard in Resources */, 390 | CC44B0AB1DC7B72C0055E7EA /* Assets.xcassets in Resources */, 391 | CC647E9A1DCB4EF6003C872A /* Categories.storyboard in Resources */, 392 | CC44B0AC1DC7B72C0055E7EA /* LaunchScreen.storyboard in Resources */, 393 | ); 394 | runOnlyForDeploymentPostprocessing = 0; 395 | }; 396 | /* End PBXResourcesBuildPhase section */ 397 | 398 | /* Begin PBXSourcesBuildPhase section */ 399 | CC44B0841DC7B6DB0055E7EA /* Sources */ = { 400 | isa = PBXSourcesBuildPhase; 401 | buildActionMask = 2147483647; 402 | files = ( 403 | CC6C13EF1DCC9084001FBC87 /* AddCategoryViewController.swift in Sources */, 404 | CC44B0B11DC7B7E10055E7EA /* CoreDataManager.swift in Sources */, 405 | CC647E9F1DCB6E3B003C872A /* UIColor.swift in Sources */, 406 | CCF53E171DC9F80700B4450B /* Note.swift in Sources */, 407 | CC44B0A91DC7B72C0055E7EA /* AppDelegate.swift in Sources */, 408 | CCED55FA1DCDB98400D3E951 /* TagsViewController.swift in Sources */, 409 | CCED56001DCDBA4900D3E951 /* AddTagViewController.swift in Sources */, 410 | CC647E8B1DCB4D2A003C872A /* NoteTableViewCell.swift in Sources */, 411 | CC647E921DCB4E4E003C872A /* CategoriesViewController.swift in Sources */, 412 | CC647E8A1DCB4D2A003C872A /* NotesViewController.swift in Sources */, 413 | CC6C13EC1DCC82D4001FBC87 /* Category.swift in Sources */, 414 | CC647E951DCB4E95003C872A /* CategoryTableViewCell.swift in Sources */, 415 | CC647E9D1DCB59E5003C872A /* UIViewController.swift in Sources */, 416 | CC647E881DCB4D2A003C872A /* AddNoteViewController.swift in Sources */, 417 | CC647E891DCB4D2A003C872A /* NoteViewController.swift in Sources */, 418 | CC647E8F1DCB4E20003C872A /* CategoryViewController.swift in Sources */, 419 | CCED56031DCDBAF800D3E951 /* TagTableViewCell.swift in Sources */, 420 | CCED55FD1DCDB9E400D3E951 /* TagViewController.swift in Sources */, 421 | CC6C13E81DCC4A2A001FBC87 /* ColorViewController.swift in Sources */, 422 | CC44B0B51DC7B9CA0055E7EA /* Notes.xcdatamodeld in Sources */, 423 | ); 424 | runOnlyForDeploymentPostprocessing = 0; 425 | }; 426 | /* End PBXSourcesBuildPhase section */ 427 | 428 | /* Begin XCBuildConfiguration section */ 429 | CC44B0981DC7B6DB0055E7EA /* Debug */ = { 430 | isa = XCBuildConfiguration; 431 | buildSettings = { 432 | ALWAYS_SEARCH_USER_PATHS = NO; 433 | CLANG_ANALYZER_NONNULL = YES; 434 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 435 | CLANG_CXX_LIBRARY = "libc++"; 436 | CLANG_ENABLE_MODULES = YES; 437 | CLANG_ENABLE_OBJC_ARC = YES; 438 | CLANG_WARN_BOOL_CONVERSION = YES; 439 | CLANG_WARN_CONSTANT_CONVERSION = YES; 440 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 441 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 442 | CLANG_WARN_EMPTY_BODY = YES; 443 | CLANG_WARN_ENUM_CONVERSION = YES; 444 | CLANG_WARN_INFINITE_RECURSION = YES; 445 | CLANG_WARN_INT_CONVERSION = YES; 446 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 447 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 448 | CLANG_WARN_UNREACHABLE_CODE = YES; 449 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 450 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 451 | COPY_PHASE_STRIP = NO; 452 | DEBUG_INFORMATION_FORMAT = dwarf; 453 | ENABLE_STRICT_OBJC_MSGSEND = YES; 454 | ENABLE_TESTABILITY = YES; 455 | GCC_C_LANGUAGE_STANDARD = gnu99; 456 | GCC_DYNAMIC_NO_PIC = NO; 457 | GCC_NO_COMMON_BLOCKS = YES; 458 | GCC_OPTIMIZATION_LEVEL = 0; 459 | GCC_PREPROCESSOR_DEFINITIONS = ( 460 | "DEBUG=1", 461 | "$(inherited)", 462 | ); 463 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 464 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 465 | GCC_WARN_UNDECLARED_SELECTOR = YES; 466 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 467 | GCC_WARN_UNUSED_FUNCTION = YES; 468 | GCC_WARN_UNUSED_VARIABLE = YES; 469 | IPHONEOS_DEPLOYMENT_TARGET = 10.1; 470 | MTL_ENABLE_DEBUG_INFO = YES; 471 | ONLY_ACTIVE_ARCH = YES; 472 | SDKROOT = iphoneos; 473 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 474 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 475 | }; 476 | name = Debug; 477 | }; 478 | CC44B0991DC7B6DB0055E7EA /* Release */ = { 479 | isa = XCBuildConfiguration; 480 | buildSettings = { 481 | ALWAYS_SEARCH_USER_PATHS = NO; 482 | CLANG_ANALYZER_NONNULL = YES; 483 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 484 | CLANG_CXX_LIBRARY = "libc++"; 485 | CLANG_ENABLE_MODULES = YES; 486 | CLANG_ENABLE_OBJC_ARC = YES; 487 | CLANG_WARN_BOOL_CONVERSION = YES; 488 | CLANG_WARN_CONSTANT_CONVERSION = YES; 489 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 490 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 491 | CLANG_WARN_EMPTY_BODY = YES; 492 | CLANG_WARN_ENUM_CONVERSION = YES; 493 | CLANG_WARN_INFINITE_RECURSION = YES; 494 | CLANG_WARN_INT_CONVERSION = YES; 495 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 496 | CLANG_WARN_SUSPICIOUS_MOVES = YES; 497 | CLANG_WARN_UNREACHABLE_CODE = YES; 498 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 499 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 500 | COPY_PHASE_STRIP = NO; 501 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 502 | ENABLE_NS_ASSERTIONS = NO; 503 | ENABLE_STRICT_OBJC_MSGSEND = YES; 504 | GCC_C_LANGUAGE_STANDARD = gnu99; 505 | GCC_NO_COMMON_BLOCKS = YES; 506 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 507 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 508 | GCC_WARN_UNDECLARED_SELECTOR = YES; 509 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 510 | GCC_WARN_UNUSED_FUNCTION = YES; 511 | GCC_WARN_UNUSED_VARIABLE = YES; 512 | IPHONEOS_DEPLOYMENT_TARGET = 10.1; 513 | MTL_ENABLE_DEBUG_INFO = NO; 514 | SDKROOT = iphoneos; 515 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 516 | VALIDATE_PRODUCT = YES; 517 | }; 518 | name = Release; 519 | }; 520 | CC44B09B1DC7B6DB0055E7EA /* Debug */ = { 521 | isa = XCBuildConfiguration; 522 | buildSettings = { 523 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 524 | DEVELOPMENT_TEAM = 2493UGBPKJ; 525 | INFOPLIST_FILE = "Notes/Supporting Files/Info.plist"; 526 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 527 | PRODUCT_BUNDLE_IDENTIFIER = com.cocoacasts.Notes; 528 | PRODUCT_NAME = "$(TARGET_NAME)"; 529 | SWIFT_VERSION = 3.0; 530 | }; 531 | name = Debug; 532 | }; 533 | CC44B09C1DC7B6DB0055E7EA /* Release */ = { 534 | isa = XCBuildConfiguration; 535 | buildSettings = { 536 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 537 | DEVELOPMENT_TEAM = 2493UGBPKJ; 538 | INFOPLIST_FILE = "Notes/Supporting Files/Info.plist"; 539 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 540 | PRODUCT_BUNDLE_IDENTIFIER = com.cocoacasts.Notes; 541 | PRODUCT_NAME = "$(TARGET_NAME)"; 542 | SWIFT_VERSION = 3.0; 543 | }; 544 | name = Release; 545 | }; 546 | /* End XCBuildConfiguration section */ 547 | 548 | /* Begin XCConfigurationList section */ 549 | CC44B0831DC7B6DB0055E7EA /* Build configuration list for PBXProject "Notes" */ = { 550 | isa = XCConfigurationList; 551 | buildConfigurations = ( 552 | CC44B0981DC7B6DB0055E7EA /* Debug */, 553 | CC44B0991DC7B6DB0055E7EA /* Release */, 554 | ); 555 | defaultConfigurationIsVisible = 0; 556 | defaultConfigurationName = Release; 557 | }; 558 | CC44B09A1DC7B6DB0055E7EA /* Build configuration list for PBXNativeTarget "Notes" */ = { 559 | isa = XCConfigurationList; 560 | buildConfigurations = ( 561 | CC44B09B1DC7B6DB0055E7EA /* Debug */, 562 | CC44B09C1DC7B6DB0055E7EA /* Release */, 563 | ); 564 | defaultConfigurationIsVisible = 0; 565 | defaultConfigurationName = Release; 566 | }; 567 | /* End XCConfigurationList section */ 568 | 569 | /* Begin XCVersionGroup section */ 570 | CC44B0B31DC7B9CA0055E7EA /* Notes.xcdatamodeld */ = { 571 | isa = XCVersionGroup; 572 | children = ( 573 | CC6C13E91DCC524C001FBC87 /* Notes 2.xcdatamodel */, 574 | CC44B0B41DC7B9CA0055E7EA /* Notes.xcdatamodel */, 575 | ); 576 | currentVersion = CC6C13E91DCC524C001FBC87 /* Notes 2.xcdatamodel */; 577 | path = Notes.xcdatamodeld; 578 | sourceTree = ""; 579 | versionGroupType = wrapper.xcdatamodel; 580 | }; 581 | /* End XCVersionGroup section */ 582 | }; 583 | rootObject = CC44B0801DC7B6DB0055E7EA /* Project object */; 584 | } 585 | -------------------------------------------------------------------------------- /Notes/Storyboards/Notes.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 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 50 | 56 | 62 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 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 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 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 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 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 | 229 | 236 | 242 | 248 | 255 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | --------------------------------------------------------------------------------