Sample CodeiOS 15.6, iPadOS 15.6, Mac Catalyst 15.6, Xcode 16.0Reviewed 2026-07-21View on Apple Developer

Capturing depth using the LiDAR camera

At a glance

Item Summary
Purpose Stream or photograph synchronized LiDAR depth and color data, then present several Metal visualizations.
App architecture SwiftUI owns CameraManager; the manager owns a capture controller and shared captured-data object; delegate callbacks update views backed by reusable Metal coordinators.
Main patterns View-model adapter, weak delegate, synchronized outputs, protocol/generic rendering layer, shared Metal environment.
Project style Model/ contains capture/data coordination; Views/Metal/ contains protocol-based render wrappers, coordinators, and shaders.

Project structure

LiDARDepth/
├── LiDARDepthApp.swift
├── ContentView.swift
├── Model/
│   ├── CameraManager.swift
│   ├── CameraController.swift
│   └── CVPixelBuffer+Extension.swift
└── Views/
    ├── DepthOverlay.swift
    ├── ZoomOnTap.swift
    ├── View+Extensions.swift
    └── Metal/
        ├── MetalContentView.swift
        ├── MetalEnvironment.swift
        ├── MetalPointCloud.swift
        ├── MetalTexture*.swift
        └── Shaders/shaders.metal

Structure observations

  • The model layer separates UI-facing state (CameraManager) from AVFoundation session ownership (CameraController).
  • Metal view wrappers and renderer coordinators are grouped by implementation technology.
  • All visualizations share CameraCapturedData by reference and a singleton Metal device/queue/library.

Overall architecture

Reference code

LiDARDepth/ContentView.swift:12 and LiDARDepth/Model/CameraManager.swift:31 — UI-to-capture composition

struct ContentView: View {
    @StateObject private var manager = CameraManager()
}

init() {
    capturedData = CameraCapturedData()
    controller = CameraController()
    controller.startStream()
    controller.delegate = self
}

Interpretation

SwiftUI retains one observable manager. The manager adapts delegate deliveries to published UI state and mutates a stable captured-data reference consumed by renderers. The controller remains the sole capture-graph implementation, while a protocol/generic coordinator layer shares Metal view setup.

Ownership and state

Ownership evidence

LiDARDepth/Model/CameraManager.swift:27 — manager composition

let controller: CameraController

init() {
    capturedData = CameraCapturedData()
    controller = CameraController()
    controller.isFilteringEnabled = true
    controller.startStream()
    controller.delegate = self
}
Owner Object or state Relationship Mutation authority
ContentView CameraManager Creates/retains with @StateObject Manager publishes state to SwiftUI.
CameraManager Controller, captured data, Combine cancellables Creates and retains all Manager updates published state and captured textures.
CameraController Session, outputs, synchronizer, texture cache Creates during initialization Controller and delegate callbacks.
CameraController CaptureDataReceiver Weak reference supplied by manager Receiver handles delivered value; controller does not own manager.
MetalEnvironment Device, queue, library Process-wide singleton owns immutable Metal services Private initializer; shared instance only.

Class and protocol design

Reference code

LiDARDepth/Model/CameraController.swift:12 and LiDARDepth/Views/Metal/MetalContentView.swift:18 — capture and rendering capabilities

protocol CaptureDataReceiver: AnyObject {
    func onNewData(capturedData: CameraCapturedData)
    func onNewPhotoData(capturedData: CameraCapturedData)
}

protocol MetalRepresentable: UIViewRepresentable {
    var rotationAngle: Double { get set }
}
Type Responsibility Depends on
CameraManager Bridge capture events and controls to observable UI state CameraController, Combine, CameraCapturedData
CameraController Configure LiDAR formats/outputs and deliver synchronized/photo data AVFoundation, Core Video, delegate protocol
CaptureDataReceiver Decouple controller delivery from UI model implementation CameraCapturedData
MetalRepresentable Share SwiftUI-to-MTKView setup contract SwiftUI, MetalKit
MTKCoordinator<Parent> Base MTKViewDelegate and reusable pipeline setup MetalRepresentable, MetalEnvironment
MetalEnvironment Share immutable Metal device/queue/library Metal

Access control

Symbol Access Verified effect Likely rationale
ContentView.manager private Only the root view accesses its manager instance Makes the screen the clear lifecycle owner.
Controller outputs, synchronizer, queue, cache, and setup methods private Capture graph cannot be rewired by manager/views Preserves controller session invariants.
CameraController.captureSession private(set) Manager may read/use the session but cannot replace it Exposes observation/preview access without giving away construction authority.
CameraController.delegate weak, implicit internal Avoids controller-manager retain cycle Standard callback ownership.
MetalEnvironment.init private Only shared can construct the environment Enforces one shared Metal resource set.
App types/protocols implicit internal Visible only inside the app module No public framework API is intended.

