Skip to content
macOSDeep Dive Published Updated 8 min readViews unavailable

NSSharingServicePicker on macOS: Share Items, Anchors, and Completion

Present NSSharingServicePicker with valid pasteboard items, retained delegate ownership, accurate file-versus-URL semantics, and safe completion handling.

NSSharingServicePicker presents the system’s available services for one or more items. In current macOS versions it appears as a share-sheet popover; older releases present a menu of services. The picker handles the selected service after the person chooses it, but the app remains responsible for the content being shared, the validity of the anchor, the delegate’s lifetime, and interpreting success or failure from the underlying service.

Treat sharing as a handoff of data, not as a promise that a remote recipient received or persisted it. The person may cancel, a service may fail, and an item may no longer exist by the time it is used. Keep the source document intact and expose completion only when the selected service provides a meaningful completion signal.

Build valid share items

The picker initializer accepts items that conform to NSPasteboardWriting or NSPreviewRepresentableActivityItem. Examples include strings, images, URLs, NSItemProvider, and documents. Validate each item for the service workflows the product intends to support. A filename string, a file URL, and a remote URL have different meaning to receiving services.

import AppKit

@MainActor
final class ShareController: NSObject, NSSharingServicePickerDelegate {
    private var activePicker: NSSharingServicePicker?

    func presentShare(for fileURL: URL, from anchor: NSView) {
        guard anchor.window != nil else { return }
        let picker = NSSharingServicePicker(items: [fileURL])
        picker.delegate = self
        activePicker = picker
        picker.show(
            relativeTo: anchor.bounds,
            of: anchor,
            preferredEdge: .minY
        )
    }
}

The example retains the picker while the interaction is active and keeps the delegate alive through its owning controller. The picker delegate is weak, so assigning a temporary delegate that immediately deallocates will silently remove customization and completion observation. A production controller should clear its activePicker when the share workflow ends through the delegate or owner lifecycle that the app uses.

File URL and remote URL mean different payloads

Apple documents that if an item is a file URL, the picker shares the file content. If the URL is remote, the picker shares the URL itself. That difference can change what the recipient receives and whether the data remains available after the source document closes. Decide whether the product intends to share a file copy, an address, a rendered preview, or a provider-backed representation.

If sharing a file, ensure it exists and is readable for the duration required by the selected service. Do not delete a temporary export immediately after presenting the picker. If the data is generated on demand, use a stable temporary location or a provider object whose lifecycle extends through the handoff. Verify the exported copy before presenting it so users do not share partial output.

For a remote URL that requires authentication, confirm that the address is intended to be public to the recipient. Do not put bearer tokens or private query credentials into the share URL. Prefer a controlled share link with server-side access policy instead of exporting the app’s internal authenticated endpoint.

Anchor and presentation lifecycle

Show the picker relative to a visible view in the window. The rect is expressed in the coordinate system of that view; pass NSZeroRect to use the view bounds. A hidden anchor, a reused collection cell, or a window that closes during presentation can make the popover point at the wrong content. Tie the share request to a stable model identity, and dismiss or regenerate the item if the source changes before the user selects a service.

The share sheet may be a menu or popover depending on the macOS version. Do not build application logic around its internal view hierarchy, exact animation, or screen position. Use the public delegate callbacks and keep the anchor available. Test toolbar overflow and narrow windows if sharing is initiated from a toolbar item.

Delegate callbacks and completion boundaries

NSSharingServicePickerDelegate can customize which services appear for proposed items, receive notification when a service is chosen, and provide a delegate for that service. Use these callbacks to adjust the service list only when there is a product-level reason, such as excluding a service that cannot handle the content. Do not remove system options merely to promote one destination without a clear user benefit.

The picker informs the delegate that a person selected a service, then the selected service receives the item after the delegate callback returns. That notification is not a successful completion result. When the actual sharing service exposes a completion handler or delegate, use it to report success, failure, or cancellation. If the service does not provide a durable delivery guarantee, describe only that the handoff was initiated.

Avoid starting two pickers for the same item in response to repeated clicks. Coalesce presentation requests or close the previous picker before opening a replacement. If the item is a document that can change during sharing, decide whether the share uses a snapshot at click time or the latest data when the service reads it. A stable exported snapshot usually gives clearer semantics.

Keep the delegate callbacks small and deterministic. sharingServicePicker(_:sharingServicesForItems:) can filter the proposed services for the items at hand; it should not perform network requests or block while it decides. sharingServicePicker(_:delegateFor:) lets an application supply a delegate for a chosen service when the app needs the service-level lifecycle. Return an object whose owner remains alive for the entire service operation. In sharingServicePicker(_:didChoose:), record that the user selected a destination, but do not mark the transfer complete there: Apple documents that the service receives the items after this callback returns.

This ordering matters for mutable inputs. If a service reads the source lazily, deleting or replacing a temporary file from inside the selection callback can invalidate the handoff. Retain the export until the service reports its own terminal outcome, or until an explicit timeout/cleanup policy runs. Do not retain temporary exports forever either; maintain an owner for the transfer and make cleanup idempotent so both cancellation and completion release resources safely.

Avoid using the picker delegate as a general analytics hook that records content. A service name and a coarse result may be enough to diagnose an integration issue. If the selected service does not expose a success callback, report the state as “handed off” or “service opened,” not “delivered.” Email, AirDrop, and third-party extensions have different downstream behavior, and a local callback cannot prove that a remote person opened or retained the item.

Privacy and content minimization

Share only the content the user selected. A preview item should not include hidden metadata, editing history, internal identifiers, or extra attachments unless the user expects them. For images, decide whether to share the original or a rendered derivative. For documents, check metadata and embedded resources if they can contain private information.

Do not log shared file contents, recipient identity, or full share URLs in ordinary diagnostics. Record item type, size, service identity where available, and outcome category. If the content is sensitive, show a clear confirmation about what will leave the app before presenting system services.

If the share item is a URL, review both its scheme and its query parameters before handing it to another process. A file URL may refer to a security-scoped location or a temporary export; the picker receiving the URL does not replace the app’s responsibility to manage access and lifetime. For a web URL, canonicalize only according to the product’s URL policy and avoid silently stripping parameters that are required for a legitimate share. The safer default for secrets is not to share them at all.

Present the picker from a user action and use an accessible control label that describes the item being shared. Do not invoke it as a surprise side effect after background work. If the item becomes unavailable while the picker is visible, disable or cancel the originating action and explain what changed. For multiple items, make the group intentional: services may handle a list differently from a single item, and an unexpected extra item can disclose data.

Testing and acceptance matrix

Test a text item, image, local file URL, remote URL, missing file, temporary generated file, unavailable anchor, closed parent window, delegate deallocation attempt, user dismissing the picker, service selection, service failure, and large payload. Verify file-versus-URL behavior, that source documents remain unchanged, and that a picker never reports delivery based solely on service selection.

Check the UI on versions where the picker appears as a menu and as a popover, and at narrow and wide window sizes. Test keyboard and accessibility navigation, VoiceOver labels for the share button, and whether the item preview accurately represents the outgoing payload. Capture the supported OS version and item type in test results because the presentation surface changes across releases.

NSSharingServicePicker provides native destination selection and handoff. Your app owns the data snapshot, file lifetime, privacy review, anchor, delegate retention, and truthful completion semantics. Treat selection as the beginning of a transfer workflow, not proof that another person received the content.

Related:

Sources:

Comments