All skills

Code generator skills that produce production-ready Swift code for common app components. Use when user wants to add logging, analytics, onboarding, review prompts, networking, authentication, paywalls, settings, persistence, error monitoring, CI/CD pipelines, localization, push notifications, deep linking, testing, accessibility, widgets, feature flags, app icons, image caching, pagination, HTTP caching, share cards, social export, subscription lifecycle, referral systems, watermarks, streak tracking, milestone celebrations, what's new screens, lapsed user re-engagement, usage insights, variable rewards, consent flows, account deletion, permission priming, force updates, state restoration, debug menus, offline queues, feedback forms, announcement banners, quick win sessions, Spotlight indexing, App Clips, screenshot automation, background processing, app extensions, data export, or SwiftUI preview sample data and variant matrices.

Use this Skill: https://skilld.dev/gh/rshankras/claude-code-apple-skills/generators

This session only. Nothing lands on disk.

image-loadingtemplates.md

≈4.7k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Image Loading Code Templates

Production-ready Swift templates for image loading pipeline. All code targets iOS 16+ / macOS 13+ (iOS 17+ / macOS 14+ for @Observable) and uses modern Swift concurrency.

Platform Compatibility

#if canImport(UIKit)
import UIKit
typealias PlatformImage = UIImage
#elseif canImport(AppKit)
import AppKit
typealias PlatformImage = NSImage
#endif

ImageCache.swift

import Foundation

/// Protocol for image cache storage.
///
/// Implementations handle memory and/or disk caching.
protocol ImageCaching: Sendable {
    func image(for url: URL) async -> PlatformImage?
    func store(_ image: PlatformImage, for url: URL) async
    func remove(for url: URL) async
    func removeAll() async
}

MemoryImageCache.swift

import Foundation

/// NSCache-based memory image cache.
///
/// - Auto-evicts under memory pressure (NSCache behavior)
/// - Thread-safe without manual locking
/// - Configurable count and cost limits
final class MemoryImageCache: ImageCaching, @unchecked Sendable {
    private let cache = NSCache<NSString, PlatformImage>()

    init(countLimit: Int = 200, totalCostLimit: Int = 100 * 1024 * 1024) {
        cache.countLimit = countLimit
        cache.totalCostLimit = totalCostLimit
    }

    func image(for url: URL) async -> PlatformImage? {
        cache.object(forKey: url.absoluteString as NSString)
    }

    func store(_ image: PlatformImage, for url: URL) async {
        let cost = estimatedCost(of: image)
        cache.setObject(image, forKey: url.absoluteString as NSString, cost: cost)
    }

    func remove(for url: URL) async {
        cache.removeObject(forKey: url.absoluteString as NSString)
    }

    func removeAll() async {
        cache.removeAllObjects()
    }

    private func estimatedCost(of image: PlatformImage) -> Int {
        #if canImport(UIKit)
        guard let cgImage = image.cgImage else { return 0 }
        return cgImage.bytesPerRow * cgImage.height
        #elseif canImport(AppKit)
        guard let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil) else { return 0 }
        return cgImage.bytesPerRow * cgImage.height
        #endif
    }
}

DiskImageCache.swift

import Foundation
import CryptoKit

/// Disk-backed image cache with LRU eviction and TTL expiration.
///
/// Stores images as JPEG files in the Caches directory.
/// Uses SHA-256 hashed filenames to avoid path conflicts.
actor DiskImageCache: ImageCaching {
    private let cacheDirectory: URL
    private let maxDiskSize: Int
    private let maxAge: TimeInterval

    init(
        directoryName: String = "ImageCache",
        maxDiskSize: Int = 250 * 1024 * 1024,
        maxAge: TimeInterval = 7 * 24 * 3600  // 7 days
    ) {
        self.cacheDirectory = FileManager.default
            .urls(for: .cachesDirectory, in: .userDomainMask)[0]
            .appendingPathComponent(directoryName, isDirectory: true)
        self.maxDiskSize = maxDiskSize
        self.maxAge = maxAge

        try? FileManager.default.createDirectory(
            at: cacheDirectory,
            withIntermediateDirectories: true
        )
    }

    func image(for url: URL) async -> PlatformImage? {
        let fileURL = fileURL(for: url)
        guard FileManager.default.fileExists(atPath: fileURL.path) else { return nil }

        // Check expiration
        if let attrs = try? FileManager.default.attributesOfItem(atPath: fileURL.path),
           let modified = attrs[.modificationDate] as? Date,
           Date().timeIntervalSince(modified) > maxAge {
            try? FileManager.default.removeItem(at: fileURL)
            return nil
        }

        // Touch access date for LRU
        try? FileManager.default.setAttributes(
            [.modificationDate: Date()],
            ofItemAtPath: fileURL.path
        )

        guard let data = try? Data(contentsOf: fileURL) else { return nil }
        return PlatformImage(data: data)
    }

    func store(_ image: PlatformImage, for url: URL) async {
        let fileURL = fileURL(for: url)

        #if canImport(UIKit)
        guard let data = image.jpegData(compressionQuality: 0.8) else { return }
        #elseif canImport(AppKit)
        guard let tiffData = image.tiffRepresentation,
              let bitmap = NSBitmapImageRep(data: tiffData),
              let data = bitmap.representation(using: .jpeg, properties: [.compressionFactor: 0.8]) else { return }
        #endif

        try? data.write(to: fileURL)
        await evictIfNeeded()
    }

    func remove(for url: URL) async {
        let fileURL = fileURL(for: url)
        try? FileManager.default.removeItem(at: fileURL)
    }

    func removeAll() async {
        try? FileManager.default.removeItem(at: cacheDirectory)
        try? FileManager.default.createDirectory(
            at: cacheDirectory,
            withIntermediateDirectories: true
        )
    }

    // MARK: - Private

    private func fileURL(for url: URL) -> URL {
        let hash = SHA256.hash(data: Data(url.absoluteString.utf8))
        let filename = hash.compactMap { String(format: "%02x", $0) }.joined()
        return cacheDirectory.appendingPathComponent(filename)
    }

    private func evictIfNeeded() async {
        let fileManager = FileManager.default
        guard let files = try? fileManager.contentsOfDirectory(
            at: cacheDirectory,
            includingPropertiesForKeys: [.fileSizeKey, .contentModificationDateKey],
            options: .skipsHiddenFiles
        ) else { return }

        let totalSize = files.reduce(0) { total, url in
            let size = (try? url.resourceValues(forKeys: [.fileSizeKey]))?.fileSize ?? 0
            return total + size
        }

        guard totalSize > maxDiskSize else { return }

        // LRU: sort by modification date (oldest first)
        let sorted = files.sorted { a, b in
            let dateA = (try? a.resourceValues(forKeys: [.contentModificationDateKey]))?.contentModificationDate ?? .distantPast
            let dateB = (try? b.resourceValues(forKeys: [.contentModificationDateKey]))?.contentModificationDate ?? .distantPast
            return dateA < dateB
        }

        var currentSize = totalSize
        for url in sorted {
            guard currentSize > maxDiskSize else { break }
            let size = (try? url.resourceValues(forKeys: [.fileSizeKey]))?.fileSize ?? 0
            try? fileManager.removeItem(at: url)
            currentSize -= size
        }
    }
}

ImageDownloader.swift

import Foundation

/// Protocol for image downloading.
protocol ImageDownloading: Actor {
    func download(url: URL) async throws -> Data
    func cancel(url: URL)
}

/// Actor-based image downloader with request deduplication.
///
/// If multiple views request the same URL simultaneously,
/// only one download occurs and all callers receive the result.
actor ImageDownloader: ImageDownloading {
    private var activeDownloads: [URL: Task<Data, Error>] = [:]
    private let session: URLSession

    init(session: URLSession? = nil) {
        let config = URLSessionConfiguration.default
        config.urlCache = nil  // We handle caching ourselves
        self.session = session ?? URLSession(configuration: config)
    }

    func download(url: URL) async throws -> Data {
        // Deduplicate: if already downloading, wait for existing task
        if let existing = activeDownloads[url] {
            return try await existing.value
        }

        let task = Task<Data, Error> {
            let (data, response) = try await session.data(from: url)

            guard let httpResponse = response as? HTTPURLResponse,
                  (200...299).contains(httpResponse.statusCode) else {
                throw ImageLoadingError.downloadFailed(url: url)
            }

            return data
        }

        activeDownloads[url] = task

        do {
            let data = try await task.value
            activeDownloads.removeValue(forKey: url)
            return data
        } catch {
            activeDownloads.removeValue(forKey: url)
            throw error
        }
    }

    func cancel(url: URL) {
        activeDownloads[url]?.cancel()
        activeDownloads.removeValue(forKey: url)
    }
}

/// Errors specific to image loading.
enum ImageLoadingError: Error, LocalizedError {
    case downloadFailed(url: URL)
    case decodingFailed(url: URL)
    case processingFailed

    var errorDescription: String? {
        switch self {
        case .downloadFailed(let url):
            return "Failed to download image from \(url.absoluteString)"
        case .decodingFailed(let url):
            return "Failed to decode image from \(url.absoluteString)"
        case .processingFailed:
            return "Failed to process image"
        }
    }
}

ImagePipeline.swift

import Foundation
import SwiftUI

/// Central image loading orchestrator.
///
/// Flow: Memory Cache → Disk Cache → Download → Process → Store
///
/// Usage:
/// ```swift
/// let pipeline = ImagePipeline.shared
/// let image = try await pipeline.image(for: url)
/// ```
@Observable
final class ImagePipeline: @unchecked Sendable {
    static let shared = ImagePipeline()

    private let memoryCache: MemoryImageCache
    private let diskCache: DiskImageCache
    private let downloader: any ImageDownloading
    private let processor: ImageProcessor?

    init(
        memoryCache: MemoryImageCache = MemoryImageCache(),
        diskCache: DiskImageCache = DiskImageCache(),
        downloader: any ImageDownloading = ImageDownloader(),
        processor: ImageProcessor? = nil
    ) {
        self.memoryCache = memoryCache
        self.diskCache = diskCache
        self.downloader = downloader
        self.processor = processor
    }

    /// Load an image from cache or network.
    func image(for url: URL, processing: ImageProcessingOptions? = nil) async throws -> PlatformImage {
        let cacheKey = processing.map { "\(url.absoluteString)_\($0.cacheKeySuffix)" } ?? url.absoluteString
        let cacheURL = URL(string: cacheKey) ?? url

        // 1. Check memory cache
        if let cached = await memoryCache.image(for: cacheURL) {
            return cached
        }

        // 2. Check disk cache
        if let cached = await diskCache.image(for: cacheURL) {
            await memoryCache.store(cached, for: cacheURL)
            return cached
        }

        // 3. Download
        let data = try await downloader.download(url: url)

        guard var image = PlatformImage(data: data) else {
            throw ImageLoadingError.decodingFailed(url: url)
        }

        // 4. Process (optional)
        if let processing, let processor {
            image = try processor.process(image, options: processing)
        }

        // 5. Store in both caches
        await memoryCache.store(image, for: cacheURL)
        await diskCache.store(image, for: cacheURL)

        return image
    }

    /// Cancel a pending download.
    func cancel(for url: URL) async {
        await downloader.cancel(url: url)
    }

    /// Clear all caches.
    func clearCache() async {
        await memoryCache.removeAll()
        await diskCache.removeAll()
    }
}

// For testing: allow injection via SwiftUI Environment
private struct ImagePipelineKey: EnvironmentKey {
    static let defaultValue: ImagePipeline = .shared
}

extension EnvironmentValues {
    var imagePipeline: ImagePipeline {
        get { self[ImagePipelineKey.self] }
        set { self[ImagePipelineKey.self] = newValue }
    }
}

ImageProcessor.swift

import Foundation
import CoreGraphics
import ImageIO

/// Image processing options for the pipeline.
enum ImageProcessingOptions: Sendable {
    /// Resize to fit within target size, maintaining aspect ratio.
    case resize(targetSize: CGSize)

    /// Generate a thumbnail of the specified max pixel dimension.
    case thumbnail(maxPixelSize: Int)

    /// Cache key suffix to differentiate processed variants.
    var cacheKeySuffix: String {
        switch self {
        case .resize(let size):
            return "resize_\(Int(size.width))x\(Int(size.height))"
        case .thumbnail(let size):
            return "thumb_\(size)"
        }
    }
}

/// Handles image processing (resize, thumbnail generation).
struct ImageProcessor: Sendable {

    func process(_ image: PlatformImage, options: ImageProcessingOptions) throws -> PlatformImage {
        switch options {
        case .resize(let targetSize):
            return try resize(image, to: targetSize)
        case .thumbnail(let maxPixelSize):
            return try generateThumbnail(from: image, maxPixelSize: maxPixelSize)
        }
    }

    // MARK: - Resize

    private func resize(_ image: PlatformImage, to targetSize: CGSize) throws -> PlatformImage {
        #if canImport(UIKit)
        let renderer = UIGraphicsImageRenderer(size: targetSize)
        return renderer.image { _ in
            image.draw(in: CGRect(origin: .zero, size: targetSize))
        }
        #elseif canImport(AppKit)
        let newImage = NSImage(size: targetSize)
        newImage.lockFocus()
        image.draw(
            in: NSRect(origin: .zero, size: targetSize),
            from: NSRect(origin: .zero, size: image.size),
            operation: .copy,
            fraction: 1.0
        )
        newImage.unlockFocus()
        return newImage
        #endif
    }

    // MARK: - Thumbnail (ImageIO — most efficient)

    private func generateThumbnail(from image: PlatformImage, maxPixelSize: Int) throws -> PlatformImage {
        #if canImport(UIKit)
        guard let data = image.jpegData(compressionQuality: 0.9) else {
            throw ImageLoadingError.processingFailed
        }
        #elseif canImport(AppKit)
        guard let tiffData = image.tiffRepresentation,
              let bitmap = NSBitmapImageRep(data: tiffData),
              let data = bitmap.representation(using: .jpeg, properties: [.compressionFactor: 0.9]) else {
            throw ImageLoadingError.processingFailed
        }
        #endif

        let options: [CFString: Any] = [
            kCGImageSourceCreateThumbnailFromImageAlways: true,
            kCGImageSourceThumbnailMaxPixelSize: maxPixelSize,
            kCGImageSourceCreateThumbnailWithTransform: true,
            kCGImageSourceShouldCacheImmediately: true
        ]

        guard let source = CGImageSourceCreateWithData(data as CFData, nil),
              let cgImage = CGImageSourceCreateThumbnailAtIndex(source, 0, options as CFDictionary) else {
            throw ImageLoadingError.processingFailed
        }

        #if canImport(UIKit)
        return UIImage(cgImage: cgImage)
        #elseif canImport(AppKit)
        return NSImage(cgImage: cgImage, size: NSSize(width: cgImage.width, height: cgImage.height))
        #endif
    }
}

CachedAsyncImage.swift

import SwiftUI

/// A drop-in replacement for AsyncImage with caching support.
///
/// Uses `ImagePipeline` for memory/disk caching, request deduplication,
/// and optional image processing.
///
/// Usage:
/// ```swift
/// CachedAsyncImage(url: user.avatarURL) { image in
///     image.resizable().aspectRatio(contentMode: .fill)
/// } placeholder: {
///     ProgressView()
/// }
/// ```
struct CachedAsyncImage<Content: View, Placeholder: View>: View {
    let url: URL?
    let processing: ImageProcessingOptions?
    let content: (Image) -> Content
    let placeholder: () -> Placeholder

