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

Building a document browser app for custom file formats

At a glance

Item Summary
Purpose Creates, opens, edits, previews, thumbnails, and restores a custom SceneKit particle document.
App architecture A document-browser root opens Document values into a compound editor; Quick Look preview/thumbnail targets reuse the same document and particle renderer.
Main patterns UIDocument model, compound view controller, shared renderer across targets, framework delegates, and extension adapters.
Project style Main Particles app plus ParticlesPreview and ParticlesThumbnails extension targets.

Project structure

Particles/
├── DocumentBrowserViewController.swift
├── DocumentViewController.swift
├── Document.swift
├── ParticleViewController.swift
├── EditorViewController.swift
└── AppDelegate.swift
ParticlesPreview/
└── PreviewViewController.swift
ParticlesThumbnails/
└── ThumbnailProvider.swift

Structure observations

  • Document and particle rendering form the reusable core consumed by three targets.
  • The main document screen composes separate renderer and inspector children.
  • Quick Look targets adapt the core instead of duplicating file decoding or visual representation.

Overall architecture

Reference code

Particles/DocumentViewController.swift:77 — the compound screen opens one coordinated document and injects it into both child feature owners.

func setDocument(_ document: Document, completion: @escaping () -> Void) {
    self.document = document
    loadViewIfNeeded()
    document.open { success in
        if success {
            self.particleViewController.document = self.document
            self.editorViewController.document = self.document
        }
        completion()
    }
}

Interpretation

The custom-format boundary is Document, not a view controller. Main app and extensions open that same type and reuse ParticleViewController; only the surrounding lifecycle adapter changes.

Ownership and state

Ownership evidence

Particles/DocumentViewController.swift:11 — document replacement is read-only to collaborators, while the compound controller privately owns its children.

class DocumentViewController: UIViewController {
    private(set) var document: Document?
    private let particleNavigationController = UINavigationController()
    private let particleViewController = ParticleViewController()
    private let editorViewController = EditorViewController()
}
Owner Object or state Relationship Mutation authority
Document browser Transition controller and presented document screen Creates/retains during presentation Browser delegate/restoration methods
Document screen Open document and renderer/editor children Creates/retains/injects setDocument, close action
Document Particle system and last error Decodes/retains UIDocument lifecycle
Preview extension Open document and embedded renderer Creates for preview lifetime Quick Look completion flow
Thumbnail provider Temporary document/renderer/snapshot Creates per request Provider callback

Class and protocol design

Type or protocol Responsibility Depends on
Document / UIDocument Coordinated load/save and secure archive of particle system NSKeyedArchiver, SceneKit
Browser delegates Create/import/pick/present documents and preserve bookmark state Document browser, transition delegate
DocumentViewController Compose renderer + inspector and own open/close lifecycle Child containment, Document
ParticleViewController Render/snapshot one document SCNView, SceneKit
EditorViewController Map slider settings to particle-system key paths Diffable table source, document picker
Preview / thumbnail entry types Adapt shared core to Quick Look contracts Document and renderer

The sample uses framework protocols at integration boundaries; it does not introduce an app-defined document-service protocol.

Access control

Symbol Access Verified effect Likely rationale
DocumentViewController.document private(set) Other code may inspect it, but only the compound screen may replace it. Preserve open/injection lifecycle.
Child controllers/navigation private let Outside code cannot bypass compound setup or inject different children. Keep containment and propagation coherent.
Editor settings/data source/derived particle system private Only inspector logic knows key paths, transformers, and cells. Hide KVC/editor implementation.
Particle scene view private Consumers use document/snapshot behavior, not rendering internals. Preserve renderer encapsulation.
Main types implicit internal Shared through target membership where configured, not a public API. App/extension source reuse.

Reference code

Particles/EditorViewController.swift:74 — the inspector exposes its document input while keeping table schema and derived model access private.

private lazy var dataSource = makeDataSource()
var document: Document? { didSet { readValues() } }
private var particleSystem: SCNParticleSystem? { document?.particleSystem }
private var settings = [
    EditorSetting(keyPath: "birthRate", label: "Birth Rate", min: 0.1, max: 100, transformer: nil)
]

Logic ownership and placement

Logic Owning type or file Placement rationale
File coordination and serialization Document Custom format invariant and UIDocument lifecycle.
Creation, picking, transition, restoration Document browser Root has provider URLs and presentation context.
Open/close and child injection Document screen Owns the visible editing session.
Particle rendering/snapshot Particle controller Shared visual capability across targets.
Settings-to-model mapping Editor controller Inspector-specific KVC transformation.
Quick Look completion/reply Preview/thumbnail adapters Framework extension contracts.

Design patterns

Pattern Source evidence Purpose or tradeoff
Document model Particles/Document.swift:12 Centralizes coordinated reads/writes and format decoding.
Compound controller Particles/DocumentViewController.swift:20 Gives renderer and inspector focused roles under one session owner.
Shared renderer ParticlesPreview/PreviewViewController.swift:29 Reuses the app representation in Quick Look.
Framework delegate Particles/DocumentBrowserViewController.swift:11 Adapts provider/document events and custom transitions.
Extension adapter ParticlesThumbnails/ThumbnailProvider.swift:11 Wraps shared core in a per-request Quick Look lifecycle.

Naming conventions

  • Document, DocumentBrowserViewController, and DocumentViewController distinguish model, root browser, and open-session UI.
  • ParticleViewController names the reusable renderer; EditorViewController names mutation UI.
  • Extension entry types use framework roles: PreviewViewController and ThumbnailProvider.
  • Commands use lifecycle verbs: presentDocument, setDocument, open, close, and provideThumbnail.

Architecture takeaways

  • Make the UIDocument subclass the custom-format authority shared by app and extensions.
  • Reuse a focused renderer across editing, preview, and thumbnail contexts.
  • Let the visible document screen own open/close and inject one document into child views.
  • Use private(set) when navigation/restoration needs to inspect the current document but must not replace it.

Source map

Source file Relevant symbols
Particles/Document.swift Custom format and UIDocument lifecycle
Particles/DocumentBrowserViewController.swift Browser, transition, and restoration
Particles/DocumentViewController.swift Compound editor and document ownership
Particles/ParticleViewController.swift Shared SceneKit renderer/snapshot
Particles/EditorViewController.swift Settings inspector
ParticlesPreview/PreviewViewController.swift Quick Look preview adapter
ParticlesThumbnails/ThumbnailProvider.swift Quick Look thumbnail adapter