URL Image view in SwiftUI

Dmytro Anokhin
4 min readJun 7, 2019

--

Views that download image from URL are extremely popular in iOS development. It is very convenient to set a URL on a view and get an image displayed. And not to worry about networking, caching, and synchronization.

SwiftUI came out without native support for downloading images. This presented an opportunity to create one, native to SwiftUI, and without UIView legacy.

After more than a year of development, and two released versions, I have some thoughts why there is no native one. I will keep `em for the end of the article. I will also share how to create one yourself, and pitfalls you may encounter.

But first, let me introduce you to the URLImage package.

Using URLImage

URLImage uses Swift Package Manager so you only need to copy/paste the package URL:

https://github.com/dmytro-anokhin/url-image

in Xcode (File/Swift Packages/Add Package Dependency...) and import URLImage module.

You initialize the URLImage object with a URL of an image and after some time it gets displayed. Similar to how you initialize the Image object with the name of an image in your app bundle or the system one.

As oppose to standard Image object, that synchronously fetches the image, URLImage reflects a state of asynchronous operation. Actually a chain of operations: cache lookup, download, caching, and decoding.

All you need is the URL and a closure to customize presentation of the downloaded image.

In the closure you can add various modifiers specific to the image.

Asynchronous operation can be in the one of four states: empty or not started, in progress, successfully completed, or failed. Each of four states is represented by a view. URLImage initiates download and switches this views to reflect the state change.

You can provide a view for each state. Some closures contain additional arguments, like download progress, encountered error, or image information.

URLImageOptions and URLImageService

There is a way to customize downloads. Things like when images can be deleted, caching policies, HTTP headers, and many more.

This example sets custom identifier so the image can be looked up in cache using it, rather than the URL.

URLImageService object provides centralized way to set image options used by default, and also cleanup expired images.

How URLImage works

URLImage utilizes approach in detail described in one of my articles: Building reusable content loading view in SwiftUI.

The idea, in a nutshell, is to load an image in an object that implements ObservableObject protocol. The view subscribes to this object, using ObservedObject property wrapper, and reflects the loading state in its body method. View-model relationship of SwiftUI and Combine.

Here’s how to build one in 5 min or so:

Of course, this approach can be applied to any kind of content.

The question is why we need a package if this can be so simple? Let me explain.

Downloading image data

You may notice that the example uses data task, created by dataTaskPublisher method. This creates in memory buffer to accumulate data. Unnecessarily using memory, and potentially harming performance, when you have large images.

You may want to use download task, to download an image into a temporary file. However, you must your own Publisher implementation for URLSessionDownloadTask since URLSession doesn’t provide one.

Another case to consider is what if the same image is used in multiple places. In order not to duplicate downloads you can use Publishers.Share type.

Caching downloaded data

Data task uses URLCache and, by default, respects HTTP Cache-Control header. This is very useful if you have dedicated CDN and your app can not work without internet connection. Exactly how a web browser do.

You can configure the URLImage package to work like that: Using URLCache.

For offline usage; to create your own expiry date policy, or if you opt to use download task; or if you need to uniquely identify images using identifier, not related to URL; you will have to build you own cache.

When caching files, in order to achieve best lookup performance, you should use a database to keep the index of downloaded files. Core Data is perfect for this task, and URLImage package describes Core Data model programmatically, so you don’t need to bundle unnecessary resources (also because when the first version was released Swift Packages didn’t support resources).

In-memory cache

You may want to consider second level of caching. In-memory cache provides quick lookup for recently accessed images to reduce disk access. URLImage uses standard NSCache for this purpose.

When to start download

In the example playgroundURLImage loads its content from the initialization method. In SwiftUI view initialization is typically very close in lifecycle to presentation so it is a good place.

However, you may want to use URLImage inside List, LazyVStack, or LazyVGrid, ie a view that displays a collection of items. This is where it is beneficial to use appearance callbacks (onAppear/onDisappear) to start and cancel downloads. Maybe with a small delay to account for user scrolling over a collection of items.

The URLImage package provides options to control download behaviour: loadImmediately, loadOnAppear, and cancelOnDisappear.

It may be surprising that there is no standard URL image view in SwiftUI. As you can see, it is not hard to build a simple one yourself. What makes it challenging is to tailor the solution for a concrete case.

In some apps you may want to have behaviour as close to web browser as possible. Good example is e-commerce apps, where you have access to proper CDN and control HTTP headers.

Other apps, like content readers or social networks, may need to download and store images for offline usage.

This leads to the second reason: images are very close to UI, however they are still part of data, and ideally images must be downloaded and stored on the data model level.

Anyways, this are just my thoughts, and hopefully we will see native URL image view someday. Till then I’ll I continue developing URLImage package.

https://github.com/dmytro-anokhin/url-image

Cheers! 👋

--

--

Dmytro Anokhin

iOS Developer, here to share best practices learned through my experience. You can find me on Twitter: https://twitter.com/dmytroanokhin