Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Reviews, improves, and writes SwiftUI code following state management, view composition, performance, and iOS 26+ Liquid Glass best practices.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/image-optimization.md
1# SwiftUI Image Optimization Reference23## Table of Contents45- [AsyncImage Best Practices](#asyncimage-best-practices)6- [Image Decoding and Downsampling (Optional Optimization)](#image-decoding-and-downsampling-optional-optimization)7- [UIImage Loading and Memory](#uiimage-loading-and-memory)8- [SF Symbols](#sf-symbols)9- [Summary Checklist](#summary-checklist)1011## AsyncImage Best Practices1213### Basic AsyncImage with Phase Handling1415```swift16// Good - handles loading and error states17AsyncImage(url: imageURL) { phase in18switch phase {19case .empty:20ProgressView()21case .success(let image):22image23.resizable()24.aspectRatio(contentMode: .fit)25case .failure:26Image(systemName: "photo")27.foregroundStyle(.secondary)28@unknown default:29EmptyView()30}31}32.frame(width: 200, height: 200)33```3435For 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.3637## Image Decoding and Downsampling (Optional Optimization)3839**When you encounter `UIImage(data:)` usage, consider suggesting image downsampling as a potential performance improvement**, especially for large images in lists or grids.4041### Current Pattern That Could Be Optimized4243```swift44// Current pattern - decodes full image on main thread45// Unsafe - force unwrap can crash if imageData is invalid46Image(uiImage: UIImage(data: imageData)!)47.resizable()48.aspectRatio(contentMode: .fit)49.frame(width: 200, height: 200)50```5152### Suggested Optimization Pattern5354```swift55// Suggested optimization - decode and downsample off main thread56struct OptimizedImageView: View {57let imageData: Data58let targetSize: CGSize59@State private var processedImage: UIImage?6061var body: some View {62Group {63if let processedImage {64Image(uiImage: processedImage)65.resizable()66.aspectRatio(contentMode: .fit)67} else {68ProgressView()69}70}71.task {72processedImage = await decodeAndDownsample(imageData, targetSize: targetSize)73}74}7576private func decodeAndDownsample(_ data: Data, targetSize: CGSize) async -> UIImage? {77await Task.detached {78guard let source = CGImageSourceCreateWithData(data as CFData, nil) else {79return nil80}8182let options: [CFString: Any] = [83kCGImageSourceThumbnailMaxPixelSize: max(targetSize.width, targetSize.height),84kCGImageSourceCreateThumbnailFromImageAlways: true,85kCGImageSourceCreateThumbnailWithTransform: true86]8788guard let cgImage = CGImageSourceCreateThumbnailAtIndex(source, 0, options as CFDictionary) else {89return nil90}9192return UIImage(cgImage: cgImage)93}.value94}95}9697// Usage98OptimizedImageView(99imageData: imageData,100targetSize: CGSize(width: 200, height: 200)101)102```103104### Reusable Downsampling Actor105106For production use, wrap the logic in an `actor` with scale-aware sizing and cache-disabled source options:107108```swift109actor ImageProcessor {110func downsample(data: Data, targetSize: CGSize) -> UIImage? {111let scale = await UIScreen.main.scale112let maxPixel = max(targetSize.width, targetSize.height) * scale113let sourceOptions: [CFString: Any] = [kCGImageSourceShouldCache: false]114guard let source = CGImageSourceCreateWithData(data as CFData, sourceOptions as CFDictionary) else { return nil }115let downsampleOptions: [CFString: Any] = [116kCGImageSourceCreateThumbnailFromImageAlways: true,117kCGImageSourceThumbnailMaxPixelSize: maxPixel,118kCGImageSourceCreateThumbnailWithTransform: true,119kCGImageSourceShouldCacheImmediately: true120]121guard let cgImage = CGImageSourceCreateThumbnailAtIndex(source, 0, downsampleOptions as CFDictionary) else { return nil }122return UIImage(cgImage: cgImage)123}124}125```126127Key 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.128129### When to Suggest This Optimization130131Mention this optimization when you see `UIImage(data:)` usage, particularly in:132- Scrollable content (List, ScrollView with LazyVStack/LazyHStack)133- Grid layouts with many images134- Image galleries or carousels135- Any scenario where large images are displayed at smaller sizes136137**Don't automatically apply it**—present it as an optional improvement for performance-sensitive scenarios.138139## UIImage Loading and Memory140141### UIImage(named:) Caches in System Cache142143`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:144145```swift146// Caches in system cache -- memory builds up147let image = UIImage(named: "Wallpapers/image_001.jpg")148149// No system caching -- memory stays flat150guard let path = Bundle.main.path(forResource: "Wallpapers/image_001.jpg", ofType: nil) else { return nil }151let image = UIImage(contentsOfFile: path)152```153154### NSCache for Controlled Image Caching155156When image processing (resizing, filtering) is needed, use `NSCache` with a `countLimit` to bound memory instead of relying on system caching:157158```swift159struct ImageCache {160private let cache = NSCache<NSString, UIImage>()161162init(countLimit: Int = 50) {163cache.countLimit = countLimit164}165166subscript(key: String) -> UIImage? {167get { cache.object(forKey: key as NSString) }168nonmutating set {169if let newValue {170cache.setObject(newValue, forKey: key as NSString)171} else {172cache.removeObject(forKey: key as NSString)173}174}175}176}177```178179## SF Symbols180181```swift182Image(systemName: "star.fill")183.foregroundStyle(.yellow)184.symbolRenderingMode(.multicolor) // or .hierarchical, .palette, .monochrome185186// Animated symbols (iOS 17+)187Image(systemName: "antenna.radiowaves.left.and.right")188.symbolEffect(.variableColor)189```190191Variants are available via naming convention: `star.circle.fill`, `star.square.fill`, `folder.badge.plus`.192193## Summary Checklist194195- [ ] Use `AsyncImage` with proper phase handling196- [ ] Handle empty, success, and failure states197- [ ] Consider downsampling for `UIImage(data:)` in performance-sensitive scenarios198- [ ] Decode and downsample images off the main thread199- [ ] Use appropriate target sizes for downsampling200- [ ] Consider image caching for frequently accessed images201- [ ] Use SF Symbols with appropriate rendering modes202203**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.204