├── NoteTaker
├── Assets.xcassets
│ ├── Contents.json
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── NoteTaker.entitlements
├── TableCellEditing.swift
├── ValidateNoteDeletion.swift
├── Note.swift
├── ToolbarButton.swift
├── TableViewDataSource.swift
├── TableViewController.swift
├── Info.plist
├── AppDelegate.swift
├── TextViewController.swift
├── SplitViewController.swift
├── FileSavingandOpening.swift
└── Base.lproj
│ └── Main.storyboard
├── NoteTakerTests
├── Info.plist
└── NoteTakerTests.swift
├── NoteTakerUITests
├── Info.plist
└── NoteTakerUITests.swift
├── README.md
└── NoteTaker.xcodeproj
└── project.pbxproj
/NoteTaker/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/NoteTaker/NoteTaker.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/NoteTaker/TableCellEditing.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TableCellEditing.swift
3 | // NoteTaker
4 | //
5 | // Created by mark on 6/29/19.
6 | // Copyright © 2019 Swift Dev Journal. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | extension TableViewController: NSControlTextEditingDelegate {
12 |
13 | // I have to implement NSControlTextEditingDelegate's textShouldEndEditing method to change the note title by editing its name in the table view.
14 | func control(_ control: NSControl, textShouldEndEditing fieldEditor: NSText) -> Bool {
15 | let selectedRow = tableView.selectedRow
16 | splitViewController?.notes[selectedRow].title = fieldEditor.string
17 | splitViewController?.saveNotes()
18 | return true
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/NoteTaker/ValidateNoteDeletion.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ValidateNoteDeletion.swift
3 | // NoteTaker
4 | //
5 | // Created by mark on 8/9/19.
6 | // Copyright © 2019 Swift Dev Journal. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | // Validate the Delete Note menu item and toolbar button so they are disabled if the notes array is empty.
12 |
13 | extension SplitViewController {
14 |
15 | override func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool {
16 | switch item.action {
17 | case #selector(deleteNote(_:))?:
18 | return !(notes.isEmpty)
19 | // Add any other items you want to validate, such as additional menu items.
20 | default:
21 | return true
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/NoteTakerTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/NoteTakerUITests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/NoteTaker/Note.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Note.swift
3 | // NoteTaker
4 | //
5 | // Created by mark on 6/28/19.
6 | // Copyright © 2019 Swift Dev Journal. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct Note {
12 | var title: String = ""
13 | var contents: NSMutableAttributedString = NSMutableAttributedString(string: "")
14 |
15 | func write() -> Data? {
16 | let range = NSRange(location: 0, length: contents.length)
17 | return contents.rtf(from: range)
18 | }
19 |
20 | mutating func read(data: Data?) {
21 | guard data != nil else {
22 | return
23 | }
24 | // The guard ensures the data is not nil so I can force unwrap.
25 | if let fileContents = NSAttributedString(rtf: data!, documentAttributes: nil) {
26 | contents = NSMutableAttributedString(attributedString: fileContents)
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/NoteTakerTests/NoteTakerTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NoteTakerTests.swift
3 | // NoteTakerTests
4 | //
5 | // Created by mark on 6/26/19.
6 | // Copyright © 2019 Swift Dev Journal. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import NoteTaker
11 |
12 | class NoteTakerTests: XCTestCase {
13 |
14 | override func setUp() {
15 | // Put setup code here. This method is called before the invocation of each test method in the class.
16 | }
17 |
18 | override func tearDown() {
19 | // Put teardown code here. This method is called after the invocation of each test method in the class.
20 | }
21 |
22 | func testExample() {
23 | // This is an example of a functional test case.
24 | // Use XCTAssert and related functions to verify your tests produce the correct results.
25 | }
26 |
27 | func testPerformanceExample() {
28 | // This is an example of a performance test case.
29 | self.measure {
30 | // Put the code you want to measure the time of here.
31 | }
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/NoteTaker/ToolbarButton.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ToolbarButton.swift
3 | // NoteTaker
4 | //
5 | // Created by mark on 8/9/19.
6 | // Copyright © 2019 Swift Dev Journal. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class ToolbarButton: NSToolbarItem {
12 |
13 | // This code is from the answer I found on Stack Overflow
14 | // NSToolbarItem validation in relevant controller
15 | // URL: https:// stackoverflow.com/questions/42470645/nstoolbaritem-validation-in-relevant-controller
16 | override func validate() {
17 | if let control = self.view as? NSControl,
18 | let action = self.action,
19 | let validator = NSApp.target(forAction: action, to: self.target, from: self) as AnyObject? {
20 |
21 | switch validator {
22 | case let validator as NSUserInterfaceValidations:
23 | control.isEnabled = validator.validateUserInterfaceItem(self)
24 | default:
25 | control.isEnabled = validator.validateToolbarItem(self)
26 | }
27 |
28 | } else {
29 | super.validate()
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/NoteTaker/TableViewDataSource.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TableViewDataSource.swift
3 | // NoteTaker
4 | //
5 | // Created by mark on 6/29/19.
6 | // Copyright © 2019 Swift Dev Journal. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | extension TableViewController: NSTableViewDelegate, NSTableViewDataSource {
12 |
13 | func numberOfRows(in aTableView: NSTableView) -> Int {
14 | if let splitViewController = parent as? SplitViewController {
15 | return splitViewController.notes.count
16 | }
17 | return 0
18 | }
19 |
20 | func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
21 |
22 | guard let cellView = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "TitleColumn"), owner: self) as? NSTableCellView else {
23 |
24 | return nil
25 | }
26 |
27 | if let splitViewController = parent as? SplitViewController {
28 | cellView.textField?.stringValue = splitViewController.notes[row].title
29 | }
30 |
31 | return cellView
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/NoteTaker/TableViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TableViewController.swift
3 | // NoteTaker
4 | //
5 | // Created by mark on 6/28/19.
6 | // Copyright © 2019 Swift Dev Journal. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class TableViewController: NSViewController {
12 |
13 | @IBOutlet var tableView: NSTableView!
14 |
15 | var splitViewController: SplitViewController? {
16 | return parent as? SplitViewController
17 | }
18 |
19 | override func viewDidLoad() {
20 | super.viewDidLoad()
21 | // Do view setup here.
22 | }
23 |
24 | func tableViewSelectionDidChange(_ aNotification: Notification) {
25 | // Get the selected note and change the text view's contents to the selected note.
26 | if let textViewController = splitViewController?.textViewController {
27 | textViewController.changeTextViewContents(selectedRow: tableView.selectedRow)
28 | }
29 | }
30 |
31 | func selectNote(_ position: Int) {
32 | let indexSet = IndexSet(integer: position)
33 | tableView?.selectRowIndexes(indexSet, byExtendingSelection: false)
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/NoteTaker/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleVersion
22 | 1
23 | LSMinimumSystemVersion
24 | $(MACOSX_DEPLOYMENT_TARGET)
25 | NSHumanReadableCopyright
26 | Copyright © 2019 Swift Dev Journal. All rights reserved.
27 | NSMainStoryboardFile
28 | Main
29 | NSPrincipalClass
30 | NSApplication
31 |
32 |
33 |
--------------------------------------------------------------------------------
/NoteTaker/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "size" : "16x16",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "size" : "16x16",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "size" : "32x32",
16 | "scale" : "1x"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "size" : "32x32",
21 | "scale" : "2x"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "size" : "128x128",
26 | "scale" : "1x"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "size" : "128x128",
31 | "scale" : "2x"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "size" : "256x256",
36 | "scale" : "1x"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "size" : "256x256",
41 | "scale" : "2x"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "size" : "512x512",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "size" : "512x512",
51 | "scale" : "2x"
52 | }
53 | ],
54 | "info" : {
55 | "version" : 1,
56 | "author" : "xcode"
57 | }
58 | }
--------------------------------------------------------------------------------
/NoteTakerUITests/NoteTakerUITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NoteTakerUITests.swift
3 | // NoteTakerUITests
4 | //
5 | // Created by mark on 6/26/19.
6 | // Copyright © 2019 Swift Dev Journal. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class NoteTakerUITests: XCTestCase {
12 |
13 | override func setUp() {
14 | // Put setup code here. This method is called before the invocation of each test method in the class.
15 |
16 | // In UI tests it is usually best to stop immediately when a failure occurs.
17 | continueAfterFailure = false
18 |
19 | // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method.
20 | XCUIApplication().launch()
21 |
22 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
23 | }
24 |
25 | override func tearDown() {
26 | // Put teardown code here. This method is called after the invocation of each test method in the class.
27 | }
28 |
29 | func testExample() {
30 | // Use recording to get started writing UI tests.
31 | // Use XCTAssert and related functions to verify your tests produce the correct results.
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/NoteTaker/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // NoteTaker
4 | //
5 | // Created by mark on 6/26/19.
6 | // Copyright © 2019 Swift Dev Journal. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | @NSApplicationMain
12 | class AppDelegate: NSObject, NSApplicationDelegate {
13 |
14 | var saveTimer = Timer()
15 |
16 | func applicationDidFinishLaunching(_ aNotification: Notification) {
17 | // Insert code here to initialize your application
18 | startTimer()
19 | }
20 |
21 | func applicationWillTerminate(_ aNotification: Notification) {
22 | // Insert code here to tear down your application
23 | stopTimer()
24 | }
25 |
26 | func applicationDidBecomeActive(_ notification: Notification) {
27 | startTimer()
28 | }
29 |
30 | func applicationDidResignActive(_ notification: Notification) {
31 | stopTimer()
32 | }
33 |
34 | func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
35 | return true
36 | }
37 |
38 | // MARK: Timer functions
39 | func startTimer() {
40 | // Autosave every 30 seconds.
41 | let app = NSApplication.shared
42 | if let splitViewController = app.windows.first?.contentViewController as? SplitViewController {
43 |
44 | saveTimer = Timer.scheduledTimer(timeInterval: 30.0, target: splitViewController, selector: #selector(SplitViewController.saveNotes), userInfo: nil, repeats: true)
45 | }
46 |
47 | }
48 |
49 | func stopTimer() {
50 | saveTimer.invalidate()
51 | }
52 |
53 | }
54 |
55 |
--------------------------------------------------------------------------------
/NoteTaker/TextViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TextViewController.swift
3 | // NoteTaker
4 | //
5 | // Created by mark on 6/28/19.
6 | // Copyright © 2019 Swift Dev Journal. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class TextViewController: NSViewController, NSTextDelegate {
12 |
13 | @IBOutlet var textView: NSTextView!
14 |
15 | var currentNoteLocation = 0
16 |
17 | override func viewDidLoad() {
18 | super.viewDidLoad()
19 | // Do view setup here.
20 |
21 | }
22 |
23 | func changeTextViewContents(selectedRow: Int) {
24 | // Using an if-let statement instead of having a computed property for the split view controller that the table view controller has. Using the computed property forced me to do force unwrapping to fill the text view with the selected note's contents. I prefer the if-let statement to force unwrapping.
25 | if let splitViewController = parent as? SplitViewController {
26 | let selectedNote = splitViewController.notes[selectedRow]
27 | // Fill text view with the selected note contents
28 | textView.textStorage?.setAttributedString(selectedNote.contents)
29 | currentNoteLocation = selectedRow
30 | }
31 | }
32 |
33 | func saveTextViewContents() {
34 | if let splitViewController = parent as? SplitViewController {
35 | // The note will have the text view's contents.
36 | splitViewController.notes[currentNoteLocation].contents.setAttributedString(textView.attributedString())
37 | }
38 | }
39 |
40 | func textDidChange(_ notification: Notification) {
41 | saveTextViewContents()
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NoteTaker
2 |
3 | A simple Mac note-taking app that is the project for the book [Swift Dev Journal Introduction to Cocoa](https://www.swiftdevjournal.com/swift-dev-journal-intro-to-cocoa/).
4 |
5 | I wrote the code in Xcode 10 using Swift 5. The code should also work in Xcode 11.
6 |
7 | ## Branches
8 |
9 | The `master` and `develop` branches contain the final app.
10 |
11 | ### Chapter Branches
12 |
13 | The various chapter branches show milestones along the way so you can see the steps I took from the start of the project to the final app.
14 |
15 | The `chapter04` branch shows the app after building the user interface for the notes window.
16 |
17 | The `chapter05` branch adds a toolbar to the window.
18 |
19 | The `chapter06` branch adds a Note menu to the menu bar with items to add and remove notes.
20 |
21 | The `chapter12` branch adds the ability to add notes, change a note's title, and show the currently selected note in the text view. I screwed up a merge from a feature branch so I wasn't to break this branch down into finer steps. This branch is the final result of working through Chapters 7-12 and 14.
22 |
23 | The `chapter13` branch adds note deletion.
24 |
25 | The `chapter15` branch adds note saving and loading.
26 |
27 | The `chapter16` branch adds autosaving using timers.
28 |
29 | The `chapter17` branch adds undo for deleting notes.
30 |
31 | ### Demonstration Branches
32 |
33 | The `plain-text` branch has a plain text version of the NoteTaker app.
34 |
35 | The `note-sorting` branch sorts the notes in the table view alphabetically by their title.
36 |
37 | The `toolbar-button-validation` branch shows how to validate toolbar button so they're enabled and disabled properly.
38 |
--------------------------------------------------------------------------------
/NoteTaker/SplitViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SplitViewController.swift
3 | // NoteTaker
4 | //
5 | // Created by mark on 6/29/19.
6 | // Copyright © 2019 Swift Dev Journal. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class SplitViewController: NSSplitViewController {
12 |
13 | // I'm storing the notes in the split view controller because it's a simple app and the split view controller contains the list of notes. You could create a NoteController class to manage the array of notes to avoid polluting the view controller with code that doesn't manage the split view.
14 | var notes = [Note]()
15 |
16 | var tableViewController: TableViewController? {
17 | return children.first as? TableViewController
18 | }
19 |
20 | var textViewController: TextViewController? {
21 | return children[1] as? TextViewController
22 | }
23 |
24 | override func viewDidLoad() {
25 | super.viewDidLoad()
26 | // Do view setup here.
27 | loadNotes()
28 | tableViewController?.tableView.reloadData()
29 | }
30 |
31 | @IBAction func addNote(_ sender: AnyObject) {
32 | guard let row = tableViewController?.tableView.selectedRow else {
33 | return
34 | }
35 |
36 | let newNote = Note(title: "New Note", contents: NSMutableAttributedString(string: ""))
37 | // Insert the new note after the selected note.
38 | let notePosition = row + 1
39 | notes.insert(newNote, at: notePosition)
40 |
41 | saveNotes()
42 | tableViewController?.tableView.reloadData()
43 | tableViewController?.selectNote(notePosition)
44 | }
45 |
46 | @IBAction func deleteNote(_ sender: AnyObject) {
47 | guard let row = tableViewController?.tableView.selectedRow else {
48 | return
49 | }
50 |
51 | // Register the Delete Note undo action with the undo manager.
52 | let noteToDelete = notes[row]
53 | undoManager?.registerUndo(withTarget: self, handler: { targetSelf in
54 | targetSelf.restoreNote(noteToDelete, location: row)
55 | })
56 | undoManager?.setActionName("Delete Note")
57 |
58 | notes.remove(at: row)
59 | tableViewController?.tableView.reloadData()
60 |
61 | // Select the note that was above the deleted note. Selecting the note that was below the deleted note won't work if the last note is deleted.
62 | let newSelectionIndex: Int
63 | if row > 0 {
64 | newSelectionIndex = row - 1
65 | }
66 | else {
67 | newSelectionIndex = row
68 | }
69 | tableViewController?.selectNote(newSelectionIndex)
70 | saveNotes()
71 | }
72 |
73 | func restoreNote(_ deletedNote: Note, location: Int) {
74 | // Register the redo action with the undo manager.
75 | undoManager?.registerUndo(withTarget: self) { targetSelf in
76 | targetSelf.deleteNote(self)
77 | }
78 | undoManager?.setActionName("Delete Note")
79 |
80 | notes.insert(deletedNote, at: location)
81 | tableViewController?.tableView.reloadData()
82 | tableViewController?.selectNote(location)
83 | saveNotes()
84 | }
85 |
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/NoteTaker/FileSavingandOpening.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FileSavingandOpening.swift
3 | // NoteTaker
4 | //
5 | // Created by mark on 7/22/19.
6 | // Copyright © 2019 Swift Dev Journal. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | // I placed the file saving and loading code in a class extension so I could move the code out of the SplitViewController class more easily if the split view controller was doing too much.
12 | extension SplitViewController {
13 |
14 | @objc func saveNotes() {
15 | let wrapper = buildFileWrapper()
16 | // Get the URL to save the file wrapper
17 | // Inside the user's Application Support folder
18 | if let saveLocation = noteFileLocation() {
19 | // Write the file wrapper to the URL
20 | do {
21 | try wrapper.write(to: saveLocation, options: .atomic, originalContentsURL: saveLocation)
22 | } catch {
23 | print(error)
24 | }
25 | }
26 |
27 | }
28 |
29 | func buildFileWrapper() -> FileWrapper {
30 | let mainDirectory = FileWrapper(directoryWithFileWrappers: [:])
31 | let notesDirectory = FileWrapper(directoryWithFileWrappers: [:])
32 | notesDirectory.preferredFilename = "Notes"
33 |
34 | var index = 0
35 | for note in notes {
36 | if let noteData = note.write() {
37 | let wrapper = FileWrapper(regularFileWithContents: noteData)
38 | let filename = buildFilename(note: note, position: index)
39 | wrapper.preferredFilename = filename
40 | notesDirectory.addFileWrapper(wrapper)
41 | }
42 | index += 1
43 | }
44 |
45 | mainDirectory.addFileWrapper(notesDirectory)
46 | return mainDirectory
47 | }
48 |
49 | func buildFilename(note: Note, position: Int) -> String {
50 | var filename: String = ""
51 | filename += note.title
52 | filename += "-00"
53 | filename += String(position)
54 | return filename
55 | }
56 |
57 | func loadNotes() {
58 | // Find the file wrapper
59 | guard let noteLocation = noteFileLocation() else {
60 | return
61 | }
62 |
63 | do {
64 | let mainDirectory = try FileWrapper(url: noteLocation, options: .immediate)
65 | if let notesDirectory = mainDirectory.fileWrappers?["Notes"],
66 | let noteFiles = notesDirectory.fileWrappers {
67 |
68 | for note in noteFiles {
69 | readNote(noteFile: note.value)
70 | }
71 | }
72 | } catch {
73 | print(error)
74 | }
75 | }
76 |
77 | func readNote(noteFile: FileWrapper) {
78 | if let filename = noteFile.filename {
79 | let fileComponents = filename.components(separatedBy: "-00")
80 | var newNote = Note(title: "Note", contents: NSMutableAttributedString(string: ""))
81 | newNote.title = fileComponents.first ?? "Note"
82 | // Load an empty string if there's no data to read.
83 | let emptyString = ""
84 | newNote.read(data: noteFile.regularFileContents ?? emptyString.data(using: .utf8))
85 | notes.append(newNote)
86 | }
87 |
88 | }
89 |
90 | func noteFileLocation() -> URL? {
91 | let fileManager = FileManager.default
92 |
93 | do {
94 | // Could use .localDomainMask if I want to use the machine's Application Support folder.
95 | let userApplicationSupportFolder = try fileManager.url(
96 | for: .applicationSupportDirectory,
97 | in: .userDomainMask, appropriateFor: nil, create: true)
98 | let noteFileLocation = userApplicationSupportFolder.appendingPathComponent("NoteTaker")
99 | do {
100 | // Create a directory to place the notes the first time the app launches.
101 | try fileManager.createDirectory(at: noteFileLocation, withIntermediateDirectories: true, attributes: nil)
102 | } catch {
103 | print("Failed")
104 | }
105 | return noteFileLocation
106 | } catch {
107 | print("Failed")
108 | }
109 | return nil
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/NoteTaker.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 4D1A9D0C22E6855300E27102 /* FileSavingandOpening.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D1A9D0B22E6855300E27102 /* FileSavingandOpening.swift */; };
11 | 4DEE61E422C70B130084E4B9 /* Note.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DEE61E322C70B130084E4B9 /* Note.swift */; };
12 | 4DEE61E622C70C220084E4B9 /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DEE61E522C70C220084E4B9 /* TableViewController.swift */; };
13 | 4DEE61E822C70C8C0084E4B9 /* TextViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DEE61E722C70C8C0084E4B9 /* TextViewController.swift */; };
14 | 4DEE61EA22C7E9540084E4B9 /* SplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DEE61E922C7E9540084E4B9 /* SplitViewController.swift */; };
15 | 4DEE61EC22C7EBE00084E4B9 /* TableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DEE61EB22C7EBE00084E4B9 /* TableViewDataSource.swift */; };
16 | 4DEE61EE22C8496D0084E4B9 /* TableCellEditing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DEE61ED22C8496D0084E4B9 /* TableCellEditing.swift */; };
17 | 4DFBA51C22C3FB5D0059F845 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DFBA51B22C3FB5D0059F845 /* AppDelegate.swift */; };
18 | 4DFBA52022C3FB5E0059F845 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4DFBA51F22C3FB5E0059F845 /* Assets.xcassets */; };
19 | 4DFBA52322C3FB5E0059F845 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4DFBA52122C3FB5E0059F845 /* Main.storyboard */; };
20 | 4DFBA52F22C3FB5F0059F845 /* NoteTakerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DFBA52E22C3FB5F0059F845 /* NoteTakerTests.swift */; };
21 | 4DFBA53A22C3FB5F0059F845 /* NoteTakerUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DFBA53922C3FB5F0059F845 /* NoteTakerUITests.swift */; };
22 | 4DFC89AB22FDFFD4008136E6 /* ValidateNoteDeletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4DFC89AA22FDFFD4008136E6 /* ValidateNoteDeletion.swift */; };
23 | /* End PBXBuildFile section */
24 |
25 | /* Begin PBXContainerItemProxy section */
26 | 4DFBA52B22C3FB5F0059F845 /* PBXContainerItemProxy */ = {
27 | isa = PBXContainerItemProxy;
28 | containerPortal = 4DFBA51022C3FB5D0059F845 /* Project object */;
29 | proxyType = 1;
30 | remoteGlobalIDString = 4DFBA51722C3FB5D0059F845;
31 | remoteInfo = NoteTaker;
32 | };
33 | 4DFBA53622C3FB5F0059F845 /* PBXContainerItemProxy */ = {
34 | isa = PBXContainerItemProxy;
35 | containerPortal = 4DFBA51022C3FB5D0059F845 /* Project object */;
36 | proxyType = 1;
37 | remoteGlobalIDString = 4DFBA51722C3FB5D0059F845;
38 | remoteInfo = NoteTaker;
39 | };
40 | /* End PBXContainerItemProxy section */
41 |
42 | /* Begin PBXFileReference section */
43 | 4D1A9D0B22E6855300E27102 /* FileSavingandOpening.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileSavingandOpening.swift; sourceTree = ""; };
44 | 4DEE61E322C70B130084E4B9 /* Note.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Note.swift; sourceTree = ""; };
45 | 4DEE61E522C70C220084E4B9 /* TableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewController.swift; sourceTree = ""; };
46 | 4DEE61E722C70C8C0084E4B9 /* TextViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextViewController.swift; sourceTree = ""; };
47 | 4DEE61E922C7E9540084E4B9 /* SplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitViewController.swift; sourceTree = ""; };
48 | 4DEE61EB22C7EBE00084E4B9 /* TableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewDataSource.swift; sourceTree = ""; };
49 | 4DEE61ED22C8496D0084E4B9 /* TableCellEditing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableCellEditing.swift; sourceTree = ""; };
50 | 4DFBA51822C3FB5D0059F845 /* NoteTaker.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NoteTaker.app; sourceTree = BUILT_PRODUCTS_DIR; };
51 | 4DFBA51B22C3FB5D0059F845 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
52 | 4DFBA51F22C3FB5E0059F845 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
53 | 4DFBA52222C3FB5E0059F845 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
54 | 4DFBA52422C3FB5E0059F845 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
55 | 4DFBA52522C3FB5E0059F845 /* NoteTaker.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NoteTaker.entitlements; sourceTree = ""; };
56 | 4DFBA52A22C3FB5F0059F845 /* NoteTakerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NoteTakerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
57 | 4DFBA52E22C3FB5F0059F845 /* NoteTakerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteTakerTests.swift; sourceTree = ""; };
58 | 4DFBA53022C3FB5F0059F845 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
59 | 4DFBA53522C3FB5F0059F845 /* NoteTakerUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NoteTakerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
60 | 4DFBA53922C3FB5F0059F845 /* NoteTakerUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteTakerUITests.swift; sourceTree = ""; };
61 | 4DFBA53B22C3FB5F0059F845 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
62 | 4DFC89AA22FDFFD4008136E6 /* ValidateNoteDeletion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValidateNoteDeletion.swift; sourceTree = ""; };
63 | /* End PBXFileReference section */
64 |
65 | /* Begin PBXFrameworksBuildPhase section */
66 | 4DFBA51522C3FB5D0059F845 /* Frameworks */ = {
67 | isa = PBXFrameworksBuildPhase;
68 | buildActionMask = 2147483647;
69 | files = (
70 | );
71 | runOnlyForDeploymentPostprocessing = 0;
72 | };
73 | 4DFBA52722C3FB5F0059F845 /* Frameworks */ = {
74 | isa = PBXFrameworksBuildPhase;
75 | buildActionMask = 2147483647;
76 | files = (
77 | );
78 | runOnlyForDeploymentPostprocessing = 0;
79 | };
80 | 4DFBA53222C3FB5F0059F845 /* Frameworks */ = {
81 | isa = PBXFrameworksBuildPhase;
82 | buildActionMask = 2147483647;
83 | files = (
84 | );
85 | runOnlyForDeploymentPostprocessing = 0;
86 | };
87 | /* End PBXFrameworksBuildPhase section */
88 |
89 | /* Begin PBXGroup section */
90 | 4DFBA50F22C3FB5D0059F845 = {
91 | isa = PBXGroup;
92 | children = (
93 | 4DFBA51A22C3FB5D0059F845 /* NoteTaker */,
94 | 4DFBA52D22C3FB5F0059F845 /* NoteTakerTests */,
95 | 4DFBA53822C3FB5F0059F845 /* NoteTakerUITests */,
96 | 4DFBA51922C3FB5D0059F845 /* Products */,
97 | );
98 | sourceTree = "";
99 | };
100 | 4DFBA51922C3FB5D0059F845 /* Products */ = {
101 | isa = PBXGroup;
102 | children = (
103 | 4DFBA51822C3FB5D0059F845 /* NoteTaker.app */,
104 | 4DFBA52A22C3FB5F0059F845 /* NoteTakerTests.xctest */,
105 | 4DFBA53522C3FB5F0059F845 /* NoteTakerUITests.xctest */,
106 | );
107 | name = Products;
108 | sourceTree = "";
109 | };
110 | 4DFBA51A22C3FB5D0059F845 /* NoteTaker */ = {
111 | isa = PBXGroup;
112 | children = (
113 | 4DFBA51B22C3FB5D0059F845 /* AppDelegate.swift */,
114 | 4DFBA51F22C3FB5E0059F845 /* Assets.xcassets */,
115 | 4D1A9D0B22E6855300E27102 /* FileSavingandOpening.swift */,
116 | 4DFBA52422C3FB5E0059F845 /* Info.plist */,
117 | 4DFBA52122C3FB5E0059F845 /* Main.storyboard */,
118 | 4DEE61E322C70B130084E4B9 /* Note.swift */,
119 | 4DEE61E922C7E9540084E4B9 /* SplitViewController.swift */,
120 | 4DEE61ED22C8496D0084E4B9 /* TableCellEditing.swift */,
121 | 4DEE61E522C70C220084E4B9 /* TableViewController.swift */,
122 | 4DEE61EB22C7EBE00084E4B9 /* TableViewDataSource.swift */,
123 | 4DEE61E722C70C8C0084E4B9 /* TextViewController.swift */,
124 | 4DFC89AA22FDFFD4008136E6 /* ValidateNoteDeletion.swift */,
125 | 4DFBA52522C3FB5E0059F845 /* NoteTaker.entitlements */,
126 | );
127 | path = NoteTaker;
128 | sourceTree = "";
129 | };
130 | 4DFBA52D22C3FB5F0059F845 /* NoteTakerTests */ = {
131 | isa = PBXGroup;
132 | children = (
133 | 4DFBA52E22C3FB5F0059F845 /* NoteTakerTests.swift */,
134 | 4DFBA53022C3FB5F0059F845 /* Info.plist */,
135 | );
136 | path = NoteTakerTests;
137 | sourceTree = "";
138 | };
139 | 4DFBA53822C3FB5F0059F845 /* NoteTakerUITests */ = {
140 | isa = PBXGroup;
141 | children = (
142 | 4DFBA53922C3FB5F0059F845 /* NoteTakerUITests.swift */,
143 | 4DFBA53B22C3FB5F0059F845 /* Info.plist */,
144 | );
145 | path = NoteTakerUITests;
146 | sourceTree = "";
147 | };
148 | /* End PBXGroup section */
149 |
150 | /* Begin PBXNativeTarget section */
151 | 4DFBA51722C3FB5D0059F845 /* NoteTaker */ = {
152 | isa = PBXNativeTarget;
153 | buildConfigurationList = 4DFBA53E22C3FB5F0059F845 /* Build configuration list for PBXNativeTarget "NoteTaker" */;
154 | buildPhases = (
155 | 4DFBA51422C3FB5D0059F845 /* Sources */,
156 | 4DFBA51522C3FB5D0059F845 /* Frameworks */,
157 | 4DFBA51622C3FB5D0059F845 /* Resources */,
158 | );
159 | buildRules = (
160 | );
161 | dependencies = (
162 | );
163 | name = NoteTaker;
164 | productName = NoteTaker;
165 | productReference = 4DFBA51822C3FB5D0059F845 /* NoteTaker.app */;
166 | productType = "com.apple.product-type.application";
167 | };
168 | 4DFBA52922C3FB5F0059F845 /* NoteTakerTests */ = {
169 | isa = PBXNativeTarget;
170 | buildConfigurationList = 4DFBA54122C3FB5F0059F845 /* Build configuration list for PBXNativeTarget "NoteTakerTests" */;
171 | buildPhases = (
172 | 4DFBA52622C3FB5F0059F845 /* Sources */,
173 | 4DFBA52722C3FB5F0059F845 /* Frameworks */,
174 | 4DFBA52822C3FB5F0059F845 /* Resources */,
175 | );
176 | buildRules = (
177 | );
178 | dependencies = (
179 | 4DFBA52C22C3FB5F0059F845 /* PBXTargetDependency */,
180 | );
181 | name = NoteTakerTests;
182 | productName = NoteTakerTests;
183 | productReference = 4DFBA52A22C3FB5F0059F845 /* NoteTakerTests.xctest */;
184 | productType = "com.apple.product-type.bundle.unit-test";
185 | };
186 | 4DFBA53422C3FB5F0059F845 /* NoteTakerUITests */ = {
187 | isa = PBXNativeTarget;
188 | buildConfigurationList = 4DFBA54422C3FB5F0059F845 /* Build configuration list for PBXNativeTarget "NoteTakerUITests" */;
189 | buildPhases = (
190 | 4DFBA53122C3FB5F0059F845 /* Sources */,
191 | 4DFBA53222C3FB5F0059F845 /* Frameworks */,
192 | 4DFBA53322C3FB5F0059F845 /* Resources */,
193 | );
194 | buildRules = (
195 | );
196 | dependencies = (
197 | 4DFBA53722C3FB5F0059F845 /* PBXTargetDependency */,
198 | );
199 | name = NoteTakerUITests;
200 | productName = NoteTakerUITests;
201 | productReference = 4DFBA53522C3FB5F0059F845 /* NoteTakerUITests.xctest */;
202 | productType = "com.apple.product-type.bundle.ui-testing";
203 | };
204 | /* End PBXNativeTarget section */
205 |
206 | /* Begin PBXProject section */
207 | 4DFBA51022C3FB5D0059F845 /* Project object */ = {
208 | isa = PBXProject;
209 | attributes = {
210 | LastSwiftUpdateCheck = 1020;
211 | LastUpgradeCheck = 1020;
212 | ORGANIZATIONNAME = "Swift Dev Journal";
213 | TargetAttributes = {
214 | 4DFBA51722C3FB5D0059F845 = {
215 | CreatedOnToolsVersion = 10.2.1;
216 | SystemCapabilities = {
217 | com.apple.Sandbox = {
218 | enabled = 0;
219 | };
220 | };
221 | };
222 | 4DFBA52922C3FB5F0059F845 = {
223 | CreatedOnToolsVersion = 10.2.1;
224 | TestTargetID = 4DFBA51722C3FB5D0059F845;
225 | };
226 | 4DFBA53422C3FB5F0059F845 = {
227 | CreatedOnToolsVersion = 10.2.1;
228 | TestTargetID = 4DFBA51722C3FB5D0059F845;
229 | };
230 | };
231 | };
232 | buildConfigurationList = 4DFBA51322C3FB5D0059F845 /* Build configuration list for PBXProject "NoteTaker" */;
233 | compatibilityVersion = "Xcode 9.3";
234 | developmentRegion = en;
235 | hasScannedForEncodings = 0;
236 | knownRegions = (
237 | en,
238 | Base,
239 | );
240 | mainGroup = 4DFBA50F22C3FB5D0059F845;
241 | productRefGroup = 4DFBA51922C3FB5D0059F845 /* Products */;
242 | projectDirPath = "";
243 | projectRoot = "";
244 | targets = (
245 | 4DFBA51722C3FB5D0059F845 /* NoteTaker */,
246 | 4DFBA52922C3FB5F0059F845 /* NoteTakerTests */,
247 | 4DFBA53422C3FB5F0059F845 /* NoteTakerUITests */,
248 | );
249 | };
250 | /* End PBXProject section */
251 |
252 | /* Begin PBXResourcesBuildPhase section */
253 | 4DFBA51622C3FB5D0059F845 /* Resources */ = {
254 | isa = PBXResourcesBuildPhase;
255 | buildActionMask = 2147483647;
256 | files = (
257 | 4DFBA52022C3FB5E0059F845 /* Assets.xcassets in Resources */,
258 | 4DFBA52322C3FB5E0059F845 /* Main.storyboard in Resources */,
259 | );
260 | runOnlyForDeploymentPostprocessing = 0;
261 | };
262 | 4DFBA52822C3FB5F0059F845 /* Resources */ = {
263 | isa = PBXResourcesBuildPhase;
264 | buildActionMask = 2147483647;
265 | files = (
266 | );
267 | runOnlyForDeploymentPostprocessing = 0;
268 | };
269 | 4DFBA53322C3FB5F0059F845 /* Resources */ = {
270 | isa = PBXResourcesBuildPhase;
271 | buildActionMask = 2147483647;
272 | files = (
273 | );
274 | runOnlyForDeploymentPostprocessing = 0;
275 | };
276 | /* End PBXResourcesBuildPhase section */
277 |
278 | /* Begin PBXSourcesBuildPhase section */
279 | 4DFBA51422C3FB5D0059F845 /* Sources */ = {
280 | isa = PBXSourcesBuildPhase;
281 | buildActionMask = 2147483647;
282 | files = (
283 | 4D1A9D0C22E6855300E27102 /* FileSavingandOpening.swift in Sources */,
284 | 4DEE61EC22C7EBE00084E4B9 /* TableViewDataSource.swift in Sources */,
285 | 4DEE61EA22C7E9540084E4B9 /* SplitViewController.swift in Sources */,
286 | 4DFBA51C22C3FB5D0059F845 /* AppDelegate.swift in Sources */,
287 | 4DEE61E622C70C220084E4B9 /* TableViewController.swift in Sources */,
288 | 4DEE61EE22C8496D0084E4B9 /* TableCellEditing.swift in Sources */,
289 | 4DEE61E422C70B130084E4B9 /* Note.swift in Sources */,
290 | 4DEE61E822C70C8C0084E4B9 /* TextViewController.swift in Sources */,
291 | 4DFC89AB22FDFFD4008136E6 /* ValidateNoteDeletion.swift in Sources */,
292 | );
293 | runOnlyForDeploymentPostprocessing = 0;
294 | };
295 | 4DFBA52622C3FB5F0059F845 /* Sources */ = {
296 | isa = PBXSourcesBuildPhase;
297 | buildActionMask = 2147483647;
298 | files = (
299 | 4DFBA52F22C3FB5F0059F845 /* NoteTakerTests.swift in Sources */,
300 | );
301 | runOnlyForDeploymentPostprocessing = 0;
302 | };
303 | 4DFBA53122C3FB5F0059F845 /* Sources */ = {
304 | isa = PBXSourcesBuildPhase;
305 | buildActionMask = 2147483647;
306 | files = (
307 | 4DFBA53A22C3FB5F0059F845 /* NoteTakerUITests.swift in Sources */,
308 | );
309 | runOnlyForDeploymentPostprocessing = 0;
310 | };
311 | /* End PBXSourcesBuildPhase section */
312 |
313 | /* Begin PBXTargetDependency section */
314 | 4DFBA52C22C3FB5F0059F845 /* PBXTargetDependency */ = {
315 | isa = PBXTargetDependency;
316 | target = 4DFBA51722C3FB5D0059F845 /* NoteTaker */;
317 | targetProxy = 4DFBA52B22C3FB5F0059F845 /* PBXContainerItemProxy */;
318 | };
319 | 4DFBA53722C3FB5F0059F845 /* PBXTargetDependency */ = {
320 | isa = PBXTargetDependency;
321 | target = 4DFBA51722C3FB5D0059F845 /* NoteTaker */;
322 | targetProxy = 4DFBA53622C3FB5F0059F845 /* PBXContainerItemProxy */;
323 | };
324 | /* End PBXTargetDependency section */
325 |
326 | /* Begin PBXVariantGroup section */
327 | 4DFBA52122C3FB5E0059F845 /* Main.storyboard */ = {
328 | isa = PBXVariantGroup;
329 | children = (
330 | 4DFBA52222C3FB5E0059F845 /* Base */,
331 | );
332 | name = Main.storyboard;
333 | sourceTree = "";
334 | };
335 | /* End PBXVariantGroup section */
336 |
337 | /* Begin XCBuildConfiguration section */
338 | 4DFBA53C22C3FB5F0059F845 /* Debug */ = {
339 | isa = XCBuildConfiguration;
340 | buildSettings = {
341 | ALWAYS_SEARCH_USER_PATHS = NO;
342 | CLANG_ANALYZER_NONNULL = YES;
343 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
344 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
345 | CLANG_CXX_LIBRARY = "libc++";
346 | CLANG_ENABLE_MODULES = YES;
347 | CLANG_ENABLE_OBJC_ARC = YES;
348 | CLANG_ENABLE_OBJC_WEAK = YES;
349 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
350 | CLANG_WARN_BOOL_CONVERSION = YES;
351 | CLANG_WARN_COMMA = YES;
352 | CLANG_WARN_CONSTANT_CONVERSION = YES;
353 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
354 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
355 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
356 | CLANG_WARN_EMPTY_BODY = YES;
357 | CLANG_WARN_ENUM_CONVERSION = YES;
358 | CLANG_WARN_INFINITE_RECURSION = YES;
359 | CLANG_WARN_INT_CONVERSION = YES;
360 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
361 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
362 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
363 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
364 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
365 | CLANG_WARN_STRICT_PROTOTYPES = YES;
366 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
367 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
368 | CLANG_WARN_UNREACHABLE_CODE = YES;
369 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
370 | CODE_SIGN_IDENTITY = "-";
371 | COPY_PHASE_STRIP = NO;
372 | DEBUG_INFORMATION_FORMAT = dwarf;
373 | ENABLE_STRICT_OBJC_MSGSEND = YES;
374 | ENABLE_TESTABILITY = YES;
375 | GCC_C_LANGUAGE_STANDARD = gnu11;
376 | GCC_DYNAMIC_NO_PIC = NO;
377 | GCC_NO_COMMON_BLOCKS = YES;
378 | GCC_OPTIMIZATION_LEVEL = 0;
379 | GCC_PREPROCESSOR_DEFINITIONS = (
380 | "DEBUG=1",
381 | "$(inherited)",
382 | );
383 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
384 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
385 | GCC_WARN_UNDECLARED_SELECTOR = YES;
386 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
387 | GCC_WARN_UNUSED_FUNCTION = YES;
388 | GCC_WARN_UNUSED_VARIABLE = YES;
389 | MACOSX_DEPLOYMENT_TARGET = 10.11;
390 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
391 | MTL_FAST_MATH = YES;
392 | ONLY_ACTIVE_ARCH = YES;
393 | SDKROOT = macosx;
394 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
395 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
396 | };
397 | name = Debug;
398 | };
399 | 4DFBA53D22C3FB5F0059F845 /* Release */ = {
400 | isa = XCBuildConfiguration;
401 | buildSettings = {
402 | ALWAYS_SEARCH_USER_PATHS = NO;
403 | CLANG_ANALYZER_NONNULL = YES;
404 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
405 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
406 | CLANG_CXX_LIBRARY = "libc++";
407 | CLANG_ENABLE_MODULES = YES;
408 | CLANG_ENABLE_OBJC_ARC = YES;
409 | CLANG_ENABLE_OBJC_WEAK = YES;
410 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
411 | CLANG_WARN_BOOL_CONVERSION = YES;
412 | CLANG_WARN_COMMA = YES;
413 | CLANG_WARN_CONSTANT_CONVERSION = YES;
414 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
415 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
416 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
417 | CLANG_WARN_EMPTY_BODY = YES;
418 | CLANG_WARN_ENUM_CONVERSION = YES;
419 | CLANG_WARN_INFINITE_RECURSION = YES;
420 | CLANG_WARN_INT_CONVERSION = YES;
421 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
422 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
423 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
424 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
425 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
426 | CLANG_WARN_STRICT_PROTOTYPES = YES;
427 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
428 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
429 | CLANG_WARN_UNREACHABLE_CODE = YES;
430 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
431 | CODE_SIGN_IDENTITY = "-";
432 | COPY_PHASE_STRIP = NO;
433 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
434 | ENABLE_NS_ASSERTIONS = NO;
435 | ENABLE_STRICT_OBJC_MSGSEND = YES;
436 | GCC_C_LANGUAGE_STANDARD = gnu11;
437 | GCC_NO_COMMON_BLOCKS = YES;
438 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
439 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
440 | GCC_WARN_UNDECLARED_SELECTOR = YES;
441 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
442 | GCC_WARN_UNUSED_FUNCTION = YES;
443 | GCC_WARN_UNUSED_VARIABLE = YES;
444 | MACOSX_DEPLOYMENT_TARGET = 10.11;
445 | MTL_ENABLE_DEBUG_INFO = NO;
446 | MTL_FAST_MATH = YES;
447 | SDKROOT = macosx;
448 | SWIFT_COMPILATION_MODE = wholemodule;
449 | SWIFT_OPTIMIZATION_LEVEL = "-O";
450 | };
451 | name = Release;
452 | };
453 | 4DFBA53F22C3FB5F0059F845 /* Debug */ = {
454 | isa = XCBuildConfiguration;
455 | buildSettings = {
456 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
457 | CODE_SIGN_STYLE = Automatic;
458 | COMBINE_HIDPI_IMAGES = YES;
459 | INFOPLIST_FILE = NoteTaker/Info.plist;
460 | LD_RUNPATH_SEARCH_PATHS = (
461 | "$(inherited)",
462 | "@executable_path/../Frameworks",
463 | );
464 | PRODUCT_BUNDLE_IDENTIFIER = com.SwiftDevJournal.NoteTaker;
465 | PRODUCT_NAME = "$(TARGET_NAME)";
466 | SWIFT_VERSION = 5.0;
467 | };
468 | name = Debug;
469 | };
470 | 4DFBA54022C3FB5F0059F845 /* Release */ = {
471 | isa = XCBuildConfiguration;
472 | buildSettings = {
473 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
474 | CODE_SIGN_STYLE = Automatic;
475 | COMBINE_HIDPI_IMAGES = YES;
476 | INFOPLIST_FILE = NoteTaker/Info.plist;
477 | LD_RUNPATH_SEARCH_PATHS = (
478 | "$(inherited)",
479 | "@executable_path/../Frameworks",
480 | );
481 | PRODUCT_BUNDLE_IDENTIFIER = com.SwiftDevJournal.NoteTaker;
482 | PRODUCT_NAME = "$(TARGET_NAME)";
483 | SWIFT_VERSION = 5.0;
484 | };
485 | name = Release;
486 | };
487 | 4DFBA54222C3FB5F0059F845 /* Debug */ = {
488 | isa = XCBuildConfiguration;
489 | buildSettings = {
490 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
491 | BUNDLE_LOADER = "$(TEST_HOST)";
492 | CODE_SIGN_STYLE = Automatic;
493 | COMBINE_HIDPI_IMAGES = YES;
494 | INFOPLIST_FILE = NoteTakerTests/Info.plist;
495 | LD_RUNPATH_SEARCH_PATHS = (
496 | "$(inherited)",
497 | "@executable_path/../Frameworks",
498 | "@loader_path/../Frameworks",
499 | );
500 | PRODUCT_BUNDLE_IDENTIFIER = com.SwiftDevJournal.NoteTakerTests;
501 | PRODUCT_NAME = "$(TARGET_NAME)";
502 | SWIFT_VERSION = 5.0;
503 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/NoteTaker.app/Contents/MacOS/NoteTaker";
504 | };
505 | name = Debug;
506 | };
507 | 4DFBA54322C3FB5F0059F845 /* Release */ = {
508 | isa = XCBuildConfiguration;
509 | buildSettings = {
510 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
511 | BUNDLE_LOADER = "$(TEST_HOST)";
512 | CODE_SIGN_STYLE = Automatic;
513 | COMBINE_HIDPI_IMAGES = YES;
514 | INFOPLIST_FILE = NoteTakerTests/Info.plist;
515 | LD_RUNPATH_SEARCH_PATHS = (
516 | "$(inherited)",
517 | "@executable_path/../Frameworks",
518 | "@loader_path/../Frameworks",
519 | );
520 | PRODUCT_BUNDLE_IDENTIFIER = com.SwiftDevJournal.NoteTakerTests;
521 | PRODUCT_NAME = "$(TARGET_NAME)";
522 | SWIFT_VERSION = 5.0;
523 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/NoteTaker.app/Contents/MacOS/NoteTaker";
524 | };
525 | name = Release;
526 | };
527 | 4DFBA54522C3FB5F0059F845 /* Debug */ = {
528 | isa = XCBuildConfiguration;
529 | buildSettings = {
530 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
531 | CODE_SIGN_STYLE = Automatic;
532 | COMBINE_HIDPI_IMAGES = YES;
533 | INFOPLIST_FILE = NoteTakerUITests/Info.plist;
534 | LD_RUNPATH_SEARCH_PATHS = (
535 | "$(inherited)",
536 | "@executable_path/../Frameworks",
537 | "@loader_path/../Frameworks",
538 | );
539 | PRODUCT_BUNDLE_IDENTIFIER = com.SwiftDevJournal.NoteTakerUITests;
540 | PRODUCT_NAME = "$(TARGET_NAME)";
541 | SWIFT_VERSION = 5.0;
542 | TEST_TARGET_NAME = NoteTaker;
543 | };
544 | name = Debug;
545 | };
546 | 4DFBA54622C3FB5F0059F845 /* Release */ = {
547 | isa = XCBuildConfiguration;
548 | buildSettings = {
549 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
550 | CODE_SIGN_STYLE = Automatic;
551 | COMBINE_HIDPI_IMAGES = YES;
552 | INFOPLIST_FILE = NoteTakerUITests/Info.plist;
553 | LD_RUNPATH_SEARCH_PATHS = (
554 | "$(inherited)",
555 | "@executable_path/../Frameworks",
556 | "@loader_path/../Frameworks",
557 | );
558 | PRODUCT_BUNDLE_IDENTIFIER = com.SwiftDevJournal.NoteTakerUITests;
559 | PRODUCT_NAME = "$(TARGET_NAME)";
560 | SWIFT_VERSION = 5.0;
561 | TEST_TARGET_NAME = NoteTaker;
562 | };
563 | name = Release;
564 | };
565 | /* End XCBuildConfiguration section */
566 |
567 | /* Begin XCConfigurationList section */
568 | 4DFBA51322C3FB5D0059F845 /* Build configuration list for PBXProject "NoteTaker" */ = {
569 | isa = XCConfigurationList;
570 | buildConfigurations = (
571 | 4DFBA53C22C3FB5F0059F845 /* Debug */,
572 | 4DFBA53D22C3FB5F0059F845 /* Release */,
573 | );
574 | defaultConfigurationIsVisible = 0;
575 | defaultConfigurationName = Release;
576 | };
577 | 4DFBA53E22C3FB5F0059F845 /* Build configuration list for PBXNativeTarget "NoteTaker" */ = {
578 | isa = XCConfigurationList;
579 | buildConfigurations = (
580 | 4DFBA53F22C3FB5F0059F845 /* Debug */,
581 | 4DFBA54022C3FB5F0059F845 /* Release */,
582 | );
583 | defaultConfigurationIsVisible = 0;
584 | defaultConfigurationName = Release;
585 | };
586 | 4DFBA54122C3FB5F0059F845 /* Build configuration list for PBXNativeTarget "NoteTakerTests" */ = {
587 | isa = XCConfigurationList;
588 | buildConfigurations = (
589 | 4DFBA54222C3FB5F0059F845 /* Debug */,
590 | 4DFBA54322C3FB5F0059F845 /* Release */,
591 | );
592 | defaultConfigurationIsVisible = 0;
593 | defaultConfigurationName = Release;
594 | };
595 | 4DFBA54422C3FB5F0059F845 /* Build configuration list for PBXNativeTarget "NoteTakerUITests" */ = {
596 | isa = XCConfigurationList;
597 | buildConfigurations = (
598 | 4DFBA54522C3FB5F0059F845 /* Debug */,
599 | 4DFBA54622C3FB5F0059F845 /* Release */,
600 | );
601 | defaultConfigurationIsVisible = 0;
602 | defaultConfigurationName = Release;
603 | };
604 | /* End XCConfigurationList section */
605 | };
606 | rootObject = 4DFBA51022C3FB5D0059F845 /* Project object */;
607 | }
608 |
--------------------------------------------------------------------------------
/NoteTaker/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
690 |
691 |
692 |
693 |
694 |
695 |
696 |
697 |
698 |
699 |
700 |
701 |
702 |
703 |
704 |
705 |
706 |
707 |
708 |
709 |
710 |
711 |
712 |
713 |
714 |
715 |
716 |
717 |
718 |
719 |
720 |
721 |
722 |
723 |
724 |
725 |
726 |
727 |
728 |
729 |
730 |
731 |
732 |
733 |
734 |
735 |
736 |
737 |
738 |
739 |
740 |
741 |
742 |
743 |
744 |
745 |
746 |
747 |
748 |
749 |
750 |
751 |
752 |
753 |
754 |
755 |
756 |
757 |
758 |
759 |
760 |
761 |
762 |
763 |
764 |
765 |
766 |
767 |
768 |
769 |
770 |
771 |
772 |
773 |
774 |
775 |
776 |
777 |
778 |
779 |
780 |
781 |
782 |
783 |
784 |
785 |
786 |
787 |
788 |
789 |
790 |
791 |
792 |
793 |
794 |
795 |
796 |
797 |
798 |
799 |
800 |
801 |
802 |
803 |
804 |
805 |
806 |
807 |
808 |
809 |
810 |
811 |
812 |
813 |
814 |
815 |
816 |
817 |
818 |
819 |
820 |
821 |
822 |
823 |
824 |
825 |
826 |
827 |
828 |
829 |
830 |
831 |
832 |
833 |
834 |
835 |
836 |
837 |
838 |
839 |
840 |
841 |
842 |
843 |
844 |
845 |
846 |
847 |
848 |
849 |
850 |
851 |
852 |
853 |
854 |
855 |
856 |
857 |
858 |
859 |
860 |
861 |
862 |
863 |
864 |
865 |
866 |
867 |
868 |
869 |
870 |
871 |
872 |
873 |
874 |
875 |
876 |
877 |
878 |
879 |
880 |
881 |
882 |
883 |
884 |
885 |
886 |
887 |
888 |
889 |
890 |
891 |
892 |
893 |
894 |
895 |
896 |
897 |
898 |
899 |
900 |
901 |
902 |
903 |
904 |
905 |
906 |
907 |
908 |
909 |
910 |
--------------------------------------------------------------------------------