Reference code

LiDARDepth/Model/CameraController.swift:24 — protected capture graph

private let videoQueue = DispatchQueue(
    label: "com.example.apple-samplecode.VideoQueue",
    qos: .userInteractive
)
private(set) var captureSession: AVCaptureSession!
private var photoOutput: AVCapturePhotoOutput!
private var depthDataOutput: AVCaptureDepthDataOutput!
weak var delegate: CaptureDataReceiver?

No public, open, or fileprivate declaration appears in the Swift source.

Logic ownership and placement

Logic Owning type or file Placement rationale
SwiftUI controls and visualization grid ContentView Presentation and local depth-range state.
Observable capture mode/filter/orientation state CameraManager UI-facing adapter and delegate receiver.
LiDAR device/format/output setup CameraController private methods Concrete owner of capture session and hardware requirements.
Synchronizing/video-depth packaging Controller synchronizer delegate extension Runs on delivery queue with access to outputs/cache.
Photo-depth packaging Controller photo delegate extension Handles the distinct one-shot capture lifecycle.
Reusable Metal view setup MetalContentView.swift Shared protocol/default implementation/generic coordinator.
Renderer-specific draw code Individual MetalTexture.../MetalPointCloud files One visualization algorithm per type/file.

Design patterns

Pattern Source evidence Purpose or tradeoff
View-model adapter LiDARDepth/Model/CameraManager.swift:14 Converts capture callbacks into published SwiftUI state and commands.
Weak delegate LiDARDepth/Model/CameraController.swift:12 and line 37 Decouples delivery and avoids ownership cycle.
Data-output synchronizer LiDARDepth/Model/CameraController.swift:128 Delivers matching video/depth data as one unit.
Protocol with constrained default implementation LiDARDepth/Views/Metal/MetalContentView.swift:18 and line 23 Reuses MTKView creation/update behavior across visualization wrappers.
Generic coordinator base class LiDARDepth/Views/Metal/MetalContentView.swift:46 Shares Metal view setup and lets subclasses override pipeline/draw stages.
Singleton environment LiDARDepth/Views/Metal/MetalEnvironment.swift:13 Shares one Metal device, command queue, and library; introduces global lifetime.

Main application flow

Reference code

LiDARDepth/Model/CameraController.swift:159 — synchronized delivery

func dataOutputSynchronizer(_ synchronizer: AVCaptureDataOutputSynchronizer,
                            didOutput collection: AVCaptureSynchronizedDataCollection) {
    let syncedDepthData = collection.synchronizedData(for: depthDataOutput)
    let syncedVideoData = collection.synchronizedData(for: videoDataOutput)
    // Package both streams as CameraCapturedData.
    delegate?.onNewData(capturedData: data)
}

Naming conventions

  • Types: layer roles are explicit (CameraManager, CameraController, CameraCapturedData, MetalEnvironment).
  • Protocols: receiver/capability names (CaptureDataReceiver, MetalRepresentable).
  • Methods: event callbacks use onNew...; commands use start, resume, capture, and setup verbs.
  • Views/coordinators: paired Metal...View and MTK...Coordinator names identify SwiftUI wrapper versus renderer delegate.
  • Files: Model/ versus Views/Metal/ organization mirrors capture/state and rendering responsibilities.

Architecture takeaways

  • Separate observable UI coordination from the object that owns AVFoundation hardware/session details.
  • A weak receiver protocol keeps capture delivery reusable without making the controller own the UI layer.
  • Store a stable reference object when many renderer views consume textures that update frame by frame.
  • Protocol default implementations and a generic coordinator remove repeated SwiftUI/MetalKit bridge setup.
  • private(set) and a private singleton initializer clearly encode who may construct or replace critical resources.

Source map

Source file Relevant symbols
LiDARDepth/LiDARDepthApp.swift App entry
LiDARDepth/ContentView.swift Manager ownership and visualization composition
LiDARDepth/Model/CameraManager.swift UI adapter, delegate receiver, captured data
LiDARDepth/Model/CameraController.swift Capture graph, synchronizer/photo delegates
LiDARDepth/Views/Metal/MetalContentView.swift MetalRepresentable, generic coordinator
LiDARDepth/Views/Metal/MetalEnvironment.swift Shared Metal resources
LiDARDepth/Views/Metal/MetalPointCloud.swift Example renderer specialization