[Swift] Checklist – episode III

As previously said, I am switching to English for these articles. Whoever codes in Swift needs to know this language anyway as no documentation exists for the Italian language and the few attempts I have seen at tutorials has been not satisfactory. 

State of the project

We left the project in a state where it could navigate between view controllers, edit the text field of the checklist cell and, apparently, create a new ChecklistItem object. Unfortunately it was not so as the object would not be passed over.

In the first video Brian explains that we could solve this with an extra view controller but that would look the same as the AddItemTableViewController so we would break the DRY rule (Don’t Repeat Yourself!).

We therefore need to add our own protocol as a glue between the elements. Our protocol will contain two methods, one to notifies the callee (nice word to learn, it seems to mean “the one being called”) that the user cancelled the add action and one that is called when new item is created.

Making our protocol

The first thing we have to do is to create our own protocol. It will bear the indication : class because it needs to be limited in application to classes and it will contain two methods. Interesting thing: methods declared inside of protocols contains only their name and no other code! Here is our protocol, added above the class declaration of AddItemViewController.

Screen Shot 2019-02-21 at 13.44.54

My main concern here is that, by now, our protocol does nothing, but we will see how it goes, as long as it works.

Delegation

This is one of my favourite subjects… starting from the bottom… that is because I really do not get how they can work. We are now asked to create a property of the AddItemViewController class called delegate of type AddItemViewControllerDelegate?. So, wait a minute. Missing the () at the end this property is not yet initialised, so far so good. But how can a variable be an instance of a protocol which contains only two empty methods? *head scratching*

Whatever, inside the cancel action we insert a call to delegate?.addItemViewControllerDidCancel(self) (starting to see where this is going?) and inside the done action we do some more work:

  1. we create a ChecklistItem instance
  2. we optional bind the text of the textField in this view controller to the text property of the created instance (called item, btw…).
  3. we set its checked property to false
  4. …and we call the addItemViewController(didFinishAdding:) method of the delegate property on self and on item.

Quite a lot to chew, right?

Yet another extension

As I said, we were starting to see where things were going when the self keyword was used. It refers to that view controller, which is itself tied to the main ChecklistViewController.

We move to that view controller’s Swift file and, at the very end, we add an extension of type AddItemViewControllerDelegate with, inside, the two methods which, this time, are completed with a call to navigationController?.popViewController(animated: true).

This is said to be a placeholder, but at least it does something.


Segues

Segues in iOS serve two purposes: they connect view controllers and can also pass data between them. When a segue happens the prepare(for: sender:) method gets called as the transition happens. If we want to exert some control over what the segue and this method do we need to give our segue an identifier and this is done in the storyboard. Simply click the segue and, in the Attributes Inspector, write “AddItemSegue” in the Identifier field.

Prepare for segue

Now, in the ChecklistViewController.swift file we need to add an override to the prepare method and to cast the segue’s destination as an AddItemTableViewController. Final thing: make the ChecklistViewController in which we are writing the code the delegate of the AddItemTableViewController.

Screen Shot 2019-02-21 at 20.30.44

Final magic!

In the extension at the end of this same page we need to add some lines in the addItemViewController(_: didFinishAdding:) method.

We need to store the new position of our ChecklistItem()​ which, being at the end of the list, will be equal to the count of the todos array of the current todoList.

We then have to use this position in the indexPath (a tuple made of a row and of a section in the table), store that into an array of indexPaths and then insert the new row containing our data in the new position!

Screen Shot 2019-02-21 at 20.34.43

Nice, right?


Challenge

This time the challenge is theoretical one: we need to think about what are the changes we need to perform in order to use the AddItemViewController as an EditViewController. Brian suggested we needed four of them.

I confess that while I paused the video and thought about the possible solution I did get four ideas but … they were all wrong, or at least sort of. I did not try them in practice to see why they may have been wrong but, still, they were not the solution. For your curiosity, I had thought about:

  1. Add another segue this time from the text label to the next view controller and call it EditItemSegue.
  2. Add another protocol to handle the edit.
  3. Create the delegate variable as before, this time of the new protocol’s type
  4. Modify the code.

I realise these are not really four things but about four steps in a direction so, I guess I was quite far from the solution.

Solution

  1. The title must read “Edit Item” instead of just “Title” as now.
  2. The new view controller must contain a ChecklistItem (because we want to modify one).
  3. We need to update the user interface to allow editing (right now we have a Cancel and an Add button which would not be very useful).
  4. Any edits we make must be applied to the ChecklistItem we are interacting with instead of creating a new one in the end.

Detail disclosure indicator

The first thing we have to do is to update the UI: let’s go into the Storyboard and let’s select the cell in the ChecklistViewController. With that, open the Attributes Inspector and modify its Accessory to Detail Disclosure.

Next we need to find a way to show the checkmark anyway because now, with the Detail Disclosure in place, it will not show anymore nor respond to our previous code.

We simply have to insert a new label in the cell, with the text “√” (Alt-V) inside, and position the two labels as shown in the following picture.Screen Shot 2019-02-22 at 21.17.25

This is one of the least elegant solution I have ever found in my very short career as iOS Apprentice but, not knowing how to do it better, I will just shut up and wait for new inspirations.

The code in ChecklistViewController.swift needs to be changed as well, but to do so we need to give the new label a Tag to identify it in code: give it 1001. Then change the configureCheckmark(for: with:) as follows:

Screen Shot 2019-02-22 at 21.19.20

Of course you can experiment with other text (like an emoji!) instead of the square root character! I used the pikado one 🎯 but you need to be careful because you will need more space between the two labels.


Challenge: second segue

This one was easier but it makes me think: then I was right before! 🙂

  1. add a second segue from the detail disclosure indicator to the AddItemViewController
  2. make sure it is a show segue
  3. name it EditItemSegue.

Be careful about point 1.: the detail disclosure indicator is not selectable directly. Just click and drag with the Ctrl button pressed from above it and it should work. You will not get the same dialog as before but a “Accessory Segue” (before it was an “Action Segue”).

Also, do not select the “Selection Segue” as this will do other things.

These little things seem trivial but to me they are so important (because I am a geek!).


Passing data in segues

Our next steps are the following:

  1. create properties on the receiving view controller so that it can know that something is being edited.
  2. initiate the segue, that is let the prepare(for:) method know that we are calling it in this case.
  3. check the identifier
  4. get an instance to the receiving view controller
  5. assign data

The first step is pretty straightforward: just open AddItemViewController.swift and add the following two variables:

Screen Shot 2019-02-22 at 22.06.41
I do not know why they need to be weak​but, well, such is life…

The first one is an optional ToDoList and the second one is an optional ChecklistItem. They need to be there so that we can access them in the prepare(for:) method, apparently. This part is not really well explained in the videos. It seems typical for all these tutorial to just show you the way and then if you do not understand it is your problem.

Initiate the segue & check the identifier

We already have a prepare(for:) method in ChecklistViewController.swift so we just need to update it. In the first part of the control flow statement we need to add a reference to the todoList so that the one we are trying to access here is the same as the one on the other view controller (*noise of brain starting to melt*).

