Encapsulating the User in a Function

Below is code that many of us have written. Some of us might have an example of this code in more than one place in an app. There is probably even an Apple example that looks very much like it.

This code might be found in a profile edit screen, for instance, because it allows the user to change their avatar picture. When the changeAvatar method is called, the code will put up a UIAlertController as an action sheet asking the user to choose between camera or library access. Then it presents a properly configured UIImagePickerController. Once the user has taken/chosen a picture, the code uploads the picture to the provided API. If the upload is successful, the user’s avatar is changed; otherwise an alert is displayed informing the user that there was a problem.

In this post I want to talk about the notion of “encapsulating a user interaction” in a function.

Introductory texts on promises will tell you that a promise type is best for encapsulating an asynchronous task. An obvious example in the above code is the API method which has the signature of func upload(avatar: Data, completion: @escaping (Result<Void>) -> Void). If we rework our API method to use a promise class, the signature will change to: func upload(avatar: Data) -> Promise<Void> and our ViewController’s UIImagePickerControllerDelegate method will change to:

Now, instead of the taking a block and passing a Result to it, the upload method returns a promise which takes two blocks through its then and catch methods, one for success and one for failure. It is a minor change to be sure, and one that may leave someone new to promises wondering what the benefit is. However, it’s important to notice, and the point of this post, that the network call isn’t the only asynchronous process in this code.

The network call isn’t the only asynchronous process in this code.

The code presented is full of asynchronous processes… We present an action sheet to the user, then wait for them to respond. We present the image picker to the user and again wait for them to respond. We do the network call, which is already covered above, then if the network call errors, we present an alert informing the user and again, wait for them to respond. Starting with the simplest and getting progressively more complex, we will now refactor the code to have all of these async “present then wait” jobs use promises, effectively encapsulating user interactions into functions.

Presenting an Information Alert

The simplest refactoring is the final information alert in case of error. This function needs to present the alert and then fulfill the promise when the user taps the OK button.

With the above extension in place, we can replace the construction and presentation of the final alert with a call to our new method. This refactoring is relativly simple and depending on how long you have been programming, you might even already have this sort of method in place (maybe without the promise,) but what about when we are trying to get information from the user?

Presenting an Action Sheet

Next we will encapsulate the notion of presenting an action sheet to the user and waiting for a response.

In order to call this function, we need to pass in a title, a message (either of which can be empty ""), an array of Strings to display to the user and a source view (in case the sheet is being presented on an iPad.) It returns a promise which will eventually fulfill with the index of the item chosen, or reject with a userCanceled error.

Using choiceIndexUsingActionSheet simplifies our view controller quite a bit. So let’s quickly revisit the changeAvatar method.

Now in the view controller’s changeAvatar method, we are no longer messing with the details of how to present the action sheet and extract the result. Instead we create an array of objects, sourceOptions, that we pass into the method and then we use the option that is at the index the user chose. If the user cancels, we don’t have to do anything special.

When you think about it, the entire process of choosing an avatar image is a user interaction. Can we encapsulate the whole thing into a single function?

Presenting the Image Picker Controller

This is a more complex refactoring so I’m going to approach it in two steps. The first step is to extract the UIImagePickerDelegate into a separate class.

And the ViewController using the above class.

The important thing to notice in the above code is how the ImagePickerDelegate class reports to its caller that an image was chosen (or the user canceled.) Once again we are using a promise that will either fulfill or reject.

In studying the way the ImagePickerController class is used in the ViewController class, you will see that we need to keep in mind that promises are single use objects. Once the promise is fulfilled, we can’t use the same image picker delegate object to obtain another image. This means that we have to ensure that our changeAvatar method creates a new delegate for each UIImagePickerController. At the same time, we need to make sure that the delegate outlives the method, which is why it is kept as a property of the class.

So, let’s revisit the question I asked earler. Can we encapsulate the whole process into a function given that we have to retain the delegate? The answer is yes, and here’s how:

The magic bit in the above function is that delegate is a force unwrapped variable and we refer to it in the always block of the promise. This means that the promise will retain the delegate until the promise completes.

With this new function, the view controller becomes much simpler…

The original ViewController class contained 60 lines of un-reusable code. Now that we have moved a bunch of the code into reusable components, our view controller is down to about 20 lines, and as a bonus we have three new functions that we can reuse in any view controller: displayInformationAlert, choiceIndexUsingActionSheet, and getImage. We have encapsulated the user!

I started programming as a hobby in the late ’70s and professionally in the late ’90s. I am the director of development at Haneke Design.