How do document-based apps and multiple scenes work on iPad, and how should state be organised?
Predict first
A SwiftUI document type and a DocumentGroup scene: does this compile on iOS and on watchOS?
import SwiftUI
import UniformTypeIdentifiers
struct TextDoc: FileDocument {
static var readableContentTypes: [UTType] { [.plainText] }
var text: String
init(text: String = "") { self.text = text }
init(configuration: ReadConfiguration) throws { text = String(decoding: configuration.file.regularFileContents ?? Data(), as: UTF8.self) }
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper { FileWrapper(regularFileWithContents: Data(text.utf8)) }
}
struct A: App { var body: some Scene { DocumentGroup(newDocument: TextDoc()) { f in Text(f.document.text) } } }
- Yes on iOS
- No on iOS
Predict first
The same document type built for watchOS:
import SwiftUI
import UniformTypeIdentifiers
struct TextDoc: FileDocument {
static var readableContentTypes: [UTType] { [.plainText] }
var text: String
init(text: String = "") { self.text = text }
init(configuration: ReadConfiguration) throws { text = "" }
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper { FileWrapper(regularFileWithContents: Data(text.utf8)) }
}
- It compiles
- It is an error: FileDocument is unavailable in watchOS
Mental model. Scenes are separate desks in one office. Each desk has its own papers (document and view state), and they all share the filing cabinet (the model store). If one clerk’s work leaks onto the next desk’s papers, you have a global variable.
On iPad (and Mac and visionOS) the user can open several windows of your app, side by side or on different Stage Manager canvases. The system models each window as a scene (UIScene, with a UIWindowScene for UIKit, or a WindowGroup in SwiftUI), and your app is a set of scenes sharing one process. Declaring support is a configuration choice (UIApplicationSupportsMultipleScenes in Info.plist and a scene delegate), and the rule is simple: each scene must be self-contained. A scene has its own navigation state, selection and window size, so a document or a mailbox open in window A must not change when window B shows a different one. Shared, app-wide data (the model store, account, caches) lives above the scenes in the App or a shared object, while view state lives in the scene (SwiftUI’s @SceneStorage restores it per scene, and NSUserActivity state restoration lets the system recreate a scene on relaunch). Users create new windows by drag and drop (dragging an item out becomes a new window with a user activity), from the App Exposé or the multitasking menu, or through requestSceneSessionActivation in code; they also resize freely, so layouts must respond to size (size classes and GeometryReader-free adaptive layout such as NavigationSplitView) and not to the device.
Document-based apps are the natural fit. In SwiftUI, a DocumentGroup(newDocument:) scene takes a FileDocument (a value type that reads from a ReadConfiguration and writes a FileWrapper) or a ReferenceFileDocument for large or reference-based documents, and declares the readableContentTypes as UTTypes. The system supplies the document browser (the Files-style launch screen), new/open/duplicate/rename, autosave, iCloud Drive integration, undo and a window per document. In UIKit, UIDocumentBrowserViewController and UIDocument do the same job, using file coordination so that your app, iCloud and other processes do not corrupt a file by writing at the same time. Important practices: define your document type and its uniform type identifier in Info.plist (CFBundleDocumentTypes and exported types) so Files opens it with your app; keep documents self-contained packages and versioned; save incrementally and off the main thread; handle conflicts (NSFileVersion) and external changes; support the Files app’s open-in-place; and treat the document’s lifetime as the scene’s lifetime. FileDocument and DocumentGroup are available on iOS and macOS (not on watchOS or tvOS). Test with multiple windows, Split View at several widths, Stage Manager, an external display and relaunch with windows restored, since hidden global state (a singleton for “the current document”) is the common bug that these scenarios expose.
The document API is available on iOS, where this compiles. It is not available on watchOS, which is why the same code fails there.
The document-based API does not exist on watchOS.
Common traps
- Keeping “the current document” or selection in a singleton. Two windows overwrite each other; keep that state in the scene.
- Writing document files without coordination. iCloud or another process can corrupt them; use
UIDocumentorNSFileCoordinator.
They might follow up with
- What restores a window’s state after relaunch? Per-scene state, for example
@SceneStorageor a user activity for the scene. - Where do
FileDocumentandDocumentGroupwork? iOS and macOS, not watchOS or tvOS.
Related questions
- 20.1.2 How do the platforms differ in input, process model and capabilities, and which entitlements and APIs change…
- 20.3.1 Mac Catalyst, native AppKit, SwiftUI for macOS, or an iPad app on the Mac: how do you choose, and what…
- 7.8.2 How do you handle Apple Pencil input, and how does multitasking change your layout assumptions?