URLImage
URLImage
is a SwiftUI view that displays an image downloaded from provided URL. URLImage
manages downloading remote image and caching it locally, both in memory and on disk, for you.
Using URLImage
is dead simple:
URLImage(url: url) { image in
image
.resizable()
.aspectRatio(contentMode: .fit)
}
Take a look at some examples in the demo app.
Table of Contents
Features
- SwiftUI image view for remote images;
- Local image cache;
- Fully customizable including placeholder, progress indication, error, and the image view;
- Control over various download aspects for better performance.
Installation
URLImage
can be installed using Swift Package Manager or CocoaPods.
Using Swift Package Manager
Use the package URL to search for the URLImage
package: https://github.com/dmytro-anokhin/url-image.
For how-to integrate package dependencies refer to Adding Package Dependencies to Your App documentation.
Using Cocoa Pods
Add the URLImage
pod to your Podfile:
pod 'URLImage'
Refer to https://cocoapods.org for information on setup Cocoa Pods for your project.
Usage
Basics
URLImage
expects URL of the image and the content view:
import URLImage // Import the package module
URLImage(url: url,
content: { image in
image
.resizable()
.aspectRatio(contentMode: .fit)
})
States
URLImage
transitions between 4 states:
- Empty state, when download has not started yet, or there is nothing to display;
- In Progress state to indicate download process;
- Failure state in case there is an error;
- Content to display the image.
Each of this states has a separate view that can be provided using closures. You can also customize certain settings, like cache policy and expiry interval, using URLImageOptions
.
struct MyView: View {
let url: URL
let id: UUID
init(url: URL, id: UUID) {
self.url = url
self.id = id
formatter = NumberFormatter()
formatter.numberStyle = .percent
}
private let formatter: NumberFormatter // Used to format download progress as percentage. Note: this is only for example, better use shared formatter to avoid creating it for every view.
var body: some View {
URLImage(url: url,
options: URLImageOptions(
identifier: id.uuidString, // Custom identifier
expireAfter: 300.0, // Expire after 5 minutes
cachePolicy: .returnCacheElseLoad(cacheDelay: nil, downloadDelay: 0.25) // Return cached image or download after delay
),
empty: {
Text("Nothing here") // This view is displayed before download starts
},
inProgress: { progress -> Text in // Display progress
if let progress = progress {
return Text(formatter.string(from: progress as NSNumber) ?? "Loading...")
}
else {
return Text("Loading...")
}
},
failure: { error, retry in // Display error and retry button
VStack {
Text(error.localizedDescription)
Button("Retry", action: retry)
}
},
content: { image in // Content view
image
.resizable()
.aspectRatio(contentMode: .fit)
})
}
}
Image Information
You can use init(url: URL, content: @escaping (_ image: Image, _ info: ImageInfo) -> Content)
initializer if you need information about an image, like size, or access the underlying CGImage
object.
Cache
URLImage
uses two caches:
- In memory cache for quick access;
- Local disk cache.
Downloaded images stored in user caches folder. This allows OS to take care of cleaning up files. It is also a good idea to perform manual cleanup time to time.
You can remove expired images by calling cleanup
as a part of your startup routine. This will also remove image files from the previous URLImage
version if you used it.
URLImageService.shared.cleanup()
Downloaded images expire after some time. Expired images removed in cleanup
routine. Expiry interval can be set using expiryInterval
property of URLImageOptions
.
You can also remove individual or all cached images using URLImageService
.
Using URLCache
Alternatively you can use URLCache
. You can configure the package globally and also per view.
URLImageService.shared.defaultOptions.cachePolicy = .useProtocol
// Download using `URLSessionDataTask`
URLImageService.shared.defaultOptions.loadOptions.formUnion(.inMemory)
// Set your `NSURLRequest.CachePolicy`
URLImageService.shared.defaultOptions.urlRequestConfiguration.cachePolicy = .returnCacheDataElseLoad
Using URLCache
adds support for Cache-Control header. As a trade-off you lose some control, like in-memory caching, download delays, expiry intervals (you get it with Cache-Control header). It also only works for in-memory downloads (using URLSessionDataTask
).
Options
URLImage
allows controlling various aspects of download and cache using URLImageOptions
structure. You can set default options using URLImageService.shared.defaultOptions
property. Here are the main settings:
identifier: String?
By default an image is identified by its URL. Alternatively, you can provide a string identifier to override this.
expiryInterval: TimeInterval?
Time interval after which the cached image expires and can be deleted. Images are deleted as part of cleanup routine described in Cache paragraph.
maxPixelSize: CGSize?
Maximum size of a decoded image in pixels. If this property is not specified, the width and height of a decoded is not limited and may be as big as the image itself.
cachePolicy: CachePolicy
The cache policy controls how the image loaded from cache.
Cache Policy
Cache policy, URLImageOptions.CachePolicy
type, allows to specify how URLImage
utilizes it's cache, similar to NSURLRequest.CachePolicy
. This type also allows to specify delays for accessing disk cache and starting download.
returnCacheElseLoad
Return an image from cache or download it.
returnCacheDontLoad
Return an image from cache, do not download it.
ignoreCache
Ignore cached image and download remote one.
Some options are can be set globally using URLImageService.shared.defaultOptions
property. Those are set by default:
expireAfter
to 24 hours;cachePolicy
toreturnCacheElseLoad
without delays;maxPixelSize
to 1000 by 1000 pixels (300 by 300 pixels for watchOS).
Fetching an Image
You may want to download an image without a view. This is possible using the RemoteImagePublisher
object. The RemoteImagePublisher
can cache images for future use by the URLImage
view.
Download an image as CGImage
and ignore any errors:
cancellable = URLImageService.shared.remoteImagePublisher(url)
.tryMap { $0.cgImage }
.catch { _ in
Just(nil)
}
.sink { image in
// image is CGImage or nil
}
Download multiple images as an array of [CGImage?]
:
let publishers = urls.map { URLImageService.shared.remoteImagePublisher($0) }
cancellable = Publishers.MergeMany(publishers)
.tryMap { $0.cgImage }
.catch { _ in
Just(nil)
}
.collect()
.sink { images in
// images is [CGImage?]
}
When downloading image using the RemoteImagePublisher
object all options apply as they do for the URLImage
object. Be default downloaded image will be cached on the disk. This can speedup displaying images on later stage of your app. Also, this is currently the only supported way to display images in iOS 14 widgets.
Download an Image in iOS 14 Widget
Unfortunately views in WidgetKit can not run asynchronous operations: https://developer.apple.com/forums/thread/652581. The recommended way is to load your content, including images, in TimelineProvider
.
You can still use URLImage
for this. The idea is that you load image in TimelineProvider
using the RemoteImagePublisher
object, and display it in the URLImage
view.
Reporting a Bug
Use GitHub issues to report a bug. Include this information when possible:
Summary and/or background; OS and what device you are using; Version of URLImage library; What you expected would happen; What actually happens; Additional information: Screenshots or video demonstrating a bug; Crash log; Sample code, try isolating it so it compiles without dependancies; Test data: if you use public resource provide URLs of the images.
Please make sure there is a reproducible scenario. Ideally provide a sample code. And if you submit a sample code - make sure it compiles ;)
Requesting a Feature
Use GitHub issues to request a feature.
Contributing
Contributions are welcome. Please create a GitHub issue before submitting a pull request to plan and discuss implementation.