Nuke Image Loading System

repository·main·Indexed 27 days ago

https://github.com/kean/nuke

A high-performance, modular image loading system for Apple platforms (iOS, macOS, watchOS, tvOS, visionOS). Nuke provides a robust caching system, advanced image processing, and support for formats like HEIF, WebP, and GIF. It features a plugin-based architecture with modular components including NukeUI for SwiftUI and UIKit/AppKit, NukeExtensions, and NukeVideo for short video decoding and playback.

Tokens
26.3K
Snippets
86
Records
173
Agent score
93%

What's inside Nuke

  1. Overview of Nuke Image Loading System

    main
    Nuke is a high-performance image loading system for Apple platforms (iOS, macOS, watchOS, tvOS, visionOS). It provides a robust caching system (memory and disk), advanced image processing, decompression, request coalescing, prefetching, and support for various formats including HEIF, WebP, and GIF. It is designed to be lean, fast to compile, and highly customizable via an extensible architecture.
  2. Overview of Nuke Image Format Support

    main

    Nuke provides built-in support for standard image formats such as jpeg, png, and heif. Beyond basic formats, Nuke's architecture allows for extending support to custom image formats and provides capabilities for:

    • Progressive decoding
    • Animated image rendering
    • Progressive animated image rendering
    • Drawing vector images (directly or via bitmap conversion)
    • Parsing thumbnails from image containers
  3. Implement image prefetching in UICollectionView

    main

    To improve user experience by loading images ahead of time, use UICollectionViewDataSourcePrefetching in conjunction with ImagePrefetcher.

    1. Set isPrefetchingEnabled to true on your UICollectionView.
    2. Set the prefetchDataSource to your view controller.
    3. In collectionView(_:prefetchItemsAt:), call prefetcher.startPrefetching(with:) using the URLs for the provided index paths.
    4. In collectionView(_:cancelPrefetchingForItemsAt:), call prefetcher.stopPrefetching(with:) to cancel tasks for items no longer in the prefetch window.

    Warning: If you use ImageProcessors (e.g., ImageProcessors.Resize) when displaying images, you must use the exact same processors during prefetching. Otherwise, the prefetcher will cache the original image instead of the processed version, defeating the purpose of prefetching.

    final class PrefetchingDemoViewController: UICollectionViewController {
        private let prefetcher = ImagePrefetcher()
        private var photos: [URL] = []
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            collectionView?.isPrefetchingEnabled = true
            collectionView?.prefetchDataSource = self
        }
    }
    
    extension PrefetchingDemoViewController: UICollectionViewDataSourcePrefetching {
        func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
            let urls = indexPaths.map { photos[$0.row] }
            prefetcher.startPrefetching(with: urls)
        }
    
        func collectionView(_ collectionView: UICollectionView, cancelPrefetchingForItemsAt indexPaths: [IndexPath]) {
            let urls = indexPaths.map { photos[$0.row] }
            prefetcher.stopPrefetching(with: urls)
        }
    }
  4. Share a DataCache between an app and an extension

    main

    To share a disk cache between your main app and an extension (like a Notification Service Extension), point both to a directory within a shared App Group container. In the extension, set isSweepEnabled = false so that the extension does not attempt to enforce size limits or perform LRU cleanup; let the main app handle that.

    let sharedCacheURL = FileManager.default
        .containerURL(forSecurityApplicationGroupIdentifier: "group.com.myapp")
    
    // Main app
    ImagePipeline.shared = ImagePipeline {
        $0.dataCache = try? DataCache(path: sharedCacheURL)
    }
    
    // Extension — reads/writes the same cache but skips LRU sweeps
    ImagePipeline.shared = ImagePipeline {
        $0.dataCache = {
            let cache = try? DataCache(path: sharedCacheURL)
            cache?.isSweepEnabled = false
            return cache
        }()
    }
  5. Load images using ImagePipeline

    main

    The ImagePipeline handles downloading, caching, and preparing images. You can load an image directly using the async image(for:) method on the shared pipeline.

    For more granular control, such as monitoring download progress, use imageTask(with:) to create an ImageTask. You can iterate over the progress stream and then await the image or response property.

    // Simple async load
    let image = try await ImagePipeline.shared.image(for: url)
    
    // Load with progress monitoring
    func loadImage() async throws {
        let imageTask = ImagePipeline.shared.imageTask(with: url)
        for await progress in imageTask.progress {
            // Update progress
        }
        imageView.image = try await imageTask.image
    }
  6. Monitor or modify DataLoader behavior

    main

    You can access the DataLoader from the ImagePipeline configuration to set a delegate. This is useful for monitoring network traffic (e.g., with Pulse) or handling authentication challenges via URLSessionTaskDelegate methods.

    Note that the DataLoader retains the delegate.

    // To monitor with Pulse:
    (ImagePipeline.shared.configuration.dataLoader as? DataLoader)?.delegate = URLSessionProxyDelegate()
    
    // To handle authentication challenges:
    (ImagePipeline.shared.configuration.dataLoader as? DataLoader)?.delegate = YourDelegate()
    
    final class YourDelegate: URLSessionTaskDelegate {
        func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
            // Handle authentication challenge...
        }
    }
  7. Use LazyImage in SwiftUI with Nuke 12

    main

    In Nuke 12, LazyImage uses SwiftUI.Image and requires a content closure to manage sizing and layout (similar to AsyncImage). Transition animations are disabled by default and must be provided via a transaction. Progress updates no longer trigger content reloads; use an ObservedObject to handle progress independently.

    // Basic usage with custom sizing
    LazyImage(url: URL(string: "https://example.com/image.jpeg")) { state in
        if let image = state.image {
            image
                .resizable()
                .aspectRatio(contentMode: .fill)
        }
    }
    
    // Using animations
    LazyImage(url: URL(string: "https://example.com/image.jpeg"),
              transaction: .init(animation: .default)) { $0.image }
    
    // Handling progress without reloading content
    LazyImage(url: URL(string: "https://example.com/image.jpeg")) { state in
        if state.isLoading {
            ProgressView(state.progress)
        }
    }
    
    struct ProgressView: View {
        @ObservedObject var progress: FetchImage.Progress
        var body: some View {
            Text("progress.fraction * 100 %")
        }
    }
  8. Migrate custom ImageProcessing implementations

    main

    The ImageProcessing protocol was updated to support caching processed images. Custom processors no longer conform to Equatable directly; instead, they must provide a String identifier (for cache keys) and a hashableIdentifier (AnyHashable) for efficient memory cache lookups. Additionally, the process(image:context:) method now accepts an optional ImageProcessingContext?.

    struct GaussianBlur: ImageProcessing, Hashable {
        let radius: Int
    
        func process(image: Image, context: ImageProcessingContext?) -> Image? {
            return /* create blurred image */
        }
    
        // Prefer to use reverse DNS notation.
        var identifier: String { return "com.youdomain.processor.gaussianblur-\(radius)" }
        var hashableIdentifier: AnyHashable { return self }
    }