SwiftUI Image Optimization Reference
SwiftUI Image Optimization Reference
Section titled “SwiftUI Image Optimization Reference”Table of Contents
Section titled “Table of Contents”- AsyncImage Best Practices
- Image Decoding and Downsampling (Optional Optimization)
- UIImage Loading and Memory
- SF Symbols
- Summary Checklist
AsyncImage Best Practices
Section titled “AsyncImage Best Practices”Basic AsyncImage with Phase Handling
Section titled “Basic AsyncImage with Phase Handling”// Good - handles loading and error statesAsyncImage(url: imageURL) { phase in switch phase { case .empty: ProgressView() case .success(let image): image .resizable() .aspectRatio(contentMode: .fit) case .failure: Image(systemName: "photo") .foregroundStyle(.secondary) @unknown default: EmptyView() }}.frame(width: 200, height: 200)For custom placeholders, replace ProgressView() in the .empty case with your placeholder view. Add .transition(.opacity) to the success case and .animation(.easeInOut, value: imageURL) to the container for fade-in transitions.
Image Decoding and Downsampling (Optional Optimization)
Section titled “Image Decoding and Downsampling (Optional Optimization)”When you encounter UIImage(data:) usage, consider suggesting image downsampling as a potential performance improvement, especially for large images in lists or grids.
Current Pattern That Could Be Optimized
Section titled “Current Pattern That Could Be Optimized”// Current pattern - decodes full image on main thread// Unsafe - force unwrap can crash if imageData is invalidImage(uiImage: UIImage(data: imageData)!) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 200, height: 200)Suggested Optimization Pattern
Section titled “Suggested Optimization Pattern”// Suggested optimization - decode and downsample off main threadstruct OptimizedImageView: View { let imageData: Data let targetSize: CGSize @State private var processedImage: UIImage?
var body: some View { Group { if let processedImage { Image(uiImage: processedImage) .resizable() .aspectRatio(contentMode: .fit) } else { ProgressView() } } .task { processedImage = await decodeAndDownsample(imageData, targetSize: targetSize) } }
private func decodeAndDownsample(_ data: Data, targetSize: CGSize) async -> UIImage? { await Task.detached { guard let source = CGImageSourceCreateWithData(data as CFData, nil) else { return nil }
let options: [CFString: Any] = [ kCGImageSourceThumbnailMaxPixelSize: max(targetSize.width, targetSize.height), kCGImageSourceCreateThumbnailFromImageAlways: true, kCGImageSourceCreateThumbnailWithTransform: true ]
guard let cgImage = CGImageSourceCreateThumbnailAtIndex(source, 0, options as CFDictionary) else { return nil }
return UIImage(cgImage: cgImage) }.value }}
// UsageOptimizedImageView( imageData: imageData, targetSize: CGSize(width: 200, height: 200))Reusable Downsampling Actor
Section titled “Reusable Downsampling Actor”For production use, wrap the logic in an actor with scale-aware sizing and cache-disabled source options:
actor ImageProcessor { func downsample(data: Data, targetSize: CGSize) -> UIImage? { let scale = await UIScreen.main.scale let maxPixel = max(targetSize.width, targetSize.height) * scale let sourceOptions: [CFString: Any] = [kCGImageSourceShouldCache: false] guard let source = CGImageSourceCreateWithData(data as CFData, sourceOptions as CFDictionary) else { return nil } let downsampleOptions: [CFString: Any] = [ kCGImageSourceCreateThumbnailFromImageAlways: true, kCGImageSourceThumbnailMaxPixelSize: maxPixel, kCGImageSourceCreateThumbnailWithTransform: true, kCGImageSourceShouldCacheImmediately: true ] guard let cgImage = CGImageSourceCreateThumbnailAtIndex(source, 0, downsampleOptions as CFDictionary) else { return nil } return UIImage(cgImage: cgImage) }}Key details: kCGImageSourceShouldCache: false on the source prevents the full-resolution image from being cached in memory. Multiplying targetSize by UIScreen.main.scale ensures the thumbnail is sharp on Retina displays. kCGImageSourceShouldCacheImmediately: true on the thumbnail forces decoding at creation time rather than at first render.
When to Suggest This Optimization
Section titled “When to Suggest This Optimization”Mention this optimization when you see UIImage(data:) usage, particularly in:
- Scrollable content (List, ScrollView with LazyVStack/LazyHStack)
- Grid layouts with many images
- Image galleries or carousels
- Any scenario where large images are displayed at smaller sizes
Don’t automatically apply it—present it as an optional improvement for performance-sensitive scenarios.
UIImage Loading and Memory
Section titled “UIImage Loading and Memory”UIImage(named:) Caches in System Cache
Section titled “UIImage(named:) Caches in System Cache”UIImage(named:) adds images to the system cache, which can cause memory spikes when loading many images (e.g., in a slider or gallery). For single-use or frequently-rotated images, use UIImage(contentsOfFile:) to bypass the cache:
// Caches in system cache -- memory builds uplet image = UIImage(named: "Wallpapers/image_001.jpg")
// No system caching -- memory stays flatguard let path = Bundle.main.path(forResource: "Wallpapers/image_001.jpg", ofType: nil) else { return nil }let image = UIImage(contentsOfFile: path)NSCache for Controlled Image Caching
Section titled “NSCache for Controlled Image Caching”When image processing (resizing, filtering) is needed, use NSCache with a countLimit to bound memory instead of relying on system caching:
struct ImageCache { private let cache = NSCache<NSString, UIImage>()
init(countLimit: Int = 50) { cache.countLimit = countLimit }
subscript(key: String) -> UIImage? { get { cache.object(forKey: key as NSString) } nonmutating set { if let newValue { cache.setObject(newValue, forKey: key as NSString) } else { cache.removeObject(forKey: key as NSString) } } }}SF Symbols
Section titled “SF Symbols”Image(systemName: "star.fill") .foregroundStyle(.yellow) .symbolRenderingMode(.multicolor) // or .hierarchical, .palette, .monochrome
// Animated symbols (iOS 17+)Image(systemName: "antenna.radiowaves.left.and.right") .symbolEffect(.variableColor)Variants are available via naming convention: star.circle.fill, star.square.fill, folder.badge.plus.
Summary Checklist
Section titled “Summary Checklist”- Use
AsyncImagewith proper phase handling - Handle empty, success, and failure states
- Consider downsampling for
UIImage(data:)in performance-sensitive scenarios - Decode and downsample images off the main thread
- Use appropriate target sizes for downsampling
- Consider image caching for frequently accessed images
- Use SF Symbols with appropriate rendering modes
Performance Note: Image downsampling is an optional optimization. Only suggest it when you encounter UIImage(data:) usage in performance-sensitive contexts like scrollable lists or grids.