Supporting multiple windows on iPad
At a glance
| Item | Summary |
|---|---|
| Purpose | Open, restore, and reactivate gallery and photo-inspector scenes using NSUserActivity payloads. |
| App architecture | AppDelegate selects a scene configuration from an activity type, GalleryViewController requests an inspector for a selected Photo, and InspectorSceneDelegate reuses a matching session or creates and restores one. |
| Main patterns | Activity-routed scenes, Session reuse before creation, Restorable activity payload |
| Project style | Nine Swift files; storyboard-based UIKit scenes with gallery, detail, and inspector feature slices. |
Project structure
Source bundle/
├── Gallery/
│ ├── AppDelegate.swift
│ ├── InspectorSceneDelegate.swift
│ ├── GalleryViewController.swift
│ ├── InspectorViewController.swift
│ ├── PhotoDetailViewController.swift
│ ├── SceneDelegate.swift
│ ├── Photo.swift
│ ├── GalleryViewController+Toolbar.swift
│ └── PhotoCell.swift
├── Configuration/
│ └── SampleCode.xcconfig
└── Gallery.xcodeproj/
├── .xcodesamplecode.plist
└── project.pbxproj
Structure observations
- The app delegate owns configuration routing only; gallery and inspector controllers own feature presentation.
Photoconverts domain identity into detail/inspector activities, keeping payload keys out of controllers.- Inspector activation searches existing sessions first, preventing duplicate windows for the same photo.
Overall architecture
flowchart LR
ACT["NSUserActivity type and photo payload"] --> APP["AppDelegate scene configuration router"]
APP --> GALLERY["Gallery scene"]
APP --> INSPECTOR["Inspector scene"]
GALLERY --> PHOTO["Selected Photo"]
PHOTO --> REQUEST["InspectorSceneDelegate.openInspectorSceneSessionForPhoto"]
REQUEST --> FIND["Find matching open session"]
FIND -->|found| REACTIVATE["Reactivate session"]
FIND -->|missing| CREATE["Create session with activity"]
CREATE --> INSPECTOR
REACTIVATE --> INSPECTOR
Reference code
Gallery/AppDelegate.swift:18 — activity-based scene configuration
func application(_ application: UIApplication,
configurationForConnecting connectingSceneSession: UISceneSession,
options: UIScene.ConnectionOptions) -> UISceneConfiguration {
let configurationName: String
switch options.userActivities.first?.activityType {
case UserActivity.GalleryOpenInspectorActivityType:
configurationName = "Inspector Configuration"
default:
configurationName = "Default Configuration"
}
return UISceneConfiguration(name: configurationName,
sessionRole: connectingSceneSession.role)
}Interpretation
Scene creation is routed by an activity contract rather than a direct controller call. The gallery asks the inspector scene coordinator to open a photo, and the inspector coordinator owns session identity, activation, configuration, and restoration.
Ownership and state
classDiagram
PhotoManager *-- PhotoArray : shared catalog
GalleryViewController o-- PhotoArray : retained value
GalleryViewController *-- UICollectionView : creates
GalleryViewController *-- DiffableDataSource : creates
Photo *-- NSUserActivity : computed payload
UISceneSession o-- PhotoIdentity : userInfo
InspectorSceneDelegate o-- UIWindow : UIKit supplies
Ownership evidence
Gallery/GalleryViewController.swift:13 — gallery model and collection state
let photos = PhotoManager.shared.photos
enum Section {
case main
}
var dataSource: UICollectionViewDiffableDataSource<Section, Int>! = nil
var collectionView: UICollectionView! = nil| Owner | Object or state | Relationship | Mutation authority |
|---|---|---|---|
PhotoManager.shared |
Immutable sample photo catalog | Static singleton value | It defines the bundled gallery data. |
GalleryViewController |
Photo array, collection view, diffable data source | Reads shared data and creates the UIKit presentation objects | It selects photos and requests detail/inspector presentation. |
Each Photo value |
Activity user-info and target-content identity | Computes new activities on demand | The model defines how its identity crosses a scene boundary. |
InspectorSceneDelegate |
Inspector window/session restoration state | UIKit supplies the window; the delegate annotates the scene session | It configures, restores, finds, and activates inspector sessions. |
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
Gallery/InspectorSceneDelegate.swift:89 — reuse-before-create scene policy
class func activeInspectorSceneSessionForPhoto(_ photoAsset: String) -> UISceneSession? {
for session in UIApplication.shared.openSessions
where session.configuration.delegateClass == InspectorSceneDelegate.self {
if let userInfo = session.userInfo,
userInfo[InspectorSceneDelegate.inspectorScenePhotoAsset] as? String == photoAsset {
return session
}
}
return nil
}| Type | Responsibility | Depends on or conforms to |
|---|---|---|
AppDelegate |
Maps activity types to named scene configurations | UIApplicationDelegate |
SceneDelegate |
Restores the default gallery/detail scene | UIWindowSceneDelegate |
InspectorSceneDelegate |
Configures inspector content and manages inspector sessions | UIWindowSceneDelegate, NSUserActivityDelegate |
GalleryViewController |
Displays photos and emits detail/inspector commands | Collection view data source, drag, and delegate roles |
Photo |
Photo identity plus activity-payload factory | Value type |
Scene, collection, drag, and user-activity protocols are UIKit/Foundation callbacks. The sample defines no local protocol abstraction; its cross-scene contract is the NSUserActivity type and user-info schema.
Access control
| Symbol | Access | Verified effect | Design reason |
|---|---|---|---|
Photo |
public type |
The type is visible outside the module, but its stored properties remain internal | The explicit modifier is broader than the rest of this app sample; it does not make members public automatically. |
Photo.assetName, title, activity properties |
implicit internal |
Usable only within the app module | Scene files collaborate without exporting a complete public model API. |
GalleryViewController.makeTargetedPreview |
private |
Callable only inside the controller’s lexical scope | Context-menu preview construction is a view implementation detail. |
| Scene delegates and manager | implicit internal |
Available to the application target | UIKit resolves configured classes without a library-level public surface. |
Reference code
Gallery/Photo.swift:20 — public type with internal members
public struct Photo {
let assetName: String
let title: String
var inspectorUserActivity: NSUserActivity {
let userActivity = NSUserActivity(
activityType: UserActivity.GalleryOpenInspectorActivityType)
userActivity.userInfo = activityUserInfo
return userActivity
}
}Swift access is per declaration: public struct Photo does not promote unmodified properties to public. No open class hierarchy or fileprivate boundary appears in this feature.
Logic ownership and placement
| Logic | Owning type or file | Why it lives there |
|---|---|---|
| Scene configuration selection | AppDelegate |
UIKit asks it which named scene configuration should connect. |
| Photo selection, collection snapshots, inspector command | GalleryViewController |
These operations share the gallery’s UI and selection state. |
| Cross-scene payload construction | Photo |
Payload identity is domain data, not presentation mechanics. |
| Session lookup, activation, restoration | InspectorSceneDelegate |
It owns inspector-scene lifecycle and session metadata. |
Design patterns
| Pattern | Source evidence | Purpose or tradeoff |
|---|---|---|
| Activity-routed scenes | Gallery/AppDelegate.swift:18 |
Selects a concrete scene role from a stable activity type. |
| Session reuse before creation | Gallery/InspectorSceneDelegate.swift:113 |
Avoids opening duplicate inspectors for one photo. |
| Restorable activity payload | Gallery/Photo.swift:49 |
Carries photo identity through activation, suspension, and restoration. |
Naming conventions
- Scene roles are explicit (
SceneDelegate,InspectorSceneDelegate) and configuration names match those roles. - Activity constants include the use case (
GalleryOpenDetailActivityType,GalleryOpenInspectorActivityType). - Commands state lifecycle intent (
openInspectorSceneSessionForPhoto,activeInspectorSceneSessionForPhoto). - Controller suffixes describe presentation roles;
PhotoManageridentifies the data owner.
Architecture takeaways
- Use an activity type and payload as the contract between a feature controller and a new scene.
- Let the scene delegate own reuse, activation, configuration, and restoration policy.
- Read access per declaration: a public container can still have internal members.
Source map
| Source file | Architectural role |
|---|---|
Gallery/AppDelegate.swift |
Scene-configuration routing |
Gallery/GalleryViewController.swift |
Gallery composition, selection, and inspector requests |
Gallery/InspectorSceneDelegate.swift |
Inspector configuration, session lookup, activation, and restoration |
Gallery/SceneDelegate.swift |
Default scene restoration |
Gallery/Photo.swift |
Photo catalog and activity payloads |
Gallery/InspectorViewController.swift / Gallery/PhotoDetailViewController.swift |
Inspector and in-scene detail presentation |