We then create an alternative to this set of events with an else if segue.identifier == "EditItemSegue". The destination will be of the same kind (that is why we can use the same storyboard item (read: view controller) but then we want to access the cell inside of it so that we can edit it.

Get an instance to the receiving view controller

In the prepare(for:) method one of the parameters is called sender and it corresponds to the part of the screen connected to a segue that the user tapped on.

We get the sender as a UITableViewCell and an its indexPath in the optional binding statement (if let). We then store the corresponding item in the addItemViewController.itemToEdit statement. Here is the new prepare(for:) method:

Screen Shot 2019-02-22 at 22.18.11

Assign the Data

The party is about to begin as we move back to AddItemViewController.swift and we modify the viewDidLoad method. Just under the super.viewDidLoad() call we optional bind itemToEdit to item and we set:

  • the title to “Edit Item”
  • the textField.text to item.text so that we get the same text of the cell we tapped upon.
  • we enable the addBarButton.

The only problem now is that, clicking the Done button, the data is not getting passed back to the previous view controller. Time for another challenge it seems.

A little comment here: all this calling many variables with the same name is incredibly confusing to me: I have an item here, an item there… which one is which?


What is the problem?

The issue here is, as we saw, that editing the item doesn’t modify the original list. This is a big issue and understanding why it is so is not easy. After all, our prepare(for:) method looks pretty good.

The problem is in our done() method, which creates a new ChecklistItem and uses it to populate the todoList. Also, we call the addItemViewController method on the delegate here but we do not assign self to be the delegate in the else if statement of the prepare(for:) method. Phew 😅 , this is a big one!

Apparently we need to enrich our protocol so that it can also edit items instead of just adding them. Let’s see how to do that!


NSObject

My programming apprentice path starts with Swift and I have only done a bit of practice in the C programming language so I have no idea what Objective-C looks like but it seems that we have to inherit some of its functionalities to allow our app to pass data between view controller. I know nothing of all this but this seems pretty low level stuff that should be included in Swift but well, we will see how life evolves! Let’s cut the chatter and get back to work!

Delegation

First thing we need to add this line to the prepare(for:) method, right at the end: addItemViewController.delegate = self, exactly as in the first part of the method.

Update the protocol

Back in AddItemTableViewController.swift we want to enrich the functionalities of our protocol (personally, now that I think about it, I would have created the protocol as a separate Swift file but, I guess there three lines of code do not make any harm). Add this line to the protocol declaration: func addItemViewController(_ controller: AddItemTableViewController, didFinishEditing item: ChecklistItem).

Edit the IBActions

From both the cancel and done IBActions we need to remove the call to the popViewController method. Just erase all the line entirely. They are not needed because the delegate method is already doing that.

In the done method some more work is required (we actually need to rewrite it in a clearer way):

Screen Shot 2019-02-22 at 23.12.13

This control flow + optional binding segment allows us to distinguish between two states of our app, the one in which we are editing the item and the one in which we are adding a new todo. This is a brilliant way of coding in my ignorant opinion (IMIO, just crafted right now!) because, finally, I feel I understand how things work. Would I be able to rewrite all of this code on my own? Of course not! But I hope this will come, sooner or later!

Update the extension

Back in ChecklistViewController.swift we need to populate the addItemViewController(_: didFinishEditing:) method. The first thing to do is to find where my ChecklistItem is located and, to do that, we need to open ChecklistItem.swift and add : NSObject after the class declaration so that we are inheriting the functionality of this apparently very powerful piece of code!

We then store the index of the item we are trying to edit (if it exists) with optional binding, get its “address” (the indexPath) and then, if we find a cell in that location BUM, we call configureText! Brilliant, really!

Then we just need to pop our ViewController to go back to the main screen! Amazing! Here’s the code!

Screen Shot 2019-02-22 at 23.20.42

Everything is fine!… or is it?

We now feel so good because our app is able to edit the items and to bring them back to the Checklist view controller but, did we try to see if the rest of the app is still working? No we didn’t because if we did we would have noticed that our todoList.todos array is out of sync.

If we try to add a new ChecklistItem the app will crash because it will expect index 5 +/- 1 and will get asked for 7, not finding it.

To fix this we need to erase the call to creating a new item in the addItemViewController(_: didFinishAdding:) method and modify the rowIndex constant with a - 1 in the end.

I’m not really sure why this is working now because we based all our building on the fact that we needed to create a new row at todoList.todos.count, that is, at the end. Maybe it is because in the done method we are already asking for a todoList?.newTodo() which appends a new item to the array…

It still sounds nebulous to me and I wonder when I will be able to understand this. If when we are calling the didFinishAdding method we are looking for the count -1 place, does it mean that our app has an increasing amount of empty cells at the bottom of the array? Isn’t this a waste of resources? Is there a better way of doing this?


Refactoring

At this point Brian suggests that the name AddItemViewController is not a good one and that we should substitute it with ItemDetailV … I mean, after spending all this time with such long names are we really now starting to abbreviate? Come on… If I recall correctly in the iOS Apprentice book this class is called ItemDetailViewController in the end, but, again, such is life! Let’s move on, we are almost done!


Ok… that was a joke!

In our next challenge we had to play with Xcode’s refactoring tools so that we change the name of the class to ItemDetailViewController and the name of the protocol methods to itemDetailViewController...

This was done either via the contextual menu of the class name or via the find and replace tool.

Really nice!


So we came at the end of this Fourth Part!

I can’t wait to begin the new one!

See you there!

Happy coding!


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!

Published by Michele Galvagno

Professional Musical Scores Designer and Engraver Graduated Classical Musician (cello) and Teacher Tech Enthusiast and Apprentice iOS / macOS Developer Grafico di Partiture Musicali Professionista Musicista classico diplomato (violoncello) ed insegnante Appassionato di tecnologia ed apprendista Sviluppatore iOS / macOS

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: