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