Skip to content

Latest commit

History

53 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

SmartImages

Swift VersionsPlatformsNikSativa CI

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.

Modular Architecture

SmartImages — Core: fetching, caching, decoding, processing
SmartImagesUIKit — UIKit: placeholder, animation, ImageView extensions
SmartImagesSwiftUI — SwiftUI: SmartImageView (phase-builder + content-scale)
You needImport
Only protocols and core engineSmartImages
UIKit image views with placeholders and animationsSmartImagesUIKit
SwiftUI async image viewSmartImagesSwiftUI

UIKit and SwiftUI targets depend on SmartImages — it is pulled in automatically.

Installation

Swift Package Manager

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)
])

Quick Start

UIKit

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")!))

SwiftUI

SmartImageView is a single generic view with two API modes.

Convenience: placeholder + loader + content scale

import SmartImagesSwiftUI
SmartImageView(url: imageURL,
imageFetcher: fetcher,
contentScale:.scaledToFill){ProgressView()} placeholder:{Color.gray
}

Phase-builder (full control, à la AsyncImage)

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")}}

Inject a default fetcher via environment

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 /* … */ }

Core (closure-based)

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 cancel

Async/Await

letimage=tryawait fetcher.download(url: imageURL, priority:.high)

Prefetching

fetcher.prefetch(of:ImageRequest(url: upcomingURL, priority:.prefetch)){ _ in}

Core Types

ImageFetcher

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
)

ImageRequest

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
)

ImageCacheConfiguration

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
)

ImageProcessor

Protocol for image transformations applied during download.

structRoundCornersProcessor:ImageProcessor{letradius:CGFloatfunc process(_ image:SmartImage)->SmartImage{
// transform logic
}}

Built-in processors

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)])

FetchPriority

Download priority levels.

PriorityUse case
.veryHighVisible on screen right now
.highAbout to appear
.normalDefault
.lowBackground work
.prefetchSpeculative preload

ImagePlaceholder (UIKit)

.image(UIImage(systemName:"photo")!).imageNamed("placeholder", bundle:.main).clear
.custom{ imageView in imageView.backgroundColor =.gray }.none

ImageAnimation (UIKit)

.crossDissolve // iOS/tvOS
.custom{ imageView, image in...} // all platforms

SwiftUI

SmartImageView<Content>

A 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
}

SmartImageContentScale

Used by the convenience initializers. Equivalent to UIView.ContentMode for the loaded image.

CaseBehaviour
.scaledToFitAspect-fit inside the container (default)
.scaledToFillAspect-fill, may crop
.stretchResizable, ignores aspect
.originalNatural size, no .resizable()
.scaleDown.scaledToFit clamped to the image's native size — never upscales

Environment modifiers

ModifierPurpose
.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))

Previews and tests: PreviewImageFetcher

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)

Custom Networking

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
}}

Migration to 4.0

SwiftUI

Pre-4.04.0
AsyncImageView(url:imageDownloader:...)SmartImageView(url:imageFetcher:...)
SmartImagePhaseViewmerged into SmartImageView
SmartImageStyle / SmartImageStyleConfigurationremoved; use SmartImageContent<P, L> or supply your own phase-builder
SmartImagePlaceholderrenamed to SmartImageResourceView
case .loaded(SwiftUI.Image)case .loaded(SwiftUI.Image, nativeSize: CGSize)

Core

Pre-4.04.0
ImageLoadConfigurationImageRequest (now also carries headers)

Supported Platforms

PlatformMinimum
iOS16.0
macOS14.0
Mac Catalyst16.0
tvOS16.0
watchOS9.0
visionOS1.0
  • Swift 5.10+ — full compatibility
  • Swift 6.0+ — strict concurrency with Sendable

Testing

swift test

About

Simple and lightweight library for loading images in a fast way

Topics

Resources

Stars

4 stars

Watchers

1 watching

Forks

Releases

Used by

Contributors

Languages