Add Functionality to Finder with Action Extensions
At a glance
| Item | Summary |
|---|---|
| Purpose | Add three Finder actions: uppercase text, create image thumbnails, and remove image transparency. |
| Architecture | A nearly empty host app plus three independent extension targets; small NSImage extensions hold reusable transformations. |
| Main boundary | Finder supplies NSExtensionContext input and each action completes or cancels that request. |
Project structure
CustomQuickActions.xcodeproj
├── Main Application/AppDelegate.swift
├── UppercaseAction/ActionRequestHandler.swift
├── ThumbnailAction/ActionRequestHandler.swift
├── RemoveOpacityAction/ActionViewController.swift
└── NSImage Extensions/
├── NSImage+Thumbnail.swift
├── NSImage+Opaque.swift
└── NSImage+SavePNG.swift
The folders reflect executable targets, not layers in one process. The two request handlers are headless; the opacity action uses a view controller because it asks for a color.
Overall architecture
flowchart LR
Finder["Finder / extension host"] --> Context["NSExtensionContext"]
Context --> Upper["Uppercase ActionRequestHandler"]
Context --> Thumb["Thumbnail ActionRequestHandler"]
Context --> Opaque["RemoveOpacity ActionViewController"]
Thumb --> ImageHelpers["NSImage utilities"]
Opaque --> ImageHelpers
Upper --> Complete["completeRequest"]
Thumb --> Complete
Opaque --> Complete
Reference code
ThumbnailAction/ActionRequestHandler.swift:13 — the extension entry point consumes the framework-owned request.
func beginRequest(with context: NSExtensionContext) {
// ...
}Ownership and state
classDiagram
NSExtensionContext o-- NSExtensionItem : supplies
NSExtensionItem o-- NSItemProvider : attachments
ActionRequestHandler ..> DispatchGroup : creates per request
ActionRequestHandler ..> NSItemProvider : creates outputs
ActionViewController *-- NSColor : selected state
Ownership evidence
RemoveOpacityAction/ActionViewController.swift:43 — output state and synchronization are scoped to one user action.
class ActionViewController: NSViewController {
// ...
var outputAttachments: [NSItemProvider] = []
// ...
}Finder owns the input context. Each extension invocation owns its local output array and dispatch group. NSItemProvider load handlers defer creation of replacement files until the host requests a representation.
Class and protocol design
| Type | Role | Protocol or base class |
|---|---|---|
Two ActionRequestHandler types |
Transform text or images without UI | NSObject, NSExtensionRequestHandling |
ActionViewController |
Own the color picker and image transformation flow | NSViewController |
NSImage extensions |
Thumbnail, opacity, and PNG operations | Concrete type extensions; no app protocol |
AppDelegate |
Satisfy the containing app target | NSApplicationDelegate |
The repeated handler name is safe because each declaration is compiled into a different extension target.
Access control
| Boundary | Effect and rationale |
|---|---|
Most declarations are implicit internal |
Each handler and helper is visible only inside the target that compiles it; there is no library API. |
fileprivate createThumbnail |
Limits the provider factory to its source file, keeping it out of the extension’s callable surface. |
public image helpers |
Explicitly makes the member exportable wherever that source is compiled, although the sample does not expose a public framework. |
| Local output variables | Per-request state cannot be retained or mutated by another request. |
ThumbnailAction/ActionRequestHandler.swift:58 shows the narrow file-scoped helper, while NSImage Extensions/NSImage+Thumbnail.swift:12 shows the wider member declaration.
Logic ownership and placement
| Logic | Location | Why it belongs there |
|---|---|---|
| Request validation and completion | Each action entry type | It is specific to the extension contract. |
| Asynchronous attachment aggregation | Request handler or action controller | The owner decides when all providers are ready. |
| Pixel transformations and PNG writing | NSImage extensions |
Both image actions can reuse focused operations. |
| Temporary replacement URL creation | Image action types | Output naming differs by action. |
Design patterns
| Pattern | Evidence | Use |
|---|---|---|
| App extension | UppercaseAction/ActionRequestHandler.swift:10 |
Lets Finder invoke a target without launching the host UI. |
| Framework callback | ThumbnailAction/ActionRequestHandler.swift:13 |
AppKit hands request ownership into the extension. |
| Dispatch barrier | RemoveOpacityAction/ActionViewController.swift:45 |
Completes only after asynchronous attachment loads finish. |
| Concrete utility extension | NSImage Extensions/NSImage+Opaque.swift:10 |
Keeps transformations out of request orchestration. |
Naming conventions
- Target folders use capability names such as
ThumbnailActionandUppercaseAction. - Framework entry types use Apple’s expected role names:
ActionRequestHandlerandActionViewController. - Helper methods state the result or side effect:
thumbnailImage,opaqueImage, andsavePNGToDisk.
Architecture takeaways
- Treat each app extension as its own composition root and lifetime.
- Keep extension-context orchestration close to the entry point and reusable transformations separate.
- Use a per-request synchronization object; do not make request state global.
- This sample is framework-protocol-oriented at its boundary, not an abstract protocol-oriented domain design.
Source map
| Source | Role |
|---|---|
UppercaseAction/ActionRequestHandler.swift:13 |
Text request flow |
ThumbnailAction/ActionRequestHandler.swift:58 |
Lazy thumbnail file provider |
RemoveOpacityAction/ActionViewController.swift:22 |
UI action and image request flow |
NSImage Extensions/NSImage+SavePNG.swift:12 |
Shared file output helper |