We all enjoy fast and responsive apps that feel good when you use them. Therefore as iOS developers we spend a lot of time on delivering the best possible experience to the user. This becomes extra important when your application has to perform some long running operation, for example downloading a large video. Depending on the size of the download, speed of the user’s internet connection, or hardware this could take a while, which can be frustrating for the user. We can improve such an experience by giving the user a sense of the current work that is happening and how far that work has progressed by using a progress indicator.

Lately I have discovered the Progress class from Foundation which allows us to do just that. This weeks blogpost will show you the basics of Progress by taking you through an example of how to show the progress of downloading an image to the user.

What is Progress?

Progress is a class that represents the completion of some work that an application is doing for example downloading or uploading a file to the server. Many of the api’s that we use on a daily basis report their progress with Progress for example NSURLSession which we get to later in this blogpost. Of course you can also use Progress to report progress from the custom components in your application and if the work your application is doing consists of multiple sub steps you can even use composition to create a tree of progress objects.

The basics of Progress

Progress has three important properties that you should know about:

  • totalUnitCount
  • completedUnitCount
  • fractionCompleted

Let’s have a look at each one of them.

totalUnitCount

The totalUnitCount represents how many units of work a Progress object should track, for example the amount of bytes of a download.

completedUnitCount

The completedUnitCount reports how much work of the total amount of work that the progress tracks has completed.

fractionCompleted

fractionCompleted tells you how much of the work has completed. It is a double value between 0 and 1 where 0 means that none of the work has completed and 1 means that all the work has completed.

Localization

Progress also has a property called localizedDescription and localizedAdditionalDescription which can be used to provide the user with more information on what is happening in your application. Through the kind property you can tell the progress object what its unit represents and to format the progress as such, for example if you set it to ProgressKind.file the localizedAdditionalDescription gives you a byte formatted string.

An example

In this example we are going to download an image from the internet and display the progress to the user. Download the sample project if you want to follow along with this example.

The example project has one view controller which is very simple. It contains an UIImageView for showing the downloaded image, a UIButton to trigger a download, two UILabels that will contain text to explain the user what’s going. It also contains a UIProgressView which is a view that displays the progress in a progress bar.

Our view controller also has a reference to an ImageDownloader which has a method downloadImage(with:completion:) that downloads the image from the passed in URL This method returns a progress object which we will use to update our UI and has a completion handler with an optional image that gets called once the download has finished.

Inside the downloadImage(with:completion) method we create a URLSessionDownloadTask with the passed in url of the image. Since iOS 11 a task has a property called progress that contains the tasks progress object which we can use to update our UI.

func downloadImage(with url: URL, completion: @escaping ((UIImage?) -> Void)) -> Progress {
        let task = session.downloadTask(with: url) { (url, response, error) in
          ...
            do {
                let imageData = try Data(contentsOf: url)
                let image = UIImage(data: imageData)

                completion(image)
            } catch {
              completion(nil)
            }
        }

        task.resume()

        return task.progress
    }

When a download action is triggered we call the download function and store the returned progress object in a property called downloadProgress in our view controller. It has a property observer in which we attach the new progress object to the UI. To display the progress in our progress view we set downloadProgress to the observedProgress property of the view. We use KVO to update our progress label every time the localizedAdditionalDescription changes. KVO notifications of properties on Progress are sent from the thread on which the progress was updated and therefore we have to dispatch our update of the progress label onto the main queue to make sure our UI gets updated on the main thread.

private var downloadProgress: Progress? {
        didSet {
            observation?.invalidate()
            progressView.observedProgress = nil

            guard let progress = downloadProgress else {
                return
            }

            progressView.observedProgress = progress
            progressDescriptionLabel.text = progress.localizedDescription

            observation = progress.observe(\.localizedAdditionalDescription, options: [.initial, .new]) { [weak self] _, change in
                DispatchQueue.main.async {
                    self?.progressLabel.text = change.newValue
                }
            }

        }
    }

Conclusion

Next time when your application is performing some long running task consider using Progress to let the user know what is happening in the application. As you can see from the example some Cocoa api’s already support progress reporting and therefore just require a few steps to make it work. But there is much more you can do with Progress. You could for example create a tree of progress objects or use the progress object to cancel, pause or resume work. Take a look at it and have some fun!

Contact me on Twitter @kairadiagne if you have any questions, comments or feedback.