SmartImages is a modular Swift library for intelligent image loading with prioritization, caching, and processing. The framework is split into three independent targets so you only import what you need.
SmartImages — Core: fetching, caching, decoding, processing
SmartImagesUIKit — UIKit: placeholder, animation, ImageView extensions
SmartImagesSwiftUI — SwiftUI: SmartImageView (phase-builder + content-scale)
| You need | Import |
|---|---|
| Only protocols and core engine | SmartImages |
| UIKit image views with placeholders and animations | SmartImagesUIKit |
| SwiftUI async image view | SmartImagesSwiftUI |
UIKit and SwiftUI targets depend on SmartImages — it is pulled in automatically.
dependencies:[.package(url:"https://github.com/NikSativa/SmartImages.git", from:"4.0.0")]Add only the targets you need:
.target(name:"YourApp", dependencies:["SmartImagesUIKit", // UIKit app
// or
"SmartImagesSwiftUI", // SwiftUI app
// or
"SmartImages", // core only (library / module)
])import SmartImagesUIKit
letfetcher=ImageFetcher(
network:YourNetworkImpl(),
cache:ImageCacheConfiguration(folderName:"MyImages"),
concurrentLimit:6)
// one-liner with placeholder and animation
fetcher.download(url: imageURL,
for: imageView,
animated:.crossDissolve,
placeholder:.image(UIImage(systemName:"photo")!))SmartImageView is a single generic view with two API modes.
import SmartImagesSwiftUI
SmartImageView(url: imageURL,
imageFetcher: fetcher,
contentScale:.scaledToFill){ProgressView()} placeholder:{Color.gray
}SmartImageView(url: imageURL, imageFetcher: fetcher){ phase inswitch phase {case.idle,.loading:ProgressView()case.loaded(let image, _):
image.resizable().scaledToFit()case.failed,.noURL:Image(systemName:"photo")}}Skip imageFetcher: on every call site by injecting once at the root:
@mainstructMyApp:App{letfetcher=ImageFetcher(network:YourNetworkImpl())varbody:someScene{WindowGroup{ContentView().smartImageFetcher(fetcher)}}}
// anywhere downstream:
SmartImageView(url: url){ phase in /* … */ }import SmartImages
lettoken= fetcher.download(of:ImageRequest(url: imageURL)){ result inswitch result {case.success(let image):break // use image
case.failure(let error):break // handle error
}}
// token.cancel() to cancelletimage=tryawait fetcher.download(url: imageURL, priority:.high)fetcher.prefetch(of:ImageRequest(url: upcomingURL, priority:.prefetch)){ _ in}The main class that coordinates networking, caching, decoding, and queuing.
letfetcher=ImageFetcher(
network:YourNetworkImpl(), // required
cache:ImageCacheConfiguration(...), // optional
decoders:[CustomDecoder()], // optional
decodingQueue:.async(.background), // optional
concurrentLimit:6 // optional
)Configuration for a single download.
letrequest=ImageRequest(
url: imageURL,
cachePolicy:.returnCacheDataElseLoad,
timeoutInterval:30,
headers:["Authorization":"Bearer \(token)"],
processors:[ImageProcessors.Resize(size:CGSize(width:200, height:200),
contentMode:.aspectFill),ImageProcessors.Crop(rect:CGRect(x:0, y:0, width:200, height:200))],
priority:.high
)Cache configuration. URLCache handles size-based LRU eviction; the optional ttl adds age-based eviction on read.
// defaults: 40 MB memory, 400 MB disk
letcache=ImageCacheConfiguration(folderName:"MyAppImages")
// custom limits + TTL
letcache=ImageCacheConfiguration(
folderName:"LargeCache",
memoryCapacity:100*1024*1024,
diskCapacity:1000*1024*1024,
ttl:60*60*24*7 // 7 days
)Protocol for image transformations applied during download.
structRoundCornersProcessor:ImageProcessor{letradius:CGFloatfunc process(_ image:SmartImage)->SmartImage{
// transform logic
}}ImageProcessors.Resize(size:CGSize(width:200, height:200),
contentMode:.aspectFit) // .stretch / .aspectFit / .aspectFill
ImageProcessors.Crop(rect:CGRect(x:0, y:0, width:200, height:200))
// chain multiple processors
ImageProcessors.Composition(processors:[ImageProcessors.Resize(size:...),RoundCornersProcessor(radius:10)])Download priority levels.
| Priority | Use case |
|---|---|
.veryHigh | Visible on screen right now |
.high | About to appear |
.normal | Default |
.low | Background work |
.prefetch | Speculative preload |
.image(UIImage(systemName:"photo")!).imageNamed("placeholder", bundle:.main).clear
.custom{ imageView in imageView.backgroundColor =.gray }.none.crossDissolve // iOS/tvOS
.custom{ imageView, image in...} // all platformsA single generic view backed by a (SmartImagePhase) -> Content builder. Convenience initializers cover the common cases by constructing a SmartImageContent renderer for you.
Phase enum:
publicenumSmartImagePhase{case idle
case loading
case loaded(SwiftUI.Image, nativeSize:CGSize)case failed
case noURL
}Used by the convenience initializers. Equivalent to UIView.ContentMode for the loaded image.
| Case | Behaviour |
|---|---|
.scaledToFit | Aspect-fit inside the container (default) |
.scaledToFill | Aspect-fill, may crop |
.stretch | Resizable, ignores aspect |
.original | Natural size, no .resizable() |
.scaleDown | .scaledToFit clamped to the image's native size — never upscales |
| Modifier | Purpose |
|---|---|
.smartImageFetcher(_:) | Default ImageFetching for descendant SmartImageViews |
.smartImageAnimation(_:) | Animation applied when phase transitions to .loaded |
.smartImageTransition(_:) | AnyTransition applied to the loaded branch |
.smartImageTransition(_:animation:) | Convenience: both at once |
ContentView().smartImageFetcher(fetcher).smartImageTransition(.opacity, animation:.easeInOut(duration:0.24))A drop-in ImageFetching implementation that returns a deterministic result without performing real I/O.
#Preview {SmartImageView(url: previewURL,
imageFetcher:PreviewImageFetcher(image:.init(named:"sample")!),
contentScale:.scaledToFit){ProgressView()} placeholder:{Color.gray
}}
// per-URL responses
letfetcher=PreviewImageFetcher(images:[
url1: image1,
url2: image2
])
// always fail
letfetcher=PreviewImageFetcher(error:URLError(.notConnectedToInternet))
// simulate latency
letfetcher=PreviewImageFetcher(image: image, delay:1.5)Implement ImageNetworkProvider and ImageNetworkTask:
structMyNetwork:ImageNetworkProvider{func request(with url:URL,
cachePolicy:URLRequest.CachePolicy?,
timeoutInterval:TimeInterval?,
completion:@escapingResultCompletion)->ImageNetworkTask{
// your networking logic
}
// Optional: opt in to ImageRequest.headers by overriding the default
func request(with url:URL,
cachePolicy:URLRequest.CachePolicy?,
timeoutInterval:TimeInterval?,
headers:[String:String]?,
completion:@escapingResultCompletion)->ImageNetworkTask{
// build a URLRequest, attach `headers`, dispatch it
}}If you don't override the headers: method, the default implementation forwards to the legacy 3-arg method and silently drops headers — useful for keeping older provider implementations source-compatible.
Integration with SmartNetwork
import SmartImages
import SmartNetwork
structImageDownloaderNetworkAdaptor:ImageNetworkProvider{letmanager:RequestManagerfunc request(with url:URL,
cachePolicy:URLRequest.CachePolicy?,
timeoutInterval:TimeInterval?,
completion:@escapingResultCompletion)->ImageNetworkTask{letaddress:SmartURL=.init(url)letparameters:Parameters=.init(requestPolicy: cachePolicy ??.useProtocolCachePolicy,
timeoutInterval: timeoutInterval ??30)lettask= manager.data
.request(url: address, with: parameters).complete(in:.absent, completion: completion)returnImageDownloaderTaskAdaptor(task: task)}}privatestructImageDownloaderTaskAdaptor:ImageNetworkTask,@uncheckedSendable{lettask:SmartTaskingfunc start(){
task.start()}func cancel(){
// no-op. auto-cancel on deinit
}}| Pre-4.0 | 4.0 |
|---|---|
AsyncImageView(url:imageDownloader:...) | SmartImageView(url:imageFetcher:...) |
SmartImagePhaseView | merged into SmartImageView |
SmartImageStyle / SmartImageStyleConfiguration | removed; use SmartImageContent<P, L> or supply your own phase-builder |
SmartImagePlaceholder | renamed to SmartImageResourceView |
case .loaded(SwiftUI.Image) | case .loaded(SwiftUI.Image, nativeSize: CGSize) |
| Pre-4.0 | 4.0 |
|---|---|
ImageLoadConfiguration | ImageRequest (now also carries headers) |
| Platform | Minimum |
|---|---|
| iOS | 16.0 |
| macOS | 14.0 |
| Mac Catalyst | 16.0 |
| tvOS | 16.0 |
| watchOS | 9.0 |
| visionOS | 1.0 |
- Swift 5.10+ — full compatibility
- Swift 6.0+ — strict concurrency with
Sendable
swift test