Skip to content

Commit 128afb0

Browse files
committed
Update AsyncImage documentation
1 parent 632590f commit 128afb0

File tree

2 files changed

+126
-0
lines changed

2 files changed

+126
-0
lines changed

Sources/OpenSwiftUI/View/Image/AsyncImage.swift

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,14 +75,42 @@ import ImageIO
7575
/// }
7676
///
7777
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.
7883
var url: URL?
7984

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`.
8090
var scale: CGFloat
8191

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.
8297
var transaction: Transaction
8398

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.
84104
var content: (AsyncImagePhase) -> Content
85105

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.
86114
@State private var loadingState = LoadingState(phase: .empty)
87115

88116
/// Loads and displays an image from the specified URL.
@@ -223,15 +251,30 @@ public struct AsyncImage<Content>: View where Content : View {
223251

224252
// MARK: - AsyncImage.Inner
225253

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.
226258
private struct Inner: View {
259+
/// The current phase of the loading operation.
227260
var phase: AsyncImagePhase
261+
262+
/// A closure that transforms the loading phase into a view.
228263
var content: (AsyncImagePhase) -> Content
229264

230265
var body: some View {
231266
_UnaryViewAdaptor(content(phase))
232267
}
233268
}
234269

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.
235278
private func didChangeURL(oldValue: URL?, newValue: URL?) {
236279
let value = (newValue, loadingState.url)
237280
guard value.0 != value.1 else {
@@ -254,13 +297,21 @@ public struct AsyncImage<Content>: View where Content : View {
254297
updateTask(task, url: newURL)
255298
}
256299

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.
257304
private func onDisappear() {
258305
if let task = loadingState.task {
259306
task.cancel()
260307
}
261308
loadingState.task = nil
262309
}
263310

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.
264315
private func resetLoadingState() {
265316
withTransaction(transaction) {
266317
if let task = loadingState.task {
@@ -275,6 +326,14 @@ public struct AsyncImage<Content>: View where Content : View {
275326
}
276327
}
277328

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.
278337
private func updateTask(_ task: Task<Void, Never>, url: URL) {
279338
loadingState.task?.cancel()
280339
// SwiftUI iOS 18.0 implementation:
@@ -346,35 +405,83 @@ public enum AsyncImagePhase: Sendable {
346405

347406
// MARK: - LoadingState
348407

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
349414
private struct LoadingState {
415+
/// The task that handles the asynchronous image loading.
416+
/// May be nil if no loading operation is in progress.
350417
var task: Task<Void, Never>?
418+
419+
/// The URL being downloaded, if any.
420+
/// Used to prevent duplicate downloads of the same URL.
351421
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.
352425
var phase: AsyncImagePhase
353426
}
354427

355428
// MARK: - LoadingError
356429

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.
357434
private struct LoadingError: Error {}
358435

359436
// MARK: - TaskConfig
360437

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
361445
private struct TaskConfig {
446+
/// Binding to the current loading state.
447+
/// Used to update the UI when the loading state changes.
362448
@Binding var loadingState: LoadingState
449+
450+
/// The scale to apply to the loaded image.
363451
var scale: CGFloat
452+
453+
/// The transaction to use when updating the UI.
454+
/// Controls animation when the image appears.
364455
var transaction: Transaction
365456
}
366457

367458
// MARK: - TaskResult
368459

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.
369464
private struct TaskResult {
465+
/// The local URL where the downloaded image is temporarily stored.
370466
var localURL: URL?
467+
371468
#if canImport(CoreGraphics)
469+
/// The loaded Core Graphics image.
372470
var cgImage: CGImage?
373471
#endif
472+
473+
/// The orientation of the loaded image, if available.
374474
var orientation: Image.Orientation?
475+
476+
/// The error that occurred during loading, if any.
375477
var error: Error?
376478
}
377479

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.
378485
private func downloadURL(_ url: URL) async throws -> TaskResult {
379486
#if canImport(CoreGraphics) && canImport(ImageIO)
380487
let (localURL, _) = try await URLSession.shared.download(from: url, delegate: nil)
@@ -394,6 +501,10 @@ private func downloadURL(_ url: URL) async throws -> TaskResult {
394501

395502
#if canImport(ImageIO)
396503
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.
397508
fileprivate func orientation(at index: Int) -> Image.Orientation? {
398509
guard let properties = CGImageSourceCopyPropertiesAtIndex(self, index, nil),
399510
let orientationResult = CFDictionaryGetValue(
@@ -409,6 +520,11 @@ extension CGImageSource {
409520
}
410521
#endif
411522

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.
412528
private func updateTaskResult(_ result: TaskResult, config: TaskConfig) {
413529
#if canImport(CoreGraphics)
414530
if let cgImage = result.cgImage {

Sources/OpenSwiftUICore/View/Image/Image.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,16 @@ extension Image {
7373
/// horizontal flip from the orientation of its original pixel data.
7474
case rightMirrored = 5
7575

76+
/// Creates an image orientation from an EXIF orientation value.
77+
///
78+
/// This initializer converts the standard EXIF orientation values (1-8)
79+
/// to the corresponding `Image.Orientation` cases.
80+
///
81+
/// - Parameter exifValue: An integer representing the EXIF orientation.
82+
/// Valid values are 1 through 8, corresponding to the standard EXIF
83+
/// orientation values.
84+
/// - Returns: The corresponding orientation, or `nil` if the provided
85+
/// value is not a valid EXIF orientation value.
7686
@_spi(Private)
7787
public init?(exifValue: Int) {
7888
switch exifValue {

0 commit comments

Comments
 (0)