Skip to content

Commit 248bedf

Browse files
Add URLRequest initializers
1 parent db2ab0c commit 248bedf

File tree

1 file changed

+114
-13
lines changed

1 file changed

+114
-13
lines changed

Sources/CachedAsyncImage/CachedAsyncImage.swift

Lines changed: 114 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ public struct CachedAsyncImage<Content>: View where Content: View {
6767

6868
@State private var phase: AsyncImagePhase
6969

70-
private let url: URL?
70+
private let urlRequest: URLRequest?
7171

7272
private let urlSession: URLSession
7373

@@ -79,7 +79,7 @@ public struct CachedAsyncImage<Content>: View where Content: View {
7979

8080
public var body: some View {
8181
content(phase)
82-
.task(id: url, load)
82+
.task(id: urlRequest, load)
8383
}
8484

8585
/// Loads and displays an image from the specified URL.
@@ -105,7 +105,34 @@ public struct CachedAsyncImage<Content>: View where Content: View {
105105
/// displays. For example, set a value of `2` for an image that you
106106
/// would name with the `@2x` suffix if stored in a file on disk.
107107
public init(url: URL?, urlCache: URLCache = .shared, scale: CGFloat = 1) where Content == Image {
108-
self.init(url: url, urlCache: urlCache, scale: scale) { phase in
108+
let urlRequest = url == nil ? nil : URLRequest(url: url!)
109+
self.init(urlRequest: urlRequest, urlCache: urlCache, scale: scale)
110+
}
111+
112+
/// Loads and displays an image from the specified URL.
113+
///
114+
/// Until the image loads, SwiftUI displays a default placeholder. When
115+
/// the load operation completes successfully, SwiftUI updates the
116+
/// view to show the loaded image. If the operation fails, SwiftUI
117+
/// continues to display the placeholder. The following example loads
118+
/// and displays an icon from an example server:
119+
///
120+
/// CachedAsyncImage(url: URL(string: "https://example.com/icon.png"))
121+
///
122+
/// If you want to customize the placeholder or apply image-specific
123+
/// modifiers --- like ``Image/resizable(capInsets:resizingMode:)`` ---
124+
/// to the loaded image, use the ``init(url:scale:content:placeholder:)``
125+
/// initializer instead.
126+
///
127+
/// - Parameters:
128+
/// - urlRequest: The URL request of the image to display.
129+
/// - urlCache: The URL cache for providing cached responses to requests within the session.
130+
/// - scale: The scale to use for the image. The default is `1`. Set a
131+
/// different value when loading images designed for higher resolution
132+
/// displays. For example, set a value of `2` for an image that you
133+
/// would name with the `@2x` suffix if stored in a file on disk.
134+
public init(urlRequest: URLRequest?, urlCache: URLCache = .shared, scale: CGFloat = 1) where Content == Image {
135+
self.init(urlRequest: urlRequest, urlCache: urlCache, scale: scale) { phase in
109136
#if os(macOS)
110137
phase.image ?? Image(nsImage: .init())
111138
#else
@@ -146,7 +173,43 @@ public struct CachedAsyncImage<Content>: View where Content: View {
146173
/// - placeholder: A closure that returns the view to show until the
147174
/// load operation completes successfully.
148175
public init<I, P>(url: URL?, urlCache: URLCache = .shared, scale: CGFloat = 1, @ViewBuilder content: @escaping (Image) -> I, @ViewBuilder placeholder: @escaping () -> P) where Content == _ConditionalContent<I, P>, I : View, P : View {
149-
self.init(url: url, urlCache: urlCache, scale: scale) { phase in
176+
let urlRequest = url == nil ? nil : URLRequest(url: url!)
177+
self.init(urlRequest: urlRequest, urlCache: urlCache, scale: scale, content: content, placeholder: placeholder)
178+
}
179+
180+
/// Loads and displays a modifiable image from the specified URL using
181+
/// a custom placeholder until the image loads.
182+
///
183+
/// Until the image loads, SwiftUI displays the placeholder view that
184+
/// you specify. When the load operation completes successfully, SwiftUI
185+
/// updates the view to show content that you specify, which you
186+
/// create using the loaded image. For example, you can show a green
187+
/// placeholder, followed by a tiled version of the loaded image:
188+
///
189+
/// CachedAsyncImage(url: URL(string: "https://example.com/icon.png")) { image in
190+
/// image.resizable(resizingMode: .tile)
191+
/// } placeholder: {
192+
/// Color.green
193+
/// }
194+
///
195+
/// If the load operation fails, SwiftUI continues to display the
196+
/// placeholder. To be able to display a different view on a load error,
197+
/// use the ``init(url:scale:transaction:content:)`` initializer instead.
198+
///
199+
/// - Parameters:
200+
/// - urlRequest: The URL request of the image to display.
201+
/// - urlCache: The URL cache for providing cached responses to requests within the session.
202+
/// - scale: The scale to use for the image. The default is `1`. Set a
203+
/// different value when loading images designed for higher resolution
204+
/// displays. For example, set a value of `2` for an image that you
205+
/// would name with the `@2x` suffix if stored in a file on disk.
206+
/// - content: A closure that takes the loaded image as an input, and
207+
/// returns the view to show. You can return the image directly, or
208+
/// modify it as needed before returning it.
209+
/// - placeholder: A closure that returns the view to show until the
210+
/// load operation completes successfully.
211+
public init<I, P>(urlRequest: URLRequest?, urlCache: URLCache = .shared, scale: CGFloat = 1, @ViewBuilder content: @escaping (Image) -> I, @ViewBuilder placeholder: @escaping () -> P) where Content == _ConditionalContent<I, P>, I : View, P : View {
212+
self.init(urlRequest: urlRequest, urlCache: urlCache, scale: scale) { phase in
150213
if let image = phase.image {
151214
content(image)
152215
} else {
@@ -191,17 +254,57 @@ public struct CachedAsyncImage<Content>: View where Content: View {
191254
/// - content: A closure that takes the load phase as an input, and
192255
/// returns the view to display for the specified phase.
193256
public init(url: URL?, urlCache: URLCache = .shared, scale: CGFloat = 1, transaction: Transaction = Transaction(), @ViewBuilder content: @escaping (AsyncImagePhase) -> Content) {
257+
let urlRequest = url == nil ? nil : URLRequest(url: url!)
258+
self.init(urlRequest: urlRequest, urlCache: urlCache, scale: scale, transaction: transaction, content: content)
259+
}
260+
261+
/// Loads and displays a modifiable image from the specified URL in phases.
262+
///
263+
/// If you set the asynchronous image's URL to `nil`, or after you set the
264+
/// URL to a value but before the load operation completes, the phase is
265+
/// ``AsyncImagePhase/empty``. After the operation completes, the phase
266+
/// becomes either ``AsyncImagePhase/failure(_:)`` or
267+
/// ``AsyncImagePhase/success(_:)``. In the first case, the phase's
268+
/// ``AsyncImagePhase/error`` value indicates the reason for failure.
269+
/// In the second case, the phase's ``AsyncImagePhase/image`` property
270+
/// contains the loaded image. Use the phase to drive the output of the
271+
/// `content` closure, which defines the view's appearance:
272+
///
273+
/// CachedAsyncImage(url: URL(string: "https://example.com/icon.png")) { phase in
274+
/// if let image = phase.image {
275+
/// image // Displays the loaded image.
276+
/// } else if phase.error != nil {
277+
/// Color.red // Indicates an error.
278+
/// } else {
279+
/// Color.blue // Acts as a placeholder.
280+
/// }
281+
/// }
282+
///
283+
/// To add transitions when you change the URL, apply an identifier to the
284+
/// ``CachedAsyncImage``.
285+
///
286+
/// - Parameters:
287+
/// - urlRequest: The URL request of the image to display.
288+
/// - urlCache: The URL cache for providing cached responses to requests within the session.
289+
/// - scale: The scale to use for the image. The default is `1`. Set a
290+
/// different value when loading images designed for higher resolution
291+
/// displays. For example, set a value of `2` for an image that you
292+
/// would name with the `@2x` suffix if stored in a file on disk.
293+
/// - transaction: The transaction to use when the phase changes.
294+
/// - content: A closure that takes the load phase as an input, and
295+
/// returns the view to display for the specified phase.
296+
public init(urlRequest: URLRequest?, urlCache: URLCache = .shared, scale: CGFloat = 1, transaction: Transaction = Transaction(), @ViewBuilder content: @escaping (AsyncImagePhase) -> Content) {
194297
let configuration = URLSessionConfiguration.default
195298
configuration.urlCache = urlCache
196299
configuration.requestCachePolicy = .returnCacheDataElseLoad
197-
self.url = url
300+
self.urlRequest = urlRequest
198301
self.urlSession = URLSession(configuration: configuration)
199302
self.scale = scale
200303
self.transaction = transaction
201304
self.content = content
202305

203306

204-
if let image = cachedImage(from: url, cache: urlCache) {
307+
if let image = cachedImage(from: urlRequest, cache: urlCache) {
205308
self._phase = State(wrappedValue: .success(image))
206309
} else {
207310
self._phase = State(wrappedValue: .empty)
@@ -211,7 +314,7 @@ public struct CachedAsyncImage<Content>: View where Content: View {
211314
@Sendable
212315
private func load() async {
213316
do {
214-
if let image = try await remoteImage(from: url, session: urlSession) {
317+
if let image = try await remoteImage(from: urlRequest, session: urlSession) {
215318
withAnimation(transaction.animation) {
216319
phase = .success(image)
217320
}
@@ -238,17 +341,15 @@ private extension AsyncImage {
238341
// MARK: - Helpers
239342

240343
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
241-
private func remoteImage(from url: URL?, session: URLSession) async throws -> Image? {
242-
guard let url = url else { return nil }
243-
let request = URLRequest(url: url)
344+
private func remoteImage(from request: URLRequest?, session: URLSession) async throws -> Image? {
345+
guard let request = request else { return nil }
244346
let (data, _) = try await session.data(for: request)
245347
return image(from: data)
246348
}
247349

248350
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
249-
private func cachedImage(from url: URL?, cache: URLCache) -> Image? {
250-
guard let url = url else { return nil }
251-
let request = URLRequest(url: url)
351+
private func cachedImage(from request: URLRequest?, cache: URLCache) -> Image? {
352+
guard let request = request else { return nil }
252353
guard let cachedResponse = cache.cachedResponse(for: request) else { return nil }
253354
return image(from: cachedResponse.data)
254355
}

0 commit comments

Comments
 (0)