@@ -75,14 +75,42 @@ import ImageIO
75
75
/// }
76
76
///
77
77
public struct AsyncImage < Content> : View where Content : View {
78
+ /// The URL of the image to load.
79
+ ///
80
+ /// When this property changes, the view automatically starts loading the new image
81
+ /// and updates the display when the load completes. Set to `nil` to display the
82
+ /// default placeholder.
78
83
var url : URL ?
79
84
85
+ /// The scale to apply to the loaded image.
86
+ ///
87
+ /// This value determines how the view interprets the image data. For example,
88
+ /// a value of `2` treats the image as having double the resolution of the display.
89
+ /// The default is `1`.
80
90
var scale : CGFloat
81
91
92
+ /// The transaction to use when updating the UI.
93
+ ///
94
+ /// This transaction controls how the view animates when the image loads or
95
+ /// when a loading error occurs. The default transaction uses default animation
96
+ /// timing and does not disable animations.
82
97
var transaction : Transaction
83
98
99
+ /// A closure that transforms the loading phase into a view.
100
+ ///
101
+ /// This closure receives the current phase of the loading operation and returns
102
+ /// a view to display for that phase. The view returned by this closure becomes
103
+ /// the body of the AsyncImage.
84
104
var content : ( AsyncImagePhase ) -> Content
85
105
106
+ /// The state that tracks the image loading process.
107
+ ///
108
+ /// This state property stores information about the current loading operation, including:
109
+ /// - The current phase (empty, success, or failure)
110
+ /// - The task that's performing the download operation
111
+ /// - The URL being downloaded
112
+ ///
113
+ /// When this state changes, the view automatically updates to reflect the new loading status.
86
114
@State private var loadingState = LoadingState ( phase: . empty)
87
115
88
116
/// Loads and displays an image from the specified URL.
@@ -223,15 +251,30 @@ public struct AsyncImage<Content>: View where Content : View {
223
251
224
252
// MARK: - AsyncImage.Inner
225
253
254
+ /// Internal view that renders the content based on the current loading phase.
255
+ ///
256
+ /// This view adapts the content closure's output to display the appropriate
257
+ /// view for the current phase of the loading operation.
226
258
private struct Inner : View {
259
+ /// The current phase of the loading operation.
227
260
var phase : AsyncImagePhase
261
+
262
+ /// A closure that transforms the loading phase into a view.
228
263
var content : ( AsyncImagePhase ) -> Content
229
264
230
265
var body : some View {
231
266
_UnaryViewAdaptor ( content ( phase) )
232
267
}
233
268
}
234
269
270
+ /// Handles changes to the URL.
271
+ ///
272
+ /// This method is called when the URL changes or when the view appears.
273
+ /// It starts a new loading operation if necessary.
274
+ ///
275
+ /// - Parameters:
276
+ /// - oldValue: The previous URL, if any.
277
+ /// - newValue: The new URL, if any.
235
278
private func didChangeURL( oldValue: URL ? , newValue: URL ? ) {
236
279
let value = ( newValue, loadingState. url)
237
280
guard value. 0 != value. 1 else {
@@ -254,13 +297,21 @@ public struct AsyncImage<Content>: View where Content : View {
254
297
updateTask ( task, url: newURL)
255
298
}
256
299
300
+ /// Handles the view disappearing.
301
+ ///
302
+ /// Cancels any in-progress loading operations to prevent memory leaks
303
+ /// and unnecessary work when the view is no longer visible.
257
304
private func onDisappear( ) {
258
305
if let task = loadingState. task {
259
306
task. cancel ( )
260
307
}
261
308
loadingState. task = nil
262
309
}
263
310
311
+ /// Resets the loading state.
312
+ ///
313
+ /// Called when the URL is set to nil or when a new URL is set.
314
+ /// Cancels any in-progress loading operations and resets the state.
264
315
private func resetLoadingState( ) {
265
316
withTransaction ( transaction) {
266
317
if let task = loadingState. task {
@@ -275,6 +326,14 @@ public struct AsyncImage<Content>: View where Content : View {
275
326
}
276
327
}
277
328
329
+ /// Updates the task and loading state.
330
+ ///
331
+ /// Called when a new loading operation starts.
332
+ /// Cancels any previous loading operation and updates the state.
333
+ ///
334
+ /// - Parameters:
335
+ /// - task: The new loading task.
336
+ /// - url: The URL being loaded.
278
337
private func updateTask( _ task: Task < Void , Never > , url: URL ) {
279
338
loadingState. task? . cancel ( )
280
339
// SwiftUI iOS 18.0 implementation:
@@ -346,35 +405,83 @@ public enum AsyncImagePhase: Sendable {
346
405
347
406
// MARK: - LoadingState
348
407
408
+ /// Tracks the state of an asynchronous image loading operation.
409
+ ///
410
+ /// This structure encapsulates the current state of an image load operation, including:
411
+ /// - The task responsible for downloading and processing the image
412
+ /// - The URL being downloaded
413
+ /// - The current phase of the loading operation
349
414
private struct LoadingState {
415
+ /// The task that handles the asynchronous image loading.
416
+ /// May be nil if no loading operation is in progress.
350
417
var task : Task < Void , Never > ?
418
+
419
+ /// The URL being downloaded, if any.
420
+ /// Used to prevent duplicate downloads of the same URL.
351
421
var url : URL ?
422
+
423
+ /// The current phase of the loading operation.
424
+ /// Indicates whether the operation is in progress, has succeeded, or has failed.
352
425
var phase : AsyncImagePhase
353
426
}
354
427
355
428
// MARK: - LoadingError
356
429
430
+ /// Error thrown when an image fails to load.
431
+ ///
432
+ /// This generic error is used when a more specific error isn't available
433
+ /// or when the image data couldn't be processed correctly.
357
434
private struct LoadingError : Error { }
358
435
359
436
// MARK: - TaskConfig
360
437
438
+ /// Configuration for an image loading task.
439
+ ///
440
+ /// Contains the necessary context for updating the UI when an image load
441
+ /// operation completes, including:
442
+ /// - A binding to the loading state
443
+ /// - The scale to apply to the loaded image
444
+ /// - The transaction to use when updating the UI
361
445
private struct TaskConfig {
446
+ /// Binding to the current loading state.
447
+ /// Used to update the UI when the loading state changes.
362
448
@Binding var loadingState : LoadingState
449
+
450
+ /// The scale to apply to the loaded image.
363
451
var scale : CGFloat
452
+
453
+ /// The transaction to use when updating the UI.
454
+ /// Controls animation when the image appears.
364
455
var transaction : Transaction
365
456
}
366
457
367
458
// MARK: - TaskResult
368
459
460
+ /// Result of an image loading operation.
461
+ ///
462
+ /// Contains the loaded image data or error information.
463
+ /// Used to pass the result of the loading operation back to the UI.
369
464
private struct TaskResult {
465
+ /// The local URL where the downloaded image is temporarily stored.
370
466
var localURL : URL ?
467
+
371
468
#if canImport(CoreGraphics)
469
+ /// The loaded Core Graphics image.
372
470
var cgImage : CGImage ?
373
471
#endif
472
+
473
+ /// The orientation of the loaded image, if available.
374
474
var orientation : Image . Orientation ?
475
+
476
+ /// The error that occurred during loading, if any.
375
477
var error : Error ?
376
478
}
377
479
480
+ /// Downloads an image from the specified URL.
481
+ ///
482
+ /// - Parameter url: The URL of the image to download.
483
+ /// - Returns: A `TaskResult` containing the downloaded image or an error.
484
+ /// - Throws: An error if the download fails or the image can't be processed.
378
485
private func downloadURL( _ url: URL ) async throws -> TaskResult {
379
486
#if canImport(CoreGraphics) && canImport(ImageIO)
380
487
let ( localURL, _) = try await URLSession . shared. download ( from: url, delegate: nil )
@@ -394,6 +501,10 @@ private func downloadURL(_ url: URL) async throws -> TaskResult {
394
501
395
502
#if canImport(ImageIO)
396
503
extension CGImageSource {
504
+ /// Extracts the orientation information from an image at the specified index.
505
+ ///
506
+ /// - Parameter index: The index of the image in the image source.
507
+ /// - Returns: The orientation of the image, or nil if not available.
397
508
fileprivate func orientation( at index: Int ) -> Image . Orientation ? {
398
509
guard let properties = CGImageSourceCopyPropertiesAtIndex ( self , index, nil ) ,
399
510
let orientationResult = CFDictionaryGetValue (
@@ -409,6 +520,11 @@ extension CGImageSource {
409
520
}
410
521
#endif
411
522
523
+ /// Updates the UI with the result of an image loading operation.
524
+ ///
525
+ /// - Parameters:
526
+ /// - result: The result of the loading operation.
527
+ /// - config: Configuration for updating the UI.
412
528
private func updateTaskResult( _ result: TaskResult , config: TaskConfig ) {
413
529
#if canImport(CoreGraphics)
414
530
if let cgImage = result. cgImage {
0 commit comments