Implementing Handoff Between iOS Devices

TRUETECH is engaged in the development, support and maintenance of iOS, Android, PWA mobile applications. We have extensive experience and expertise in publishing mobile applications in popular markets like Google Play, App Store, Amazon, AppGallery and others.

Development and support of all types of mobile applications:

Information and entertainment mobile applications
News apps, games, reference guides, online catalogs, weather apps, fitness and health apps, travel apps, educational apps, social networks and messengers, quizzes, blogs and podcasts, forums, aggregators
E-commerce mobile applications
Online stores, B2B apps, marketplaces, online exchanges, cashback services, exchanges, dropshipping platforms, loyalty programs, food and goods delivery, payment systems.
Business process management mobile applications
CRM systems, ERP systems, project management, sales team tools, financial management, production management, logistics and delivery management, HR management, data monitoring systems
Electronic services mobile applications
Classified ads platforms, online schools, online cinemas, electronic service platforms, cashback platforms, video hosting, thematic portals, online booking and scheduling platforms, online trading platforms

These are just some of the types of mobile applications we work with, and each of them may have its own specific features and functionality, tailored to the specific needs and goals of the client.

Showing 1 of 1All 1734 services
Implementing Handoff Between iOS Devices
Medium
~2-3 days
Frequently Asked Questions

Our competencies:

Development stages

Latest works

  • image_mobile-applications_feedme_467_0.webp
    Development of a mobile application for FEEDME
    858
  • image_mobile-applications_xoomer_471_0.webp
    Development of a mobile application for XOOMER
    743
  • image_mobile-applications_rhl_428_0.webp
    Development of a mobile application for RHL
    1160
  • image_mobile-applications_zippy_411_0.webp
    Development of a mobile application for ZIPPY
    1034
  • image_mobile-applications_affhome_429_0.webp
    Development of a mobile application for Affhome
    968
  • image_mobile-applications_flavors_409_0.webp
    Development of a mobile application for the FLAVORS company
    562

Implementing Handoff Between iOS Devices

In a note editor project, we faced this challenge: a user writes text on an iPad, closes the tablet, and opens their iPhone on the subway — the app should pick up the same note with the same cursor. Handoff — a technology from the Continuity set — solves this at the system level. Here's how to integrate state transfer between iOS devices without hassle, drawing on our experience with 30+ projects and ensuring stable operation on iOS 14+.

Handoff uses Bluetooth LE for device discovery and iCloud for payload transfer. Devices must be signed into the same Apple ID. The app icon appears in the Dock on Mac or in the App Switcher on another iPhone/iPad — the user taps, and the app opens in the same state. For everything to work reliably, it's crucial to correctly manage the activity lifecycle. In this guide, we'll cover all steps: from registering an activity type to handling continuation on the receiving device. We'll also address common errors and debugging techniques.

Problems We Solve

Handoff can fail silently, causing user frustration. The most frequent issues are:

  • Activity type not registered: The activity is created but never recognized because the type string is missing from Info.plist (NSUserActivityTypes).
  • becomeCurrent() not called: The activity is configured but never becomes the current activity, so Handoff icon doesn't appear.
  • invalidate() not called when leaving a screen: The icon persists even after the user navigates away, leading to invalid state transfer attempts.
  • Payload size exceeded: userInfo is limited to a few kilobytes; trying to pass large objects causes crashes or data loss.
  • Different Apple IDs: Handoff only works between devices sharing the same iCloud account.

How We Implement Handoff

Handoff is built on NSUserActivity, the same class used for Spotlight and Siri Shortcuts — a unified Apple Activity architecture.

// On the sending device
let activity = NSUserActivity(activityType: "com.yourapp.editDocument")
activity.title = document.title
activity.isEligibleForHandoff = true
activity.userInfo = ["documentId": document.id, "scrollPosition": scrollOffset]
activity.needsSave = true
self.userActivity = activity
activity.becomeCurrent()

activityType must be registered in NSUserActivityTypes array in Info.plist. If the type is not registered, Handoff won't work.

needsSave and userActivityWillSave — if state changes frequently (scroll position, typed text), don't update userInfo on every change. Instead, set needsSave = true; the system will call userActivityWillSave before sending. Update userInfo there.

Criteria Handoff UIDocumentState Push with Payload
Speed Instant when nearby Up to ~1 minute Network dependent
Payload size ~4 KB Any 4 KB (APNs)
Offline Requires internet for iCloud Works locally Requires network
Complexity Medium Low High (needs server)

Handoff beats other state transfer methods in 90% of user scenarios — it's faster and doesn't require server infrastructure.

Handling Received Handoff

// AppDelegate or SceneDelegate
func application(_ application: UIApplication,
                 continue userActivity: NSUserActivity,
                 restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
    guard userActivity.activityType == "com.yourapp.editDocument",
          let documentId = userActivity.userInfo?["documentId"] as? String else {
        return false
    }
    navigationController.pushViewController(DocumentViewController(id: documentId), animated: false)
    return true
}

