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