@@ -67,7 +67,7 @@ public struct CachedAsyncImage<Content>: View where Content: View {
67
67
68
68
@State private var phase : AsyncImagePhase
69
69
70
- private let url : URL ?
70
+ private let urlRequest : URLRequest ?
71
71
72
72
private let urlSession : URLSession
73
73
@@ -79,7 +79,7 @@ public struct CachedAsyncImage<Content>: View where Content: View {
79
79
80
80
public var body : some View {
81
81
content ( phase)
82
- . task ( id: url , load)
82
+ . task ( id: urlRequest , load)
83
83
}
84
84
85
85
/// Loads and displays an image from the specified URL.
@@ -105,7 +105,34 @@ public struct CachedAsyncImage<Content>: View where Content: View {
105
105
/// displays. For example, set a value of `2` for an image that you
106
106
/// would name with the `@2x` suffix if stored in a file on disk.
107
107
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
109
136
#if os(macOS)
110
137
phase. image ?? Image ( nsImage: . init( ) )
111
138
#else
@@ -146,7 +173,43 @@ public struct CachedAsyncImage<Content>: View where Content: View {
146
173
/// - placeholder: A closure that returns the view to show until the
147
174
/// load operation completes successfully.
148
175
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
150
213
if let image = phase. image {
151
214
content ( image)
152
215
} else {
@@ -191,17 +254,57 @@ public struct CachedAsyncImage<Content>: View where Content: View {
191
254
/// - content: A closure that takes the load phase as an input, and
192
255
/// returns the view to display for the specified phase.
193
256
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 ) {
194
297
let configuration = URLSessionConfiguration . default
195
298
configuration. urlCache = urlCache
196
299
configuration. requestCachePolicy = . returnCacheDataElseLoad
197
- self . url = url
300
+ self . urlRequest = urlRequest
198
301
self . urlSession = URLSession ( configuration: configuration)
199
302
self . scale = scale
200
303
self . transaction = transaction
201
304
self . content = content
202
305
203
306
204
- if let image = cachedImage ( from: url , cache: urlCache) {
307
+ if let image = cachedImage ( from: urlRequest , cache: urlCache) {
205
308
self . _phase = State ( wrappedValue: . success( image) )
206
309
} else {
207
310
self . _phase = State ( wrappedValue: . empty)
@@ -211,7 +314,7 @@ public struct CachedAsyncImage<Content>: View where Content: View {
211
314
@Sendable
212
315
private func load( ) async {
213
316
do {
214
- if let image = try await remoteImage ( from: url , session: urlSession) {
317
+ if let image = try await remoteImage ( from: urlRequest , session: urlSession) {
215
318
withAnimation ( transaction. animation) {
216
319
phase = . success( image)
217
320
}
@@ -238,17 +341,15 @@ private extension AsyncImage {
238
341
// MARK: - Helpers
239
342
240
343
@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 }
244
346
let ( data, _) = try await session. data ( for: request)
245
347
return image ( from: data)
246
348
}
247
349
248
350
@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 }
252
353
guard let cachedResponse = cache. cachedResponse ( for: request) else { return nil }
253
354
return image ( from: cachedResponse. data)
254
355
}
0 commit comments