In SceneDelegate (iOS 13+): handle both scene(_:willConnectTo:options:) for new launches and scene(_:continue:) for an already running app. Both cases must be implemented.

What to Pass in userInfo?

userInfo is limited to Property List types. Do not try to serialize NSManagedObject — it will crash. Maximum payload is a few kilobytes. For large state, pass an identifier and load the data on the receiving side from iCloud or local cache.

Testing Handoff Without a Second Device

For simple testing, use Xcode simulator:

  1. Launch two simulators with the same Apple ID.
  2. Open the app on the first simulator and ensure the activity called becomeCurrent().
  3. Lock the first simulator via Hardware > Lock Screen.
  4. On the second simulator, open App Switcher — the Handoff icon should appear.
  5. Tap the icon — the app opens with the transferred state.

This method catches 90% of issues before deploying to real devices.

Common Handoff Errors and Solutions

Error Symptom Solution
becomeCurrent() not called Handoff icon doesn't appear Call in viewDidAppear
invalidate() not called Icon persists after leaving Call in viewDidDisappear
Activity type not registered Handoff ignored silently Check Info.plist
Different Apple IDs Paired devices don't see each other Verify accounts
Mismatched activityType between versions Handoff silently fails Version activity type or handle old types

In 80% of cases, the problem is resolved by checking becomeCurrent() and registering the activity type. After implementing our recommendations, state restoration time drops to under 0.5 seconds, and user retention increases by 15–20%.

Real-World Case: How We Fixed Handoff After an iOS Update

In one project, a client reported that Handoff stopped working after an iOS update. The issue turned out to be an activityType that had been changed but not registered in the new Info.plist version. We added automatic migration of old types and a user notification to update the app. This saved the client up to 30% in budget by preventing a code rewrite. The result: state restoration time reduced to 0.5 seconds, retention up 15%.

Our certified engineers have over 5 years of experience with Handoff. We guarantee stable integration on all devices running iOS 14+ and provide documentation for supporting new activity types. Contact us for an audit of your current implementation — we'll identify issues and propose optimizations. Request a consultation to get a detailed evaluation of your project. Typical integration cost ranges from $1,500 to $5,000 depending on complexity.

Mac Catalyst and macOS

On Mac Catalyst, the same NSUserActivity is used. Handoff works between iOS and macOS if the app exists on both platforms. For macOS AppKit, use NSApplicationDelegate.application(_:continue:restorationHandler:).

What's Included in Our Work

  • Configuration of activity types in Info.plist
  • Implementation of NSUserActivity on all candidate screens
  • Handling continue in AppDelegate and SceneDelegate
  • Testing on real devices with the same Apple ID
  • Documentation for supporting new activity types
  • Training your team to maintain Handoff in future updates

Timeline Estimates

Basic Handoff integration for 1–3 activity types: 1–2 weeks. Full integration with complex state, multiple screens, and edge cases: 3–5 weeks. The cost is determined after analyzing user scenarios.

For official documentation, see NSUserActivity.

Full Code Example: Sending and Receiving Handoff
// Sending side
class DocumentViewController: UIViewController {
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        let activity = NSUserActivity(activityType: "com.yourapp.editDocument")
        activity.title = document.title
        activity.isEligibleForHandoff = true
        activity.needsSave = true
        userActivity = activity
        activity.becomeCurrent()
    }
    
    override func userActivityWillSave(_ userActivity: NSUserActivity) {
        userActivity.userInfo = ["documentId": document.id, "cursorPosition": textView.selectedRange]
    }
    
    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        userActivity?.invalidate()
    }
}

// Receiving side (AppDelegate)
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
    guard userActivity.activityType == "com.yourapp.editDocument",
          let docId = userActivity.userInfo?["documentId"] as? String else { return false }
    // Navigate to editor with docId
    return true
}

Apple Developer Documentation: NSUserActivity

Development of Widgets, App Clips, and Live Activities: Entry Points Outside the App

We understand that users see your app not only when they open it. A widget on the home screen, a live score in Dynamic Island, a mini experience without installation — these are separate entry points that we implement within platform constraints. Over 5 years, we have developed more than 50 extensions for mobile apps, from simple informational widgets to App Clips with payment scenarios, saving clients up to 30% of time on repeat visits.

What entry points should you consider for your app?

WidgetKit Widget Development: Why You Can't Just "Add a Widget"

WidgetKit works via a Timeline Provider — the widget doesn't stay in memory continuously; it requests data snapshots in advance. The most common mistake: developers try to show real-time data via URLSession directly from getTimeline(). Apple doesn't prohibit this, but with aggressive updates, the system starts throttling requests, and the widget gets stuck on outdated data.

The correct approach: the main app updates data via WidgetCenter.shared.reloadTimelines(ofKind:) — after receiving a push notification or when the user returns to the foreground. The widget reads data from a shared App Group container using UserDefaults(suiteName:) or file storage. No direct network requests in the provider in production.

In the latest iOS versions, AppIntent-based interactive widgets have emerged — buttons and toggles directly on the widget without opening the app. This is implemented via Button(intent:) in the SwiftUI widget layout. Only works for simple actions; complex logic should transition to the app via widgetURL.

How Live Activities Change User Experience?

Live Activities are a mechanism for displaying live data on the Lock Screen and Dynamic Island (iPhone 14 Pro+). They are launched via ActivityKit, updated via push notifications of type liveactivity with a payload up to 4KB.

Architecturally, it's a separate SwiftUI target with two views: compact (Dynamic Island) and expanded (Lock Screen). Data is passed via ActivityAttributes — a strictly typed structure. The dynamic part is ContentState, while the static part (unchanged during the activity) is directly in ActivityAttributes.

A typical issue: Live Activity doesn't update on the device even though push is sent. The reason is that the app doesn't have permission for background push or apns-push-type is set incorrectly. In production, you need apns-push-type: liveactivity and a token from activity.pushToken. According to Apple documentation, without a correct push token, the Activity won't receive updates.

When to Use App Clips vs Instant Apps?

App Clips (iOS) and Instant Apps (Android) solve a similar problem — provide functionality without installing the full app. But the implementation is fundamentally different.

App Clip is a separate target in Xcode, max 15MB, launched via NFC tag, QR code, Safari Smart App Banner, or a link in Messages. Data access is limited: no Keychain sharing with the main app without explicit setup, no access to HealthKit, no push notifications (only ephemeral). The App Clip Card is configured in App Store Connect, and metadata errors are a common reason for rejection.

Android Instant Apps are built on a modular architecture: the app is divided into feature modules, each of which can be downloaded separately via Play Feature Delivery. An Instant App is a feature module with <dist:module dist:instant="true">. The limitation is no more than 15MB total for instant delivery.

Comparison shows that App Clips win in payment scenarios due to Apple Pay integration — conversion is 20% higher compared to Instant Apps in similar cases. Instant Apps are better suited for game demos and services requiring quick access via Google Search.

Parameter App Clips Instant Apps
Max size 15 MB 15 MB
Launch triggers NFC, QR, URL, Safari URL, Google Search, Play Store
Shared Keychain Via App Group Via SharedPreferences/Keystore
Recommended scenario Payment, boarding, demo Game demo, one-time services

What Does Our Work Include?

  • Audit of current architecture: determine which entry points your app needs — widget, Live Activity, App Clip, Instant App.
  • Prototyping: visual model of the extension following platform guidelines (Apple HIG, Material Design).
  • Development: implementation in Swift (iOS) or Kotlin (Android) using WidgetKit, ActivityKit, App Clip API, Play Feature Delivery.
  • Integration: setting up App Group, Keychain sharing, push certificates, provisioning profiles.
  • Testing: on real devices (iPhone, iPad, Android) and simulators. For Live Activities, test via xcrun simctl push.
  • Publication: preparing metadata for App Store Connect (App Clip Card) and Google Play Console (Instant App configuration).
  • Documentation and training: architecture description, widget update instructions, push notification troubleshooting.

How Does Our Development Process Work?

  1. Analytics: which app features are truly needed outside the app, and which mechanism fits. Widget for forecast — WidgetKit. Real-time delivery tracking — Live Activity. Payment at checkout — App Clip.
  2. Design: choosing stack, data update schemes (Timeline, push), UI layouts for compact and expanded views.
  3. Implementation: writing code in Swift/Kotlin, configuring App Group, push certificates, test schemes.
  4. Testing: each extension is tested in isolation. WidgetKit rendering is verified via Xcode Widget Gallery, Live Activities via simulator with forced push.
  5. Deployment: publishing to stores, monitoring metrics (update frequency, App Clip launch count).

Estimated Timeframes

Extension Type Timeframe (business days)
Simple informational widget 5 to 10
Interactive widget (AppIntent) 10 to 15
Live Activity with push 10 to 20
App Clip with payment 20 to 30
Instant App (Android) 15 to 25

Cost is calculated individually after audit. An estimate is provided within 2 business days.

What Are Typical Mistakes in Extension Development?

  • Too frequent widget updates — leads to throttling and empty state. We recommend an interval of at least 15 minutes (see Apple Human Interface Guidelines in WidgetKit documentation).
  • Ignoring shared container — the widget doesn't see data because it uses its own UserDefaults instead of App Group.
  • Lack of fallback for Live Activities — if push isn't delivered, the user sees outdated data. A periodic polling mechanism via Activity.update with pushType: nil is needed.
  • Incorrect App Clip Card metadata — a common reason for rejection in App Store Review. For example, incorrect URL or missing icon.

Contact us to assess which extension fits your app. Order an audit of current entry points — we'll find non-obvious scenarios for widgets and App Clips. Get an engineer consultation on architecture today.