Creating A Study Note App

Creating a Study Note App


For this app, I decided to take a popular app and put my spin on it. My fiancée is going back to nursing school and I decided it would be easy to make an application for her to carry her flash cards AKA study notes with her. So I set out building this app.

To start off, I implemented the entire app's UI programmatically. So I dove into the Scene Delegate and set up the following inside scene(_ : willConnectTo: options:):

guard let windowScene = scene as? UIWindowScene else { return }
window = UIWindow(windowScene: windowScene)
window?.windowScene = windowScene
let loginVC = LoginViewController(nibName: nil, bundle: nil)
window?.rootViewController = UINavigationController(rootViewController: loginVC)
window?.makeKeyAndVisible()

I got rid of the parentheses around scene as? UIWindowScene as I was now going to use the scene itself. I then instantiated the window, passing in the windowScene as it's only parameter, and set the window's windowScene property to the unwrapped windowScene. I then created an instance of my (poorly named) LoginViewController, setting nibName and bundle to nil, as I wasn't using XIBs or the Storyboard. I then set the rootViewController of the window to a UINavigationController, and set the rootViewController for that to my loginVC. Then calling .makeKeyAndVisible() on the window, makes the window come on screen.

I then set up my CoreData model and entities. I made a Note class that has an answer and an NSOrderedSet of clues. Then I set up a Clue class that has a text property and a relationship to the Note class. I set up extensions to the classes to create convenience initializers and a CoreDataStack singleton to handle ease of use across my app.

I then got to work on my main ViewController. I created view objects as closures for all UI elements and I'm configuring the UI inside a helper method, that I then call in viewDidLoad(). When the view loads, I'm performing a fetch from CoreData and populating the view for testing.

StudyModeView

The UI was set up. Next I enabled the ability to add Cards, Taking in the note selected and unwrapping the text from the textFields in AddCardViewController. Once they were set up, I created an OrderedSet of the clues, saved it to the Note, and made a call to save() on my CoreDataStacks main view context, saving them to device.

Now I had basic working creation and reading of Cards. I added a CategoryTableViewController to the project and Category entity to CoreData with a title, and an NSOrderedSet of Note(s), and started building the functionality to add categories to the app. I populated the app with seed data at first, and then went to work on the UI. The cells were populating but I spent a lot of time trying to get that "just right" look. I was setting up a plain UIView and adding a gradient and then trying to constrain it. It took me a good hour and a half before I reached out for help, but after a short conversation, thanks to Dillon McElhinney, I discovered there's a better way to handle that with a GradientView. Once my UI was set up the way I liked it, I enabled users to add categories via a modal pop up view controller.

Category popup

After I was able to create categories, the next natural progression, was to add cards to that category. This is where I hit another bump in the road. I followed my instincts and set up an array of Notes, and tried to add it to the category. iOS was not happy about that change at all, and upon trying to save cards, the app came crashing down. After a short search, I found the answer to my troubles on SO. and this is what I came up with as a solution:

guard let category = category else { return }
		let mutableNotes = category.notes?.mutableCopy() as! NSMutableOrderedSet
		let note = Note(answer: answer, clues: NSOrderedSet(array: clues))!
		mutableNotes.add(note)
		category.notes = (mutableNotes.copy() as! NSOrderedSet)

This allowed me to add cards to categories, but I wasn't happy with the UI. If the user typed text that was longer than the textField, it followed the cursor and you weren't able to see the entire text you entered. So after some thought, I conformed to UITextFieldDelegate and called the textFieldShouldBeginEditing(_ :) method and inside there presented another modal popup view containing only a UITextView and a save and cancel button, along with the textField that presented the view controller.

Clue TextView popup

Now inside TextViewPopoverViewController I created a TextViewPopoverDelegate and laid out the framework:

protocol TextViewPopoverDelegate: AnyObject {
	func textViewDidSave(text: String, into textField: UITextField)
}

I set a weak var delegate: TextViewPopoverDelegate? property on my popover and now, once a user clicks on the save button, the delegate fires, and dismisses back to the AddCardVC and fills in the text on the TextField.
After all my work, I now have a complete version 1 to add to the AppStore and TestFlight. Here is a GIF of my progress: StudyNotes gif

Link to TestFlight