Hacking with Swift – Learning Project 5

Day 27 of the 100 Days of Swift initiative started with an article on how closures capture values. Oh dear, was it hard … Paul—the author—explained it very clearly and the concepts themselves seem to be… well… fine to understand, but if I had to repeat something about what I read I would be really hard pressed. There were many examples but I think I lack the basics of what a memory leak is and on how memory functions in a computer to see how all this can be a problem. He insisted that we go on and study that article before tackling Project 5 and so I did, I hope this gets clearer in time.


Setting up

This project is going to be an anagram game so it goes back to Table Views; we thus end up with:

  • a Table View Controller embedded in a Navigation Controller in the storyboard
  • A ViewController.swift files which changes its inheritance to UITableViewController

We also need to import the start.txt file from the Hacking with Swift GitHub repository inside of our project before starting to write any code.


Reading from disk

This is a very important feature. Up until now we have interacted with files in two ways:

  1. we have imported a fixed set of images in the assets.xcassets folder and then loaded them from there (Guess the flag, project 2)
  2. we have imported images in the app’s directory and therefore have accessed the FileManager via a uniform resource locator (URL) to store them into an array to be used in our app (Storm Viewer, project 1).

Now we learn a new way of accessing data because we are having a single file which contains over 12.000 words (in the form of strings separated by a “\n” special character). We first need to create two arrays: one to store all of the words and one to store the words already used by the user. In this way we will avoid repeating (even if, with 12.000 words, that is highly unlikely!).

We create now a startGame() method:

Screen Shot 2019-02-27 at 21.02.41

This does the following:

  • sets the title of the view controller to a random element in the allWords array.
  • it removes everything from the usedWords array
  • it reloads the data of the table view (yes, if you do not do this is as if you put your tasty lasagne in the oven and didn’t turn it on!)

Next up, accessing the file and loading it into our code:

Screen Shot 2019-02-27 at 21.05.04

This all happens inside viewDidLoad():

  • we need to be hyper-careful so we check if in our main folder of the app Bundle there is a file called “start” with a “txt” extension.
  • if so, we try to see if we can extract a String from the content of that file.
  • if also this succeeds we insert the components of that string in the allWords array.

We also write something that will happen if the array is empty, for any reason.

A call to startGame()is all that’s needed to finish this first round.


We still haven’t created the table view data source but this should be second nature for us by now.

Screen Shot 2019-02-27 at 21.17.46

By now it is unclear to me why we are using the usedWords as our base of operation as this is empty by now… puzzled…


Closures here we come

The next part is what we have been prepared for in the introductory article.

We create a UIBarButtonItem shaped as the plus button (+) and we call the #selector(promptForAnswer) action, which we have not written yet. The target here is again self, the view controller we are in right now.

Next, we create the method, following these priorities:

  • Create the alert controller with a title of “Enter answer”
  • Add a text field to the just created alert controller (this will be element 0 of an array of text fields —more on this in just a moment—)
  • Create an UIAlertAction with:
    • title “Submit”, style .default
    • a trailing closure as the decision handler which will contain some code to run:
      • as we learned that closures can capture data they work with an prevent (intentionally or not) that same data from being destroyed, the parameters we are working with need to be marked as weak so that the data becomes sort of an optional.
      • the closure expects a UIAlertAction parameter but as we do not work with it in the closure it can be marked as _.
      • check that the first element of the text fields of the alert controller array contains some text via a guard let statement followed by optional chaining.
      • call the submit method on that text to check for its validity.

Clearly, this last method has not been written yet so we will get a compile error (by the way, writing a placeholder method makes the error go away).

Here’s the beast of a method:

Screen Shot 2019-02-27 at 22.04.25

Now Day 28 is upon us and we are presented with a rather shocking news: even if Strings are nothing else than an array of characters (this comes way back from the C programming language, at least) we cannot use integer subscripting (the way we use to access an item of an array) to read a specific character of said string.

The reasons are for sure much more than valid because, at least in theory, implementing such a behaviour in a programming language should not be that hard —please keep in mind the ignorant writing these words so take my words with a good punch of grain of salt!—. As Paul explains, the main reasons seems performance, that is to prevent people from using integer subscripting inside loops. As usual, my constant (and endless, and insatiable…) thirst for knowledge would like to know more, to understand more but I am slowly learning to be patient (and I should underline slowly!)


Preparing to submit the words

We left yesterday with an empty submit method. It is now time to fill it in. It has the following priorities (always good to figure out the logic before even writing a single line of code):

  • check if the submitted word can be made from the given letters
  • check if it has already been used
  • check if it is a correct English word

If and only if these three checks pass we then insert the new row in the table view, using the insertRows method instead of the reloadData() one for optimisation reasons. We also give another look at the IndexPath data type, which is a tuple made up of a row and a section to describe where we are in the table view. One can see it as a sort of coordinate system, just with sections and rows instead of xs and ys.

To prepare this method we need to write three new placeholder methods that accept a string as parameter and return a boolean as a result. These three methods will be responsible for checking the three priorities we talked about just above.

Screen Shot 2019-02-28 at 14.11.12

Check for valid answers

As said, our three methods by now are simply empty and are doing zero to no good to us (apart from returning true, which makes us feel good!).

isOriginal

Let’s start from the simplest: isOriginal. This method has to check whether we have already tried the word we are about to check. There is a methods of arrays that can check whether an array contains a member of the array’s element called .contains(Element). So, if we want it to return true, we need that word NOT to be there. The ! “not” operator, when placed before a boolean, inverts its logic, so the code is as follows:

