Sample CodeiOS, iPadOS, Mac Catalyst, macOSReviewed 2026-07-21View on Apple Developer

Supporting HDR images in your app

At a glance

Item Summary
Purpose Import, edit, render, preview, and export HDR still images while preserving extended dynamic range.
App architecture MainView owns navigation and import/export state, creates an actor-isolated EditedAsset, and delegates Core Image rendering to Renderer; RenderView adapts the resulting pixel buffer to UIKit or AppKit.
Main patterns Observable editing model, Renderer service, Platform view adapter
Project style Ten Swift files; a cross-platform SwiftUI app with Core Image, Photos, document export, and thin UIKit/AppKit representables.

Project structure

Source bundle/
├── HDRDemo23/
│   ├── HDRDemo23App.swift
│   ├── RenderView.swift
│   ├── AdjustView.swift
│   ├── ImageView.swift
│   ├── MainView.swift
│   ├── Renderer.swift
│   ├── Adjustment.swift
│   ├── Asset.swift
│   ├── EditedAsset.swift
│   └── FilmStrip.swift
├── Configuration/
│   └── SampleCode.xcconfig
└── HDRDemo23.xcodeproj/
    └── .xcodesamplecode.plist

Structure observations

  • The app entry creates MainView; the image pipeline is not owned by an application delegate.
  • EditedAsset centralizes observable adjustment state and triggers rendering without overwriting the source Asset.
  • Conditional compilation is limited to the pixel-buffer platform adapter; editing and renderer types are shared.

Overall architecture

Reference code

HDRDemo23/EditedAsset.swift:92 — adjustment-to-render pipeline

    func applyAdjustments(_ adjustments: [Adjustment], showOriginal: Bool) async {
        var image = inputImage
        if !showOriginal {
            for adjustment in adjustments {
                image = adjustment.apply(to: image)
            }
        }
        if let image,
           let pixelBuffer = await renderer.render(image, destinationColorspace: inputImage?.colorSpace) {
            setPixelBuffer(pixelBuffer)
        }
    }

Interpretation

The view coordinates a user session, but the edit model owns the editable image state and render trigger. The renderer owns Core Image execution and HDR buffer construction. Presentation observes only the resulting buffer, keeping file/Photos workflows outside the drawing adapter.

Ownership and state

Ownership evidence

HDRDemo23/MainView.swift:16 — feature-root state and renderer

    var renderer = Renderer()

    @State var selectedItems: [PhotosPickerItem] = []
    @State var assets: [Asset] = []
    @State var selectedAsset: Asset?
    @State var editedAsset: EditedAsset?

    @State var isEditing: Bool = false
Owner Object or state Relationship Mutation authority
MainView Renderer, imported assets, selection, edit session, presentation flags Creates the renderer and stores SwiftUI state It starts/cancels/saves edits and replaces selected assets.
EditedAsset Original asset, adjustments, input image, latest pixel buffer Retains injected collaborators and mutable edit state Its setters schedule renders and its export methods materialize edits.
Renderer Dispatch queue and CIContext Creates and retains rendering resources Only the renderer allocates/renders/exports image output.
PixelBufferView Platform layer contents Receives a buffer value from RenderView The representable updates only the native layer.

Composition denotes source-visible construction and retained value state. Aggregation denotes a stored or injected collaborator whose exclusive lifetime the source does not prove.

Class and protocol design

HDRDemo23/EditedAsset.swift:17 — observable main-actor edit model

@MainActor
@Observable
class EditedAsset: FileDocument {
    nonisolated
    static var readableContentTypes: [UTType] { [.image] }

    let renderer: Renderer
    let asset: Asset
    var pixelBuffer: CVPixelBuffer? = nil
}
Type Responsibility Depends on or conforms to
MainView Coordinates import, selection, edit, Photos save, and file export View plus PhotosUI and SwiftUI document APIs
EditedAsset Observable edit-session state and export representation FileDocument, @Observable, @MainActor
Renderer Renders filtered CIImage values into HDR buffers or encoded data Core Image, Core Video, ImageIO
RenderView / PixelBufferView Bridges a rendered buffer into SwiftUI and a native layer View; UIViewRepresentable or NSViewRepresentable
Asset / Adjustment Represent original input and serializable filter choices Value-oriented domain types

FileDocument and the representable protocols are framework integration contracts. The sample defines no local service protocol; Renderer is injected concretely into each EditedAsset.

Access control

Symbol Access Verified effect Design reason
Asset.loadThumbnail static private Callable only inside Asset’s lexical scope Thumbnail decoding is an implementation helper, not an app capability.
EditedAsset and Renderer implicit internal Visible to the app module only The sample is an application target and exports no reusable package API.
@MainActor on EditedAsset actor isolation, not access control Serializes observable edit state on the main actor UI-facing mutation is protected from unsynchronized concurrency.
nonisolated document requirements isolation exception, not public access Allows framework-required static/document witnesses outside actor isolation Conformance requirements can be reached without treating them as an exported API.

Reference code

HDRDemo23/EditedAsset.swift:21 — concurrency boundary on document requirements

    nonisolated
    static var readableContentTypes: [UTType] { [.image] }

    nonisolated
    static var writableContentTypes: [UTType] { [.heic, .png] }

    let renderer: Renderer
    let asset: Asset

nonisolated changes actor isolation, not Swift visibility. Declarations without an access modifier remain internal; the only concrete private boundary highlighted here is the asset’s thumbnail loader.

Logic ownership and placement

Logic Owning type or file Why it lives there
Import, selection, edit-mode transitions, save destination MainView These decisions depend on view state and Photos/file presentation APIs.
Adjustment observation and render scheduling EditedAsset The edit model owns the values whose mutation requires rerendering.
HDR buffer allocation, attachments, Core Image render/export Renderer Rendering resources and color-space details stay out of views.
Native extended-range layer presentation PixelBufferView Platform-specific layer setup is isolated behind a SwiftUI adapter.

Design patterns

Pattern Source evidence Purpose or tradeoff
Observable editing model HDRDemo23/EditedAsset.swift:17 Binds adjustments and the latest render to SwiftUI while preserving the original asset.
Renderer service HDRDemo23/Renderer.swift:14 Centralizes expensive Core Image resources and output-format details.
Platform view adapter HDRDemo23/RenderView.swift:16 Keeps UIKit/AppKit layer code behind a shared SwiftUI surface.

Naming conventions

  • Domain nouns (Asset, EditedAsset, Adjustment, Renderer) describe data ownership rather than screen position.
  • View types use a View suffix; platform bridge PixelBufferView names the value it presents.
  • Commands use workflow verbs (toggleEdit, saveEdit, applyAdjustments, render, export).
  • Concurrency annotations are placed on the state-owning type or method, not encoded in names.

Architecture takeaways

  • Keep import/export coordination in the feature view and image mutation in an observable edit-session model.
  • Give color-space, pixel-buffer, and encoding details to one renderer service.
  • Treat @MainActor and nonisolated as concurrency design, not visibility declarations.

Source map

Source file Architectural role
HDRDemo23/HDRDemo23App.swift SwiftUI application entry
HDRDemo23/MainView.swift Import/export and edit-session coordination
HDRDemo23/EditedAsset.swift Observable editing state and document/Photos export
HDRDemo23/Renderer.swift Core Image rendering and HDR output
HDRDemo23/RenderView.swift SwiftUI-to-native pixel-buffer presentation
HDRDemo23/Asset.swift / HDRDemo23/Adjustment.swift Source image and filter domain models