At a glance
| Item |
Summary |
| Purpose |
Demonstrates context-menu actions, submenus, previews, targeted previews, and commits across collections, tables, controls, views, and web content. |
| App architecture |
A storyboard-driven example matrix shares base list controllers and protocol-extension action factories; each concrete screen owns one UIKit integration variant. |
| Main patterns |
Protocol extension, template superclass, framework delegate, diffable data source, and split-view coordinator. |
| Project style |
One UIKit target with many small *Basic, *Submenu, *Preview, and *PreviewProvider screens. |
Project structure
ContextMenus/
├── ContextMenu.swift
├── Model.swift
├── BaseCollectionViewTestViewController.swift
├── BaseTableTestViewController.swift
├── CollectionView*.swift
├── TableView*.swift
├── Control*.swift
├── View*.swift
├── WebView*.swift
├── DetailViewManager.swift
├── SceneDelegate.swift
└── Base.lproj/Main.storyboard
Structure observations
- Filename families form a deliberate matrix of host (
CollectionView, TableView, Control, View, WebView) and behavior (Basic, Submenu, Preview, PreviewProvider).
- Base list controllers own common sample data and layout; subclasses override only context-menu callbacks.
- Shared menu-action construction lives in capability protocols, not a utility singleton.
Overall architecture
flowchart LR
Storyboard["Storyboard example menu"] --> Example["Concrete example controller"]
Example --> Base["Base table or collection controller"]
Base --> Data["Diffable data source and Model"]
Example --> Delegate["UIKit context-menu delegate callback"]
Example ..-> Capability["ContextMenu capability protocol"]
Capability --> Actions["UIAction and UIMenu factories"]
Delegate --> Preview["Optional preview controller"]
Scene["SceneDelegate"] --> Detail["DetailViewManager"]
Reference code
ContextMenus/CollectionViewPreviewProvider.swift:14 — one example combines shared list data, a custom preview provider, and protocol-built actions.
override func collectionView(_ collectionView: UICollectionView,
contextMenuConfigurationForItemAt indexPath: IndexPath,
point: CGPoint) -> UIContextMenuConfiguration? {
guard let item = dataSource.itemIdentifier(for: indexPath) else { return nil }
return UIContextMenuConfiguration(identifier: NSString(string: item.name),
previewProvider: { self.storyboard?.instantiateViewController(identifier: "PreviewViewController") },
actionProvider: { _ in
UIMenu(children: [self.inspectAction(indexPath),
self.duplicateAction(indexPath),
self.deleteAction(indexPath)])
})
}
Interpretation
This is a catalog architecture, not one feature pipeline. Each screen is intentionally concrete, while base controllers and capability extensions remove only the repeated data/layout and action-building mechanics.
Ownership and state
classDiagram
SceneDelegate *-- DetailViewManager : creates
DetailViewManager o-- UISplitViewController : receives
BaseCollectionViewTestViewController *-- UICollectionViewDiffableDataSource : creates
BaseTableViewTestViewController *-- UITableViewDiffableDataSource : creates
DiffableDataSource *-- Model : identifies values
ExampleController --> UIContextMenuInteraction : creates and attaches
UIView *-- UIContextMenuInteraction : retains after attachment
UIContextMenuConfiguration --> UIViewController : preview provider creates
Ownership evidence
ContextMenus/SceneDelegate.swift:25 — the scene creates and retains the split-view helper, then injects the storyboard-owned split controller.
func scene(_ scene: UIScene, willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions) {
if let splitViewController = window!.rootViewController as? UISplitViewController {
detailViewManager = DetailViewManager()
detailViewManager.splitViewController = splitViewController
}
}
| Owner |
Object or state |
Relationship |
Mutation authority |
| Scene delegate |
DetailViewManager |
Creates and strongly retains |
Scene setup only |
| Detail manager |
Split view and KVO observation |
Receives split view, installs itself as delegate |
Manager callbacks |
| Base list controller |
Diffable data source and initial snapshot |
Creates and retains |
Base and subclasses through internal property |
| Concrete example screen |
Configuration closures and preview controllers |
Creates per interaction |
Framework callback implementation |
| Host view/control |
UIContextMenuInteraction |
Retains after addInteraction |
UIKit lifecycle |
Class and protocol design
| Type or protocol |
Responsibility |
Depends on |
ContextMenu |
Require inspect/duplicate/delete commands without an index path |
Concrete adopter behavior |
IndexPathContextMenu |
Same capability for list items |
IndexPath and adopter behavior |
WebViewContextMenu |
Add one URL-specific action |
URL and adopter behavior |
| Protocol extensions |
Build reusable UIAction values that call required methods |
UIKit menu types |
| Base list controllers |
Configure layout, diffable source, and demo items |
Model, reusable cells |
| Concrete example controllers |
Implement one context-menu configuration/preview style |
UIKit or WebKit delegate APIs |
DetailViewManager |
Keep split-view detail navigation coherent during size changes |
UISplitViewControllerDelegate, KVO |
This is genuine protocol-oriented reuse: protocols describe commands, extensions provide action factories, and concrete screens retain control of feature behavior.
Access control
| Symbol |
Access |
Verified effect |
Likely rationale |
sectionInsets |
private |
Collection subclasses cannot modify base layout spacing. |
Protect base-controller layout details. |
Base dataSource |
implicit internal |
Concrete subclasses in other files can look up item identifiers. |
Required subclass collaboration across files. |
makeTargetedPreview helpers |
private |
Preview geometry stays inside each example screen. |
Avoid turning one demonstration’s geometry into shared API. |
DetailViewManager.viewControllersObserver |
private |
Only the manager owns observation lifetime. |
Keep KVO cleanup/state local. |
| Capability protocols and example types |
implicit internal |
Reusable within the app target, not exported. |
The sample is a catalog application. |
Reference code
ContextMenus/BaseCollectionViewTestViewController.swift:10 — common layout is private, while the data source is intentionally visible to subclasses.
class BaseCollectionViewTestViewController: UICollectionViewController {
private let sectionInsets = UIEdgeInsets(top: 6, left: 6, bottom: 6, right: 6)
enum Section { case main }
var dataSource: UICollectionViewDiffableDataSource<Section, Model>! = nil
}
Logic ownership and placement
| Logic |
Owning type or file |
Placement rationale |
| Common action construction |
ContextMenu.swift protocol extensions |
Shared behavior depends only on a small capability. |
| Shared list layout/data setup |
Base table/collection controllers |
Template behavior for many examples. |
| Configuration, preview, commit behavior |
Each concrete example controller |
Keeps each API variation readable in isolation. |
| Web-element context menus |
WebViewPreviewProvider |
WebKit delegate boundary owns URL extraction and fallback preview. |
| Split-detail replacement |
DetailViewManager |
Requires whole split-view context, separate from menu examples. |
Design patterns
| Pattern |
Source evidence |
Purpose or tradeoff |
| Protocol extension |
ContextMenus/ContextMenu.swift:10 |
Shares action factories while forcing adopters to supply effects. |
| Template superclass |
ContextMenus/BaseCollectionViewTestViewController.swift:10 |
Removes repeated list setup; subclasses depend on inherited mutable data source. |
| Example matrix |
ContextMenus/ControlBasic.swift:10 and neighboring families |
Makes API variations easy to compare at the cost of many small controllers. |
| Framework delegate |
ContextMenus/WebViewPreviewProvider.swift:32 |
Adapts UIKit/WebKit callbacks to configuration values and commits. |
| Diffable data source |
ContextMenus/BaseCollectionViewTestViewController.swift:27 |
Gives sample items stable identity for list interactions. |
Naming conventions
- Family prefixes name the host surface; suffixes name the demonstrated behavior.
Base...TestViewController signals reusable catalog scaffolding rather than production domain screens.
- Capability protocols use noun phrases; required methods use
perform..., and factories use ...Action.
PreviewProvider names controllers that supply preview closures, not standalone provider objects.
Architecture takeaways
- Use capability protocols plus extensions when many unrelated screens share action construction but not effects.
- Keep UIKit context-menu configuration with the host controller because identifiers, previews, and commit navigation are host-specific.
- Share only the stable scaffolding—data source and layout—through base controllers.
- Distinguish framework ownership after
addInteraction from temporary local creation.
Source map
| Source file |
Relevant symbols |
ContextMenus/ContextMenu.swift |
Three action capability protocols and default factories |
ContextMenus/BaseCollectionViewTestViewController.swift |
Shared collection setup |
ContextMenus/BaseTableTestViewController.swift |
Shared table setup |
ContextMenus/CollectionViewPreviewProvider.swift |
List preview-provider example |
ContextMenus/ControlBasic.swift |
Interaction and button-menu example |
ContextMenus/WebViewPreviewProvider.swift |
WebKit context menu and preview fallback |
ContextMenus/DetailViewManager.swift |
Split-view detail coordination |