Sample CodeiOS, iPadOS, Mac Catalyst, macOSReviewed 2026-07-21View on Apple Developer

Continuing User Activities with Handoff

At a glance

Item Summary
Purpose Continue a viewed map region or an open store-detail interaction between iOS and macOS.
Architecture Parallel UIKit/AppKit controllers share store models, search logic, an initial-region state enum, and an NSUserActivity encoding extension.
Handoff boundary Activities carry stable region and store URL data; the receiving controller reconstructs the map and waits for matching search results before presenting detail.

Project structure

HandoffMapViewer/
├── Shared sources/
│   ├── AppleStore.swift
│   ├── AppleStoreDirectory.swift
│   ├── InitialMapRegionState.swift
│   └── NSUserActivity+HandoffMapViewer.swift
├── HandoffMapViewerIOS/*ViewController.swift
└── HandoffMapViewerMac/*ViewController.swift

Shared files hold portable state and serialization. Platform folders contain near-parallel presentation and lifecycle adapters.

Overall architecture

Reference code

HandoffMapViewer/Shared sources/NSUserActivity+HandoffMapViewer.swift:38 — shared code creates the viewing activity and defines its required payload contract.

    static func initForRegionViewing() -> NSUserActivity {
        let activity = NSUserActivity(activityType: viewingActivityType)
        activity.title = NSLocalizedString("Viewing Map", comment: "Viewng Map")
        activity.requiredUserInfoKeys = viewingActivityRequiredKeys
        activity.isEligibleForHandoff = true
        return activity
    }

Ownership and state

Ownership evidence

HandoffMapViewer/HandoffMapViewerIOS/MapViewController.swift:21 — the screen owns search, activity, and restoration state.

class MapViewController: UIViewController {
    // ...
    @IBOutlet private weak var mapView: MKMapView!
    // ...
}

Each platform controller is a feature owner. AppleStoreDirectory owns its deduplicated store set and iCloud favorite persistence. The system owns activity transfer; the receiving controller owns reconstruction and the state that prevents current-location startup from racing Handoff.

Class and protocol design

Type Responsibility
AppleStore Validated, hashable store value derived from MKMapItem.
AppleStoreDirectory Search, deduplicate, callback, and favorite persistence.
NSUserActivity extension Define activity types, keys, constructors, encoders, and decoders.
InitialMapRegionState Model startup/restoration progress explicitly.
Platform MapViewController types Adapt UIKit/AppKit, MapKit, location, popover, and activity lifecycle.

The sample relies on framework delegates rather than an app-defined cross-platform controller protocol; source sharing occurs below the UI layer.

Access control

Boundary Effect and rationale
Private activity payload keys Only the codec extension can read/write the wire representation.
Public activity-type constants Platform targets can identify activity categories; the rest of the shared types remain app-internal.
Private controller managers, activities, and restoration state Keeps lifecycle transitions within one screen owner.
Private directory store set and persistence helpers Forces discovery and favorites through validated methods.
Weak/self-aware callback capture Avoids the asynchronous search owning the directory/controller indefinitely.

HandoffMapViewer/Shared sources/NSUserActivity+HandoffMapViewer.swift:24 shows private payload keys; HandoffMapViewer/Shared sources/AppleStoreDirectory.swift:16 shows private store persistence state.

Logic ownership and placement

Logic Owner
Activity payload schema NSUserActivity extension
Store validation and identity AppleStore
Search, deduplication, favorites AppleStoreDirectory
Map, popover, activity switching Platform MapViewController
Startup race and pending detail restoration InitialMapRegionState

Design patterns

Pattern Evidence Purpose
Activity codec HandoffMapViewer/Shared sources/NSUserActivity+HandoffMapViewer.swift:16 Centralizes the cross-device payload contract.
State machine enum HandoffMapViewer/Shared sources/InitialMapRegionState.swift:10 Distinguishes unset, local, viewing, and pending-store startup paths.
Callback HandoffMapViewer/Shared sources/AppleStoreDirectory.swift:24 Reports newly discovered stores without knowing the map UI.
Cross-platform shared model HandoffMapViewer/Shared sources/AppleStore.swift:16 Keeps identity and validation consistent between apps.
Framework delegates HandoffMapViewer/HandoffMapViewerIOS/MapViewController.swift:134 Integrates MapKit, location, popover, and activity events.

Naming conventions

  • Activity types use viewing and storeEditing, matching the user action rather than a screen class.
  • Serialization helpers pair verbs: updateViewingRegion / viewingRegion and updateEditing / editingStoreURL.
  • Platform folders repeat controller names intentionally because they are alternative target implementations.

Architecture takeaways

  • Put the Handoff payload schema in one shared codec boundary.
  • Transfer stable identifiers and reconstruct live framework objects on the receiver.
  • Model startup/restoration phases explicitly to avoid racing default navigation.
  • Share models and codecs across platforms while keeping UI lifecycle code native to each target.

Source map

Source Role
HandoffMapViewer/Shared sources/NSUserActivity+HandoffMapViewer.swift:16 Handoff codec
HandoffMapViewer/Shared sources/AppleStore.swift:16 Store value model
HandoffMapViewer/Shared sources/AppleStoreDirectory.swift:14 Search and favorites owner
HandoffMapViewer/HandoffMapViewerIOS/MapViewController.swift:11 UIKit integration
HandoffMapViewer/HandoffMapViewerMac/MapViewController.swift:11 AppKit integration