Supporting Continuity Camera in your tvOS app
At a glance
| Item | Summary |
|---|---|
| Purpose | Capture high-quality photos, video, and audio in your Apple TV app by connecting an iPhone or iPad as a continuity device. |
| App architecture | A Swift sample with the source-visible chain ContinuityCameraApp → ContentView → CaptureManager → CaptureDeviceNotificationObserver → AVFoundation / SwiftUI APIs. |
| Main patterns | Delegate or data-source callbacks, Binding-based state propagation, Publisher-backed observable state |
| Project style | 17 scanned source file(s) across Swift, organized around ranked entry, type, and file boundaries. |
| Execution model | Source-visible boundaries: DispatchQueue.main.async, Task, await suspension point, @MainActor; none alone proves a background thread. |
| State/event model | Source-visible mechanisms: ObservableObject, @Published, NotificationCenter. |
| Key frameworks/packages | AVFoundation, SwiftUI, Foundation, Photos, AVFAudio; these are source dependencies, not architecture labels. |
Project structure
Source bundle/
├── App/
│ └── ContinuityCameraApp.swift
├── Observers/
│ ├── VideoEffectsObserver.swift
│ ├── CaptureDeviceNotificationObserver.swift
│ └── PreferredCameraObserver.swift
├── Views/
│ ├── ContentView.swift
│ ├── TimerView.swift
│ ├── CameraPreview.swift
│ └── CaptureButton.swift
├── Camera/
│ ├── CaptureManager.swift
│ └── CaptureType.swift
└── Media export/
├── VideoCapturer.swift
└── AudioCapturer.swift
Structure observations
- Architecturally prominent files are ranked from entry points and role-named declarations; resource-only paths are omitted.
- Primary languages: Swift.
- The verified tree contains 5 project/configuration file(s) and 21 source declaration(s).
Overall architecture
flowchart LR
N1["ContinuityCameraApp"]
N2["ContentView"]
N3["CaptureManager"]
N4["CaptureDeviceNotificationObserver"]
N5["AVFoundation / SwiftUI APIs"]
N1 --> N2
N2 --> N3
N3 --> N4
N4 --> N5
Reference code
App/ContinuityCameraApp.swift:11 — architecture anchor
@main
struct ContinuityCaptureApp: App {
// ...
var captureManager = CaptureManager()
// ...
}Interpretation
The arrows summarize the source-visible entry, role-named types or folders, and framework direction; when nodes come from structural folders, the sequence is a high-level interpretation rather than proof that every adjacent node calls the next. Ownership is claimed only where the next section cites a stored property or assignment. The diagram is intentionally limited to the dominant path into AVKit.
Ownership and state
classDiagram
ContinuityCaptureApp *-- CaptureManager : captureManager
ContinuityCaptureApp *-- AudioCapturer : audioCapturer
ContinuityCaptureApp *-- PhotoCapturer : photoCapturer
ContinuityCaptureApp *-- VideoCapturer : videoCapturer
Ownership evidence
App/ContinuityCameraApp.swift:17 — stored dependency or nearest verified ownership anchor
@main
struct ContinuityCaptureApp: App {
// ...
var captureManager = CaptureManager()
// ...
}| Owner | Object or state | Relationship | Mutation authority |
|---|---|---|---|
ContinuityCaptureApp |
CaptureManager (captureManager) |
creates and retains | App/module collaborators |
ContinuityCaptureApp |
AudioCapturer (audioCapturer) |
creates and retains | App/module collaborators |
ContinuityCaptureApp |
PhotoCapturer (photoCapturer) |
creates and retains | App/module collaborators |
ContinuityCaptureApp |
VideoCapturer (videoCapturer) |
creates and retains | App/module collaborators |
Composition arrows indicate a source-visible construction expression or locally owned value state; aggregation means the owner stores or receives a dependency without proving exclusive lifetime ownership.
Concurrency, scheduling, and thread safety
Evidence limit: actor isolation, async/await, or Task creation does not by itself prove background-thread execution; Sendable conformance alone does not prove thread-safe mutation.
| Concern | Source mechanism | Verified placement or handoff | Evidence |
|---|---|---|---|
| Queue scheduling | DispatchQueue.main.async |
The source addresses the main dispatch queue. | Camera/CaptureManager+Observers.swift:58 |
| Task creation | Task |
The source creates an unstructured task; surrounding context determines inherited actor isolation. | Camera/CaptureManager.swift:110 |
| Suspension boundary | await suspension point |
The source declares or crosses an asynchronous boundary; it does not by itself establish background execution. | Camera/CaptureManager.swift:110 |
| Main isolation | @MainActor |
The cited annotation marks its attached declaration or closure as main-actor isolated. | Media export/PhotoCapturer.swift:17 |
@MainActor/MainActor.run, DispatchQueue.main, and RunLoop.main are reported as distinct isolation, queue, and event-loop mechanisms. A plain Task is kept separate from Task.detached; neither is labeled as a background thread.
Reference code
Camera/CaptureManager+Observers.swift:58 — representative execution boundary
DispatchQueue.main.async {
self.activeInput = nil
}State propagation, frameworks, and dependencies
Evidence limit: an import proves a source-level compilation dependency at the cited line; it does not prove runtime use, architectural adoption, or whether a Swift package is a direct application dependency.
| Category | Mechanism or module | Verified role | Evidence |
|---|---|---|---|
| State propagation | ObservableObject |
ObservableObject supplies an observation contract. | Camera/CaptureManager.swift:18 |
| State propagation | @Published |
A published property can emit owner-controlled changes. | Camera/CaptureManager.swift:59 |
| State propagation | NotificationCenter |
NotificationCenter distributes named process-local events. | Observers/CaptureDeviceNotificationObserver.swift:40 |
| Source import | AVFoundation |
The cited file imports this module; runtime use and architectural role are not inferred. | Camera/CaptureManager+ContinuityCamera.swift:8 |
| Source import | SwiftUI |
The cited file imports this module; runtime use and architectural role are not inferred. | App/ContinuityCameraApp.swift:8 |
| Source import | Foundation |
The cited file imports this module; runtime use and architectural role are not inferred. | Camera/CaptureType.swift:8 |
| Source import | Photos |
The cited file imports this module; runtime use and architectural role are not inferred. | Media export/PhotoCapturer.swift:9 |
| Source import | AVFAudio |
The cited file imports this module; runtime use and architectural role are not inferred. | Media export/AudioCapturer.swift:9 |
receive(on:) describes downstream delivery scheduling, while subscribe(on:) describes upstream subscription/request/cancel scheduling. An import Combine alone establishes neither behavior nor a Store, reducer, Redux, or other application architecture.
Class and protocol design
App/ContinuityCameraApp.swift:12 — representative type boundary
@main
struct ContinuityCaptureApp: App {
// ...
var captureManager = CaptureManager()
// ...
}| Type | Responsibility | Depends on or conforms to |
|---|---|---|
ContinuityCaptureApp |
Application entry and top-level composition | App |
VideoEffectsObserver |
Observes and relays feature changes | ObservableObject |
CaptureDevicePropertyObserver |
Observes and relays feature changes | ObservableObject |
ContentView |
User-interface presentation and input forwarding | View |
TimerView |
User-interface presentation and input forwarding | View |
CaptureManager |
Long-lived feature or framework coordination | NSObject, ObservableObject |
CaptureDeviceNotificationObserver |
Observes and relays feature changes | Concrete collaborators/imported frameworks |
PreferredCameraObserver |
Observes and relays feature changes | NSObject |
PreviewLayerView |
User-interface presentation and input forwarding | UIView |
VideoEffectIndicator |
Represents a feature value or composable behavior | View |
No local protocol conformance is claimed as protocol-oriented design; external framework conformances are listed only as dependencies.
Access control
| Symbol | Access | Verified effect | Likely rationale |
|---|---|---|---|
activateDefaultContinuityCameraDevice (Camera/CaptureManager+ContinuityCamera.swift:17) |
public |
The symbol is visible to importing modules. | Inference: make the declaration available across a module or target boundary. |
activeInput (Camera/CaptureManager.swift:33) |
internal |
No explicit modifier means the Swift declaration is internal to the module. | Inference: the language’s file or module boundary is sufficient for this sample collaboration. |
updateSystemPreferredCamera (Camera/CaptureManager.swift:90) |
private |
Use is restricted to the lexical declaration and same-file extensions allowed by Swift. | Inference: hide an implementation step that is not part of the collaboration surface. |
startIfNeeded (Camera/CaptureManager.swift:108) |
public |
The symbol is visible to importing modules. | Inference: make the declaration available across a module or target boundary. |
Reference code
Camera/CaptureManager+ContinuityCamera.swift:17 — representative boundary
public func activateDefaultContinuityCameraDevice() -> Bool {
// ...
for: .video,
// ...
}Swift declarations without a modifier are internal; explicit private, fileprivate, private(set), public, or open entries above are interpreted by language semantics. Objective-C/C samples instead rely on header and implementation boundaries, which are not equivalent to Swift lexical privacy.
Logic ownership and placement
| Logic | Owning type or file | Placement rationale |
|---|---|---|
| Application entry and top-level composition | ContinuityCaptureApp |
The source’s App suffix makes this role explicit. |
| Long-lived feature or framework coordination | CaptureManager |
The source’s Manager suffix makes this role explicit. |
| Observes and relays feature changes | CaptureDeviceNotificationObserver, CaptureDevicePropertyObserver, PreferredCameraObserver, VideoEffectsObserver |
The source’s Observer suffix makes this role explicit. |
| User-interface presentation and input forwarding | ContentView, PreviewLayerView, TimerView |
The source’s View suffix makes this role explicit. |
Design patterns
| Pattern | Source evidence | Purpose or tradeoff |
|---|---|---|
| Delegate or data-source callbacks | Media export/PhotoCapturer.swift:13 |
Callback protocols invert event delivery back into the sample’s owner. |
| Binding-based state propagation | Views/CaptureButton.swift:16 |
A binding exposes controlled read/write access while the upstream owner remains authoritative. |
| Publisher-backed observable state | Media export/AudioCapturer.swift:47 |
Published properties notify observers while mutation remains with the state object. |
Main application flow
sequenceDiagram
actor User
participant View as ContentView
participant Manager as CaptureManager
participant Capturer as Selected capturer
participant Output as AVFoundation output
User->>View: Connect device or press shutter
View->>Manager: select camera / start session
User->>View: Capture photo, video, or audio
View->>Capturer: capture command
Capturer->>Output: begin capture
Output-->>Capturer: delegate completion
Capturer-->>View: publish state or photo callback
Reference code
Views/ContentView.swift:210 — onShutterButton
private func onShutterButton() {
switch captureType {
case .photo:
photoCapturer.onPhotoCompletion = { image in
showScreenFlash = true
previewImage = image
}
photoCapturer.capture()
case .video:
toggleVideoCapture()
case .audio:
toggleAudioCapture()
}
}Naming conventions
- Types: App: ContinuityCaptureApp; Manager: CaptureManager; Observer: CaptureDeviceNotificationObserver, CaptureDevicePropertyObserver, PreferredCameraObserver, VideoEffectsObserver; View: ContentView, PreviewLayerView, TimerView.
- Protocols: no local protocol declaration in the scanned source.
- Methods:
observeCamera,setCurrentState,captureTypeLabel,configureSessionForCaptureType,buildEffectIndicators,onShutterButton,toggleVideoCapture,toggleAudioCapture. - Files:
Observers/VideoEffectsObserver.swift,Views/ContentView.swift,Views/TimerView.swift,Camera/CaptureManager.swift,Observers/CaptureDeviceNotificationObserver.swift,Observers/PreferredCameraObserver.swift.
Architecture takeaways
ContinuityCameraAppis the main source-visible entry or composition anchor for this sample.- Framework work reaches AVFoundation, SwiftUI, Photos, AVFAudio through a deliberately small high-level chain; the detailed API graph remains inside the cited implementation files.
- Stored-property evidence identifies lifecycle collaboration; it does not by itself prove exclusive object ownership.
- Access-control conclusions separate verified language visibility from the likely design rationale.
- The source does not justify labeling the design protocol-oriented.
Source map
| Source file | Relevant symbols |
|---|---|
App/ContinuityCameraApp.swift |
Cited implementation, ContinuityCaptureApp, SwiftUI |
Camera/CaptureManager+ContinuityCamera.swift |
Cited implementation, AVFoundation, Feature implementation |
Camera/CaptureManager.swift |
Cited implementation, Task, await suspension point, ObservableObject, @Published, CaptureManager |
Media export/PhotoCapturer.swift |
Cited implementation, @MainActor, Photos, PhotoCapturer |
Views/CaptureButton.swift |
Cited implementation, CaptureButton, ShutterButton_Previews |
Media export/AudioCapturer.swift |
Cited implementation, AVFAudio, AudioCapturer, AudioNode, AudioError |
Camera/CaptureManager+Observers.swift |
DispatchQueue.main.async, Feature implementation |
Observers/CaptureDeviceNotificationObserver.swift |
NotificationCenter, CaptureDeviceNotificationObserver |
Camera/CaptureType.swift |
Foundation, CaptureType, CaptureState |
Observers/VideoEffectsObserver.swift |
VideoEffectsObserver, CaptureDevicePropertyObserver |
Views/ContentView.swift |
ContentView, VideoEffectIndicator |
Views/TimerView.swift |
TimerView, TimerView_Previews |
Observers/PreferredCameraObserver.swift |
PreferredCameraObserver |
Media export/VideoCapturer.swift |
VideoCapturer |
Views/CameraPreview.swift |
CameraPreview, PreviewLayerView |
Views/AudioControls.swift |
AudioControls |