A missing piece in SwiftUI that provides lazy image loading.

  • LazyImage for SwiftUI
  • LazyImageView for UIKit and AppKit

It uses Nuke for loading images and has many customization options. It also supports GIF rendering thanks to Gifu. But GIF is not the most efficient format, so NukeUI also supports playing short videos out of the box.

WARNING. It’s work-in-progress. Feel free to try it at your own risk or wait for 1.x.


The view is instantiated with a source.

struct ContainerView: View {
    var body: some View {
        LazyImage(source: "")

The view is called “lazy” because it loads the image from the source only when it appears on the screen. And when it disappears (or is deallocated), the current request automatically gets canceled. When the view reappears, the download picks up where it left off, thanks to resumable downloads.

The source can be anything from a String to a full ImageRequest.

LazyImage(source: "")
LazyImage(source: URL(string: ""))
LazyImage(source: URLRequest(url: URL(string: "")!))

let request = ImageRequest(
    url: URL(string: ""),
    processors: [ImageProcessors.Resize(width: 44)]
LazyImage(source: request)

Learn more about customizing image requests in “Image Requests.”

If you already have an image ready to be displayed, use a dedicated initializer.

// Display a regular image
LazyImage(image: UIImage("my-image"))

// Display an animated GIF
LazyImage(image: ImageContainer(image: UIImage(), type: .gif, data: data))

LazyImage is highly customizable. For example, it allows you to display a placeholder while the image is loading and display a custom view on failure.

LazyImage(source: "")
    .placeholder {
            .frame(width: 30, height: 30)
    .failure { Image("empty") }

The image view is lazy and doesn’t know the size of the image before it downloads it. Thus, you must specify the view size before loading the image. By default, the image will resize preserving the aspect ratio to fill the available space. You can change this behavior by passing a different content mode.

LazyImage(source: "")
    .contentMode(.center) // .aspectFit, .aspectFill, .center, .fill
    .frame(height: 300)

When the image is loaded, you can add an optional transition.

LazyImage(source: "")
    .transition(.fadeIn(duration: 0.33))

You can pass a complete ImageRequest as a source, but you can also configure the download via convenience modifiers.

LazyImage(source: "")
    .processors([ImageProcessors.Resize(width: 44])

You can also monitor the status of the download.

LazyImage(source: "")
    .onStart { print("Task started \($0)")
    .onProgress { ... }
    .onSuccess { ... }
    .onFailure { ... }
    .onCompletion { ... }

And if some API isn’t exposed yet, you can always access the underlying LazyImageView instance. For example, you are currently going to need to access the underlying view to enable experimental video playback support:

LazyImage(source: "")
    .onCreated { view in 
        view.videoGravity = .resizeAspect

LazyImageView is a LazyImage counterpart for UIKit and AppKit with the equivalent set of APIs.

let imageView = LazyImageView()
imageView.placeholderView = UIActivityIndicatorView()
imageView.priority = .high
imageView.pipeline = customPipeline
imageView.onCompletion = { print("Request completed")

imageView.source = ""


  • GIF support is currently limited to iOS and tvOS (macOS support in progress)
  • There is a known race condition in Gifu with an outsdanting PR with a fix
  • The support for watchOS is there but is currently limited
  • More improvements to video are coming. For example, generating and displaying a preview for a first frame before the entire video is loaded.

Minimum Requirements

NukeUI Swift Xcode Platforms
NukeUI 0.1 Swift 5.3 Xcode 12.0 iOS 11.0 / watchOS 5.0 / macOS 10.13 / tvOS 11.0

LazyImage is available on the following platforms: iOS 13.0 / watchOS 7.0 / macOS 10.15 / tvOS 13.0


NukeUI is available under the MIT license. See the LICENSE file for more info.