    @Environment(\.imagePipeline) private var pipeline
    @State private var loadedImage: PlatformImage?
    @State private var isLoading = false
    @State private var loadError: Error?

    init(
        url: URL?,
        processing: ImageProcessingOptions? = nil,
        @ViewBuilder content: @escaping (Image) -> Content,
        @ViewBuilder placeholder: @escaping () -> Placeholder
    ) {
        self.url = url
        self.processing = processing
        self.content = content
        self.placeholder = placeholder
    }

    var body: some View {
        Group {
            if let loadedImage {
                #if canImport(UIKit)
                content(Image(uiImage: loadedImage))
                #elseif canImport(AppKit)
                content(Image(nsImage: loadedImage))
                #endif
            } else {
                placeholder()
            }
        }
        .task(id: url) {
            await loadImage()
        }
    }

    private func loadImage() async {
        guard let url else { return }

        loadedImage = nil
        loadError = nil
        isLoading = true

        do {
            let image = try await pipeline.image(for: url, processing: processing)
            guard !Task.isCancelled else { return }
            loadedImage = image
        } catch {
            guard !Task.isCancelled else { return }
            loadError = error
        }

        isLoading = false
    }
}

// Convenience initializer without placeholder
extension CachedAsyncImage where Placeholder == ProgressView<EmptyView, EmptyView> {
    init(
        url: URL?,
        processing: ImageProcessingOptions? = nil,
        @ViewBuilder content: @escaping (Image) -> Content
    ) {
        self.init(url: url, processing: processing, content: content) {
            ProgressView()
        }
    }
}

ImagePrefetcher.swift

import Foundation

/// Prefetches images for upcoming list items.
///
/// Start prefetching when items are about to appear,
/// and cancel when they scroll off screen.
///
/// Usage:
/// ```swift
/// @State private var prefetcher = ImagePrefetcher()
///
/// ForEach(items) { item in
///     ItemRow(item: item)
///         .onAppear { prefetcher.startPrefetching(urls: [item.imageURL]) }
///         .onDisappear { prefetcher.stopPrefetching(urls: [item.imageURL]) }
/// }
/// ```
@Observable
final class ImagePrefetcher {
    private var activeTasks: [URL: Task<Void, Never>] = [:]
    private let pipeline: ImagePipeline

    init(pipeline: ImagePipeline = .shared) {
        self.pipeline = pipeline
    }

    /// Start prefetching images for the given URLs.
    func startPrefetching(urls: [URL]) {
        for url in urls {
            guard activeTasks[url] == nil else { continue }

            activeTasks[url] = Task {
                _ = try? await pipeline.image(for: url)
            }
        }
    }

    /// Cancel prefetching for the given URLs.
    func stopPrefetching(urls: [URL]) {
        for url in urls {
            activeTasks[url]?.cancel()
            activeTasks.removeValue(forKey: url)
        }
    }

    /// Cancel all active prefetch tasks.
    func stopAll() {
        activeTasks.values.forEach { $0.cancel() }
        activeTasks.removeAll()
    }
}

Source: SKILL.md on GitHub

1 alert16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    The 'generators' skill provides a robust set of templates and workflows for generating Swift code for iOS and macOS applications. Security analysis identified a surface for indirect prompt injection because the skill reads local project files to adapt its generation logic. It also utilizes dynamic execution by generating and running local Swift scripts to create application icons and uses shell commands to process images and audit project code. All external references are to well-known, established services.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: MEDIUM · 1 issue

  • Runlayer7mo

    59/179 files flagged

  • ZeroLeaks5mo

    Scan incomplete

Signed by skilld at 30e898f. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 months ago.

Steadyupdated 2 months ago
What it can do
Reads files Edits files Runs commands
last_verified
2026-07-16
review_by
2027-06-22
os_version
iOS 27 / macOS 27
All 7 allowed tools
ReadWriteEditGlobGrepBashAskUserQuestion

README badge

README badge for rshankras/claude-code-apple-skills/generators