Screen Shot 2019-02-28 at 19.41.15

isPossible

This ramps up the difficulty a notch: we need to check if the word used is using all of the letters in the word to match, no more and no less. That is, if our word contains three times the letter ‘s’ and the word to match had it only twice, the check should fail. Here we can use the fact that strings are arrays of character so we proceed in the following way:

  • we check that we have a title (just in case something went awfully wrong in our app). We use guard var instead of guard let. The reason is that we are going to use that object inside a loop and … drumroll … modify it! We cannot modify a constant so we are going to need to use a variable!
    • once we are sure we have a title, we make it lowercased (because ‘A’ is different from ‘a’ and so on…) and store it into our variable for use inside the upcoming loop.
  • for every letter in the word parameter (our word!) we want to check if that letter exists in the word to check using the .firstIndex(of:) method of arrays, which returns the index of the first time that element is found! This method used to be .index(of:) but it could have returned ambiguous results, especially since Arrays are not Sets and they allow duplicate elements.
    • if we find it, we remove that letter from the copy of the title we created above and move on. At any point when we do not find a letter the method will return false and make us exit the loop and the method!

Here is the method:

Screen Shot 2019-02-28 at 19.56.39

isReal

This is an amazing part: did you know that the computer can check for your grammar? Well… of course you knew, because every single word processor in commerce has a grammar checker! I just hope you didn’t think this was an easy thing to do!

Fortunately, there is a subclass of NSObject (Objective-C stuff) called UITextChecker that does just that! But brace yourself, there is a lot to understand here:

  • first: we create an instance of UITextChecker to use it later in the method.
  • second: we select a range to be checked. NSRange is, according to the Documentation, “A structure used to describe a portion of a series, such as characters in a string or objects in an array.” BINGO! We create an instance of it using the (location: length:) initialiser. Always browsing the documentation we can discover that the function called is (or should be? Please correct me!) NSMakeRange(_:_:) which creates a new NSRange from the specified values. As a location we input 0 which means we want to start from the beginning of the string which will be passed and for length we want to use this word.utf16.count.
    • So, why not just word.count? This is because of how characters are considered in Objective-C compared to Swift (or, better, the other way around). According to the “Look-up” function of macOS we get this in regard of UTF-16.

UTF-16 is a character encoding capable of encoding all 1,112,064 valid code points of Unicode. The encoding is variable-length, as code points are encoded with one or two 16-bit code units. UTF-16 arose from an earlier fixed-width 16-bit encoding known as UCS-2 once it became clear that more than 216 code points were needed.

In theory they are the same thing, in practice they aren’t so we need to use what our code wants to see passed itself.

  • Now the moment of truth: inside of a constant we store the call to a method of UITextChecker, specifically rangeOfMisspelledWord, which initiases a search of a range of a string for a misspelled word. I mean, I am still jumping on my chair: do you understand how amazing is this? Look at the method in the documentation, it is much clearer if you see it:
Screen Shot 2019-02-28 at 20.15.04
  • we pass our word as the string to check, our range for the range, 0 as the starting offset (even if we have already used the location parameter before, which means I may have misunderstood something), false as wrap because we want to check only once and "en" as language for English.
  • finally, after waiting for this awesome method to run, we return the result and we set it equal to NSNotFound which comes from a time where optionals did not yet exist. It is an Integer, which means that the programmer who created this connected a specific number to something that cannot be found. I recall when I was starting with the CS50 course with David J. Malan that the return of a function in C could be 0 if everything was fine or another number if fine was not! Then any number could be connected with some specific behaviour. Brilliant! Do you recall the “HTML error 404, page not found”? Well, that’s it essentially!
    • Anyway, from the Documentation:

      Returns

      The range of the first misspelled word encountered or {NSNotFound, 0} if none is found.

Here is the finished method, after which we can build and run:

Screen Shot 2019-02-28 at 20.23.04

Errors on building and running I got:

2019-02-28 19:36:52.480560+0100 Word Scrambler[52479:9296635] [AXMediaCommon] Unable to look up screen scale

2019-02-28 19:36:52.480685+0100 Word Scrambler[52479:9296635] [AXMediaCommon] Unexpected physical screen orientation

2019-02-28 19:36:52.526386+0100 Word Scrambler[52479:9296635] [AXMediaCommon] Unable to look up screen scale

2019-02-28 19:36:52.555889+0100 Word Scrambler[52479:9296635] [AXMediaCommon] Unable to look up screen scale

2019-02-28 19:36:52.556001+0100 Word Scrambler[52479:9296635] [AXMediaCommon] Unexpected physical screen orientation

They do not seem to be anything bad but I wonder what they are. If you can help me figure it out please let me know.


What if you didn’t make it?

Our app is complete, and it is working but … well … we are not doing anything if the user makes a mistake, that is there is no feedback on their hard work!

The solution here is pretty simple but very important:

  1. we need to exit the method if our word is good using the return keyword.
  2. we need to write an else statement for every single if case we wrote.
  3. we need to present an alert controller which will be coherent with what kind of mistake the user make.

To do this we create two constants called errorTitle and errorMessage of type String to use later in our alert but we do not initialise them, the future of the method will determine their destiny!

We then proceed as we have already done in other projects and that’s it, we are done. Here is the updated method:

Screen Shot 2019-02-28 at 20.36.02

As always a big thank you to Paul Hudson for this wonderful learning opportunity.

You can find the finished project here on GitHub.

Thank you very much for following me up to here!


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

2 thoughts on “Hacking with Swift – Learning Project 5

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

Create your website with WordPress.com
Get started
%d bloggers like this: