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

Supporting desktop-class features in your iPad app

At a glance

Item Summary
Purpose Build a document-based Markdown editor with customizable toolbars, menus, pointer interactions, keyboard commands, and multi-column presentation.
App architecture DocumentBrowserViewController opens a MarkdownDocument and presents a SplitViewController, which owns editor and outline children; local delegates synchronize parsed outline state and document edits.
Main patterns Document-controller composition, Local delegate coordination, Background parse and preview pipeline
Project style Seventeen Swift files grouped into document management, Markdown parsing, and specialized view/controller layers.

Project structure

Source bundle/
└── DesktopClassMarkdownEditor/
    └── DesktopClassMarkdownEditor/
        ├── AppDelegate.swift
        ├── Views/
        │   ├── OutlineViewController.swift
        │   ├── EditorViewController.swift
        │   ├── SplitView.swift
        │   ├── SplitViewController.swift
        │   ├── DocumentBrowserViewController.swift
        │   ├── EditorView.swift
        │   └── PreviewView.swift
        ├── SceneDelegate.swift
        └── Markdown/
            ├── MarkdownTag.swift
            ├── Outline.swift
            └── Parser.swift

Structure observations

  • Document lifecycle is isolated in MarkdownDocument and DocumentBrowserViewController.
  • SplitViewController is the composition and coordination root for editor/outline presentation.
  • Parser/model files are independent of UIKit presentation; the editor schedules parsing and preview updates.

Overall architecture

Reference code

DesktopClassMarkdownEditor/DesktopClassMarkdownEditor/Views/DocumentBrowserViewController.swift:82 — document presentation composition

    func presentDocument(_ documentURL: URL) {
        let document = MarkdownDocument(fileURL: documentURL)
        let splitVC = SplitViewController(document: document)

        splitVC.transitioningDelegate = self
        splitVC.modalPresentationStyle = .fullScreen
        splitVC.editorViewController.documentBrowser = self
        present(splitVC, animated: true)
    }

Interpretation

The browser creates the document session and composition root. The split controller connects child controllers through narrow local delegate protocols; the editor owns parsing/preview mechanics, while the outline sends user commands back through the split controller to the document.

Ownership and state

Ownership evidence

DesktopClassMarkdownEditor/DesktopClassMarkdownEditor/Views/SplitViewController.swift:13 — split-controller object graph

    let document: MarkdownDocument
    let editorViewController: EditorViewController
    let outlineViewController: OutlineViewController

    init(document: MarkdownDocument) {
        self.document = document
        editorViewController = EditorViewController(document: document)
        outlineViewController = OutlineViewController()
    }
Owner Object or state Relationship Mutation authority
DocumentBrowserViewController MarkdownDocument and SplitViewController Creates both for a selected URL and presents the split controller The browser begins/ends the document presentation session.
SplitViewController Document, editor, and outline controllers Strong immutable references created in init It wires delegates and routes outline/editor actions.
EditorViewController EditorView, PreviewView, Parser, OperationQueue Creates and retains feature subsystems It schedules parsing, refreshes preview, and updates the document.
EditorViewController Delegate and document browser Weak references Collaborators receive callbacks without ownership cycles.

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

DesktopClassMarkdownEditor/DesktopClassMarkdownEditor/Views/EditorViewController.swift:11 — editor collaboration contract

protocol EditorViewControllerDelegate: AnyObject {
    func editor(_ editorViewController: EditorViewController, didParse document: ParsedDocument)
}
Type Responsibility Depends on or conforms to
DocumentBrowserViewController Creates/imports document sessions UIDocumentBrowserViewControllerDelegate
SplitViewController Composes children and mediates editor/outline events EditorViewControllerDelegate, OutlineViewControllerDelegate
EditorViewController Edits text, parses Markdown, and updates preview UIDocumentViewController plus UIKit delegates
OutlineViewController Displays parsed outline and emits structural edit commands UICollectionViewDelegate and its local delegate
MarkdownDocument Owns document text and structural mutations UIDocument
Parser / ParsedDocument Transform Markdown text into preview and outline representations Markdown model types

The local editor and outline delegate protocols are real capability seams. They decouple child controllers from their concrete parent, but do not make every model or view protocol-based.

Access control

Symbol Access Verified effect Design reason
MarkdownTag.htmlTransformer private Only MarkdownTag can invoke/store the transformation closure Protects tag rendering invariants.
OutlineViewController.dataSource and update helpers private Only the outline implementation mutates snapshots Prevents other controllers from bypassing outline update methods.
EditorViewController.updatePreview private Callable only inside the editor file/type Parsing and queue scheduling remain an implementation detail.
Feature types and local protocols implicit internal Visible across the app target The document, split, and editor files collaborate without exporting a framework API.

Reference code

DesktopClassMarkdownEditor/DesktopClassMarkdownEditor/Markdown/MarkdownTag.swift:39 — tag data and private transformer

    let openMarkdown: String
    let midMarkdown: String
    let closeMarkdown: String
    let htmlTag: String

    let options: Options
    let outlineRepresentation: OutlineRepresentation?
    private let htmlTransformer: ((MarkdownTag, String) -> String)?

The source has no Swift open declaration here: init(open:) is an initializer parameter, not an access modifier, and the local prompt variable is likewise not open. Core feature types remain module-internal.

Logic ownership and placement

Logic Owning type or file Why it lives there
Document open/import DocumentBrowserViewController It owns UIDocumentBrowserViewController callbacks and presentation.
Text editing, parse scheduling, preview EditorViewController These operations share editor state and cancellation/queue concerns.
Outline UI and structural commands OutlineViewController Collection snapshots and outline menus stay together.
Cross-child synchronization SplitViewController It conforms to both local delegate protocols and knows both children.
Markdown transformation Parser, MarkdownTag, ParsedDocument Pure transformation remains outside view controllers.

Design patterns

Pattern Source evidence Purpose or tradeoff
Document-controller composition DesktopClassMarkdownEditor/DesktopClassMarkdownEditor/Views/DocumentBrowserViewController.swift:82 Creates one explicit object graph per open document.
Local delegate coordination DesktopClassMarkdownEditor/DesktopClassMarkdownEditor/Views/SplitViewController.swift:11 Child controllers report intent/results without retaining their coordinator.
Background parse and preview pipeline DesktopClassMarkdownEditor/DesktopClassMarkdownEditor/Views/EditorViewController.swift:39 and DesktopClassMarkdownEditor/DesktopClassMarkdownEditor/Views/EditorViewController.swift:326 Parser and operation queue isolate expensive transformation from UI composition.

Naming conventions

  • Types use domain/role names (MarkdownDocument, Parser, OutlineElement, EditorViewController).
  • Local protocols name the collaborator role with a Delegate suffix.
  • Commands use concrete verbs (presentDocument, updatePreview, swapTags, duplicate, delete).
  • Folders separate Document Management, Markdown transformation, Utilities, and Views.

Architecture takeaways

  • Make the per-document split controller the composition root, not the application delegate.
  • Use narrow weak delegates for sibling-view synchronization and keep document mutation on the document model.
  • Do not infer access modifiers from parameter names such as open; verify declaration syntax.
  • Keep parser/preview work behind a dedicated editor-owned pipeline.

Source map

Source file Architectural role
DesktopClassMarkdownEditor/DesktopClassMarkdownEditor/Views/DocumentBrowserViewController.swift Document discovery/import and presentation
DesktopClassMarkdownEditor/DesktopClassMarkdownEditor/Views/SplitViewController.swift Editor/outline composition and coordination
DesktopClassMarkdownEditor/DesktopClassMarkdownEditor/Views/EditorViewController.swift Editing, parsing, preview, commands
DesktopClassMarkdownEditor/DesktopClassMarkdownEditor/Views/OutlineViewController.swift Outline snapshots and structural actions
DesktopClassMarkdownEditor/DesktopClassMarkdownEditor/Document Management/MarkDownDocument.swift Document state and mutations
DesktopClassMarkdownEditor/DesktopClassMarkdownEditor/Markdown/Parser.swift / DesktopClassMarkdownEditor/DesktopClassMarkdownEditor/Markdown/MarkdownTag.swift Markdown transformation model