Skip to content

Communication via HCE (NFC) on iOS

This guide explains how to enable HCE-based unlocking on iOS using the Tapkey Mobile SDK.
Before proceeding, make sure to review the HCE Overview.

Familiarize Yourself with Apple HCE APIs

This guide focuses on integrating the Tapkey Mobile SDK with iOS's native HCE APIs. Before starting implementation, it is strongly recommended to review Apple’s documentation on HCE, including entitlement setup and platform requirements: Apple HCE Documentation

European Economic Area only

Apple restricts the use of HCE functionality to devices operating within the European Economic Area (EEA). This feature is not available on devices registered outside of the EEA.

Info

The configuration steps provided below — particularly those related to Apple-specific setup — are best-effort guidance. It may be necessary to adapt parts of the integration based on the specific app configuration or future platform updates.
Refer to Apple’s official documentation for the most up-to-date requirements.

Prerequisites

The following conditions must be fulfilled before starting development:

  • Minimum iOS Version: 17.4 or later
  • AID (Application Identifier): A custom AID must be provisioned and maintained by the integrator.
  • Entitlement: HCE entitlement must be explicitly requested from Apple.
  • Locks: Compatible locks must be available and pre-configured to accept the specified AID.

Configure App Capabilities and Entitlements

Enable HCE in the App Entitlements

In the app’s entitlements file, the property com.apple.developer.nfc.hce must be enabled.
Additionally, the correct AID must be registered under com.apple.developer.nfc.hce.iso7816.select-identifier-prefixes.

<plist version="1.0">
<dict>
    ...
    <key>com.apple.developer.nfc.hce</key>
    <true/>
    <key>com.apple.developer.nfc.hce.iso7816.select-identifier-prefixes</key>
    <array>
        <string>YOUR_AID</string>
    </array>
</dict>
</plist>

Register as Default HCE Handler (Optional)

On iOS, only one application can handle HCE events in the background. By default, this is the Apple Wallet app. If multiple apps support HCE, the user may select a default app in device settings.

To allow the app to be eligible as the default HCE handler, add the following entitlement:

<plist version="1.0">
<dict>
    ...
    <key>com.apple.developer.nfc.hce.default-contactless-app</key>
    <true/>
</dict>
</plist>

Configure the Tapkey Mobile SDK

Set the Application Identifier (AID) during SDK initialization to enable HCE-based communication:

TapkeyMobileSdk.initialize { builder in
    builder
        .setAid(YOUR_AID)
        ....
    }

Implement the CardSession Flow

iOS uses the CardSession API to manage HCE communication. Refer to Apple’s official CardSession Documentation for general implementation guidelines.

Passing the CardSession to the Tapkey SDK

After establishing a CardSession and detecting an NFC reader, pass the session to the hceLockCommunicator of the Tapkey Mobile SDK. The command to be executed must be provided as a callback, which will be invoked once a connection to a Tapkey-enabled lock is established.

// Check if CardSession is supported and available
guard NFCReaderSession.readingAvailable,
    CardSession.isSupported,
    await CardSession.isEligible else {
    // Handle case where CardSession is not available on the device
    return
}

// Retrieve HceLockCommunicator from the SDK
var hceLockCommunicator = TapkeyMobileSdk.serviceFactory.hceLockCommunicator


// Start a CardSession as described in Apple’s documentation
var cardSession = try await CardSession()

let asyncIterator: CardSession.EventStream.Iterator = cardSession.eventStream.makeAsyncIterator()

// Wait for session to start
guard case .sessionStarted = try await asyncIterator.next() else {
    // Handle failure to start session
    return
}

// Start emulation
try await cardSession.startEmulation()

// Wait until a reader is detected
guard case .readerDetected = try await asyncIterator.next() else {
    // Handle missing reader
    return
}
...

// Pass the card session to Tapkey SDK
// Note: No other consumer must access the event stream concurrently
var commandResult = try await hceLockCommunicator.executeCommandAsync(cardSession: cardSession, physicalLockId: nil) {
    @MainActor (tlcpConnection, publicState) async throws in
        return try await commandExecutionFacade.triggerLockAsync(tlcpConnection)
}

switch code {
case TKMCommandResult.TKMCommandResultCode.ok:
    // Command executed successfully
    break

    // Handle additional result cases as needed
    ...
}

Reacting to HCE Events in the Background

The most seamless user experience is achieved when the device initiates NFC communication simply by being brought near the lock — without requiring the app to be manually opened.

However, on iOS, this behavior is subject to platform restrictions:

  • Only one application on the device can handle HCE events when in the background.
  • By default, this is the Apple Wallet app.
  • Users have the option to select an alternative default application in the iOS Settings app.
  • If an alternative app is selected, the Wallet app must then be opened manually for its NFC functionality to remain accessible.