Reflections on Day 77
I am very satisfied with what I have done yesterday. I loved every bit of the learning process, I loved diving into Apple’s Documentation and trying to understand things myself. I found it very rewarding and I definitely feel that something has stuck in my mind.
In yesterday’s challenges I have tried to stretch myself a bit more and get where it was not needed but I really didn’t like to leave the app in the state where it was at the end. Also, the snooze functionality was not described in the Documentation so I had to delve a bit more into the open web to find a solution … which doesn’t work!
Wait … this morning at 9.30am I got a notification and… yes… it was the Guess the Flag app telling me I had to play. The only issue was that also the Remind me later… action brought me inside the app, which I would have liked to avoid for both challenge 2 and challenge 3. I have some ideas about why this could have happened, but I will probably solve it much later in my development journey.
Tomorrow I will leave for a three-day holiday and I will not bring the computer with me. This will be the first big pause I make in this great path and I sincerely believe it is needed. I agree that one should do something every day, but if you are in any real-life situation that is different from a plain CS Student one, you have to take a break once per week or once every 10-15 days. After that break you will be so much better than before!
My cello teacher used to say that we had to practice cello 6 hours per day, every day, for three months, then take a 7-10 day break and travel somewhere to breath some outstanding work of art and forget about the cello. So yes, that is about 90 days non-stop, but that is what you do all day long, not an extra skill you are adding when you are already dog-tired from the rest of the day.
Anyway, enough talking, I want to try and get Day 74 of the curriculum under my belt before leaving or, as it looks like a Consolidation Day, to do as much as possible before that.
Challenge
This day’s challenge is something I am sure I am going to love: recreate the iOS Notes app! As I use it so much I can’t wait to get my hands dirty with it.
Paul provided us with a list of to-do things that I plan to follow it as strictly as possible. Here is what we should do:
Once you’ve done those, try using Interface Builder to customise the UI – how close can you make it look like Notes?
Note: the official Apple Notes app supports rich text input and media; don’t worry about that, focus on plain text.
1. Create a table view controller that lists notes. Place it inside a navigation controller.
I’m back from these three days of holidays and I can resume the creation of this Notes clone app. Here is where I have gotten to before leaving. Some parts are really messy, especially in the Model part, but I count on solving them very soon.
Storyboard
Let’s start from here. I embedded the view controller in a navigation controller and then, selecting the navigation bar, I checked the Prefers Large Titles box. I know I can do that in code but I prefer this visual aid. Then, selecting the whole navigation controller and going to the Attributes Inspector I checked the Shows Toolbar option. I tried to do that in code but I didn’t find any way to succeed in doing so.
In the main view controller I changed the title of the Navigation Item to “Folders”, then changed the style of the prototype cell (with identifier “Note”) to Right Detail. I want the name of the folder to be on the left and the number of items inside it on the right. I also changed the style of the table view to be Grouped as I want to have different sections in it.
In the toolbar I added a flexible space bar button item and a bar button item called “New folder” (guess what this will do?!).
That’s it by now in the storyboard.
Da code
Inside ViewController.swift I created a folders
variable of type [[String]]
initialised with [["Notes"]]
. Don’t yell at me just yet, I know this is messy and I will fix it very soon but that was how I figured out sections to be. I’m pretty sure to be wrong and that maybe a dictionary of type [String: [String]]
could have been better but hey, I’m learning and testing on the go.
Inside viewDidLoad
I created an editButtonItem
and a normal UIBarButtonItem
called deleteButton
that would call the deleteRows
selector method. I grouped them inside the navigationItem.rightBarButtonItems
array and then set deleteButton.isEnabled = false
(you will see why in just a minute).
Finally I set tableView.allowsMultipleSelectionDuringEditing
to true.
I created an action from the “New folder” toolbar button item called newFolderTapped
and inside it, summoned an alert controller with a single textfield and an action attached to a “Save” button. This would insert that text in the first section of the folders
array. I would really like to make this better straight away but my skills are just where they are right now and I am already stretching quite a bit by doing these extra untaught things.
After inserting the item in the array I inserted the row in the table view. I tried to do it the nice way, that is inserting the row at the bottom but, I was getting endless crashes and I could not figure out how to do this by now.
I then created the deleteRows
method that would be loop over the selected rows (more on how to select them later) and then remove them from the array and then call three methods on the table view: beginUpdates
, deleteRows(at:with:)
and endUpdates
.
Table view data source methods
The numberOfSections
returns folders.count
, so the first level of the array. The titleForHeaderInSection
will return headers[section]
, headers
being a new array of strings just created and hosting the only string “iCloud”.
The numberOfRowsInSection
returns folders[section].count
, while the cellForRowAt
dequeues a reusable cell with the “Note” identifier (which I guess I will change to “Folder” for coherence, set the text label’s text to be folders[indexPath.section][indexPath.row]
and the detail text label to 0
(a temporary placeholder I would say).
Editing
This method is really nice because it comes free with table views! Just call override func setEditing(_:animated:)
and, inside:
super.setEditing(editing, animated: true)
tableView.setEditing(tableView.isEditing, animated: true) // this will be automatically toggled on and off when pressing the Edit button!!
toolbarItems?[1].isEnabled.toggle() // this to disallow creation of a new folder while I am editing
navigationItem.rightBarButtonItems?[1].isEnabled.toggle() // this to activate the delete button when we are editing.
Make the Model better
I created a new Swift file called “Model.swift” and, inside that created three structs to hold the base of what I need. The first is a Directory
struct with a “name” string and a [Folder]
properties. Then we have the Folder
struct which contains a name, an items count and an array of Note
objects. Guess what, the next and last struct will be the Note
one, with a title and a text string properties. Now I need to adjust the whole code on the other side, wish me luck!
First I created a directories
property like this:
var directories: [Directory] = [Directory(name: "iCloud", folders: [])]
I then changed the numberOfSections
to have directories.count
and the titleForHeaderInSection
to be directories[section].name
.
This is provisory but it works. Now let’s move to the folder.
Inside the directories
property I edited the empty array of folders adding a simple item: Folder(name: "Notes", notes: [], itemsCount: 0)
. I then edited the numberOfRowsInSection
return value to directories[section].folders.count
. Last but not least, the cellForRowAt
method needed to be changed like this:
cell.textLabel!.text = directories[indexPath.section].folders[indexPath.row].name
cell.detailTextLabel!.text = String(directories[indexPath.section].folders[indexPath.row].itemsCount)
Maybe it looks chaotic but it works so, until a better solution is proposed or found that is here to stay. I really doubt that Apple’s code for this app is much simpler.
Now, commenting out the folders
property will cause a whole lot of troubles which we are going to address right now.
The deleteRows
method needs to have the deleting code be changed to:
directories[indexPath.section].folders.remove(at: rowToDelete)
The part in the newFolderTapped
is a bit more complicated because even if we have started to create support for multiple saving directories, we cannot let the user choose where to save new folders so, by now, we are just going to assume the user has a single directory on the Notes app. Changing this later will be easier, probably…possibly?.
By now I simply changed the code in the action closure to this:
let newFolder = Folder(name: noteTitle, notes: [], itemsCount: 0)
self?.directories[0].folders.insert(newFolder, at: 0)
I tested it and it works.
I added the possibility to insert multiple directories and to give them names but I have to figure out how to let the user choose in which folder to insert the item.
Looking at how iOS does that I tried to replicate it. This had to be done in the newFolderTapped
method. I created a control flow statement where the present code would be executed with directories.count <= 1
(I’ve not implemented the deletion of sections yet and possibly I will not do that in the present iteration of the project but, better to leave room for that in the future). In all other cases I would want an .actionSheet
alert with a new action for every Directory
element. I don’t want to bore you with endless streams of code here (which you can check in the GitHub repository anyway) but the best parts here were these lines:
for (index, directory) in directories.enumerated() {
// This let me keep an index for later use
self?.directories[index].folders.insert(newFolder, at: 0)
// see how this comes in handy?
I still wonder how I cannot insert an object at the end of an array. I used to know that doing the insertion at array.count
would add an element, right? Instead… it just crashes my app. I mean, this is just working right?

So why is it not working in the app?
Never mind, let’s move on…
3. Saving the data
I want to do saving before the next screen, just because…
Paul says we can use UserDefaults and that’s fine but, how can we instead save everything to a file?
Anyway, I went with UserDefaults by now so, in order: I added the JSON decoding code into viewDidLoad
, then moved the “Helper methods” mark section to the bottom of the view controller, then created the saveModel
method with the JSON encoding code and, finally, called it in every possible sensible place.
Tried it, it works! Done! ✅
2A. Tapping on a note should slide in a detail view controller that contains a full-screen text view.
Now to the moment of truth, I need to create the second screen, where the folder is opened and it shows, inside, all the notes. But before, I need to fix a small but important bug. When I call the indexPathsForSelectedRows
during the deletion process, it seems that this property respects the order in which items are selected. This means that crashes will occur in very precise selection orders as some index paths will just not be there anymore when we want to delete them.
Thanks to the help of the always present Rob Baldwin I added a sorting method so that, whichever the selection order, the index paths passed to the deleting code would be ordered and safe.
For this second screen I am now writing in Day 80 because this took me so much time to get it right. It was crashing and crashing again, and then it was not passing the data and so on and so forth until I found the light with delegation. I had tried it before but for some reason it was not working. Now it does and I sincerely hope it is not just luck.
I will recount here only the solution that works because it would have been useless to show every single misstep.
Storyboard
We start from the storyboard and add a new table view controller. We give the cell a reuse identifier of “Note” and a style of “Subtitle”. We then give the view controller a Storyboard ID of “FolderViewController” and create an accompanying class so that we can make it inherit from that one.
Now, mosto of the work will be done in code so let’s switch to FolderTableViewController.swift.
Folder View Controller
I created four properties: an array of Note
s, an implicitly unwrapped origin index path to keep track of the index path we are coming from in the other view controller, an implicitly unwrapped ViewController
called “delegate” and a toolbar label of size (200 x 21) which will keep count of the notes inside of the view controller.
Inside viewDidLoad I created an edit button item to the top right, a compose button which will call the composeNote
method, a flexible space button, performed a few adjustments to the toolbar label, created the label button using the (customView:)
initialiser of the UIBarButtonItem
, created a delete button which will call the deleteNote
method and disabled it, arranged all these things in the toolbarItems
and, finally, allowed multiple selection during editing.
For the table view data source methods I returned notes.count
from the numberOfRowsInSection
, created an appropriate cell for the row at the index path that would show the title of the note as text label and the text of the note as the detail label before returning the cell, and overrode the commit editingStyle
method just to get the swipe to delete functionality.
For the table view delegate methods I just called the setEditing
method (which is actually a method common to all view controllers) and set it to toggle the isEnabled
property of the first and last item in the toolbar items array (that is the compose and delete buttons).
I then created the two helper methods, composeNote()
and deleteNote()
.
In the first one I created a temporary note to see if everything would be working in the form of Note(title: "Test title", text: "Testing the detail text item"
. I appended the note to the notes
array, set the toolbarLabel.text
to "\(notes.count) notes"
, called the still unwritten delegate.updateFolder(at: originIndexPath, with: notes)
method and reloaded the table view data.
In the second one I repeated the deletion code with the appropriate calls and inserted a call to the delegate method in the middle. Both here and in the sibling method in ViewController.swift I called isEditing.toggle()
at the end, to provide a better user experience.
View controller
Moving back to ViewController.swift I finally resorted on using the didSelectRowAt
method instantiating a new view controller with the “FolderViewController” identifier, setting its title to the name of the folder, its notes
property to the folder’s notes array and its originIndexPath
property to the currently selected index path. I then set the present view controller to be the delegate
of the folder view controller and then pushed the view controller on to the navigation controller.
What I like of delegation is that the name of the delegate
property is completely arbitrary: one could call it endOfTheWorld
and then you would call folderViewController.endOfTheWorld = self
and it would still works. A pity that the computer would not understand the joke!
The only thing to add would be the implementation of the updateFolder(at:with:)
method. In past tutorials I had seen this done with extension
s and in much more complicated ways but this time I tried to implement Paul solution and … it just worked …! Simply amazing!
I set the appropriate notes folder to be equal to the notes
parameter, the .itemsCount
equal to notes.count
, then reloaded the table view’s data and saved the model.
Given the amount of sweat and curses that came for this, it ended up in a much nicer way.
2B. Now for the true text view
The main issue now is that I can add many new notes but they are not really meaningful.
First let’s create a new view controller in the storyboard, give it a Storyboard ID of “NoteViewController” and add a text view inside it, pinning it to each corner of the parent view. Create a new Cocoa Touch Class that inherits from UIViewController
and call it “NoteViewController.swift”.
Inside it, create an outlet for the text view and the following properties: a Note
object (implicitly unwrapped), a weak
FolderTableViewController!
delegate property, and an originIndexPath
(exactly as before).
Inside viewDidLoad
set the largeTitleDisplayMode
for the navigation item to be .never
, as we do not want big titles for the notes, set the .text
property of the text view to be equal to the text property of the Note
object and set the font size to 18, to be a bit more legible. The way this last thing is done is quite inelegant: textView.font = textView.font?.withSize(18)
. Isn’t there really anything better than this?
Then, create a new right bar button item of type .done
and of action #selector(done)
, we are going to fill it in in a minute. Finally, implement the two observers that will let us adjust the keyboard and the frame while typing. Also don’t forget to implement the adjustForKeyboard
method at the end of the class.
Write the done
method with a popViewController
call and a delegate.updateNote(at:with:)
one (which we are about to write). Pass the originIndexPath
as first parameter and textView.text
as second parameter.
Now move back to FolderTableViewController.swift and write the delegate method updateNote(at index: IndexPath, with text: String)
. Inside it the following code:
notes[index.row].text = text // this passes the text from the text view to the notes object inside our folder view controller
toolbarLabel.text = "\(notes.count) notes" // this is very important and I guess I should have made a property observer somewhere but, now that it works, let's leave it like it is
delegate.updateFolder(at: originIndexPath, with: notes) // this is fundamental because otherwise the data was being passed between the view controllers but not saved! Nice that recursivity is so powerful!
tableView.reloadData()
… and that’s it!
Now the app works perfectly in all its fields.
Fine, I have not implemented a way to delete directories but, by now, I don’t see the need to do so. It would be an easy add anyway later down the road.
4. Add some toolbar items to the detail view controller – “delete” and “compose” seem like good choices. (Project 4)
Done!
5. Add an action button to the navigation bar in the detail view controller that shares the text using UIActivityViewController
. (Project 3)
Let’s do this!
First: let’s add a flexible space button and a share button (bar button item with .action
type) to viewDidLoad
and group them into the toolbarItems
. Make the share button call the shareNote
selector method.
Second: write the shareNote
method: create a UIActivityViewController
using [note.title, "\n", note.text]
as the activityItems
parameter and leave the second parameter as an empty array. Implement the code for the iPad (popoverPresentationController?.barButtonItem
) and present the activity view controller.
Done
Finishing touches
Following the advise of Rob Baldwin I am implementing the search bar in every view following his own tutorial.
After a good hour of trial I abandoned the project here where it is because it is becoming a whole mess to track which array should be called when. I think this is a good example for implementing an extra view controller for the search results.
This app seems way to complex for the simplest implementation of the search bar.
So, in the end, this is the app for Consolidation Day VIII and here is the code on GitHub.
Please download it, try it out and let me know what you think of it.
Please don’t forget to drop a hello and a thank you to Paul for all his great work (you can find him on Twitter) and be sure to visit the 100 Days Of Swift initiative page. We are learning so much thanks to him and he deserves to know our gratitude.
He has about 20 great books on Swift, all of which you can check about here.
The 100 Days of Swift initiative is based on the Hacking with Swift book, which you should definitely check out.
If you like what I’m doing here please consider liking this article and sharing it with some of your peers. If you are feeling like being really awesome, please consider making a small donation to support my studies and my writing (please appreciate that I am not using advertisement on my articles).
If you are interested in my music engraving and my publications don’t forget visit my Facebook page and the pages where I publish my scores (Gumroad, SheetMusicPlus, ScoreExchange and on Apple Books).
You can also support me by buying Paul Hudson’s books from this Affiliate Link.
Anyways, thank you so much for reading!
Till the next one!