Skip to content

Integrating the Tapkey Mobile SDK for iOS

Latest Version

The latest version of the Tapkey Mobile SDK for iOS is 2.44.0.0

Warning

This documentation refers to version 2.44.0 and later. This documentation may not be appropriate for older versions. Take a look at the upgrade section for upgrading to the latest version of the Tapkey Mobile SDK.

Sample App

A Tapkey Mobile SDK sample for iOS app is available on GitHub.

Requirements

The Tapkey Mobile SDK for iOS is distributed as a compiled static Swift XCFramework for iOS and watchOS. It is built using following SDKs and versions:

Tooling Version Notice
Xcode 16.0 The Xcode version the framework is built with. Module Forward Compatiblity is enabled, so the SDK should also be compatible with future Xcode versions.
Platforms iphoneOS, watchOS The framework is distributed as an XCFramework with support for these platforms.
iphoneOS SDK iOS 18.0 The iphoneOS SDK the framework is built against.
Minimum iOS Version iOS 12 The minimum iOS version the Tapkey Mobile SDK for iOS.
watchOS SDK watchOS 10.0 The watchOS SDK version the framework is built against.
Minimum watchOS Version watchOS 6.2 The minimum watchOS version the Tapkey Mobile SDK currently supports.
Swift Compiler 6.0 The version of the Swift compiler that was used for compiling the current version of the Tapkey Mobile SDK for iOS (as bundled with Xcode). As Swift 5 does not offer backwards compatibility, the target app must be compiled using the same or a newer version. Read more about Swift Stability
Cocoapods 1.12.1  The Cocoapods version the Tapkey Mobile SDK for iOS is distributed and tested with. Older version might not support static Swift frameworks or the XCFramework format.
OjbC n/a The Tapkey Mobile SDK for iOS does not expose any ObjC APIs.
Swift Package Manager n/a The Tapkey Mobile SDK for iOS is currently not available via the Swift Package Manager, as some of its dependencies are not available via Swift Package Manager as of today.

Minimum Version Policy

The Tapkey Mobile SDK for iOS officially supports and is tested against the three latest major versions of iOS. E.g. if the current version of iOS is 18.0, then the Tapkey Mobile SDK for iOS is tested against iOS Version 16.0 and newer. Usually older versions are supported on best effort as well, but this could be discontinued at any time, should technical restrictions make this necessary. watchOS is currently only officially supported on the current major version.

Adding the Tapkey Mobile SDK via CocoaPods

The Tapkey Mobile SDK is published as a Pod in a private Podspec repository. To tell CocoaPods where to find the artefacts add the Tapkey Podspec repository, add the official Tapkey Podspec repository as a source to the beginning of the Podfile and add TapkeyMobileLib as a dependency to your target app. Also make sure, use_frameworks! is set.

source 'https://github.com/tapkey/TapkeyCocoaPods'
source 'https://cdn.cocoapods.org/'

use_frameworks!

target 'App' do
    pod 'TapkeyMobileLib', '2.44.0.0'
end

Bootstrap the Tapkey Mobile SDK

  1. Import the TapkeyMobileLib module.
  2. Use the TKMServiceFactoryBuilder to create an instance of the TKMServiceFactory in the willFinishLaunchingWithOptions callback in the app delegate.
import TapkeyMobileLib
import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    private var TKMServiceFactory: TKMServiceFactory!

    func application(_ application: UIApplication,
                     willFinishLaunchingWithOptions launchOptions:
                     [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {

        // Build service factory
        self.TKMServiceFactory = TKMServiceFactoryBuilder()
            .build()
    }
}

Logging In Users

The SDK takes a Tapkey access token to log in a user. Retrieving such an access token is part of the implementing application. Usually, either the Token Exchange or Authentication Code with PKCE grant type will be used. For more information about authenticating against Tapkey, take a look at the authentication section.

Once an access token has been obtained, it can be used to log in a user as outlined below:

TKMServiceFactory.userManager.logInAsync(accessToken: "eyJhbGciOiJSUz...O-YbBq8F7086rQi-kEbERp4dA3r0WonpHnmYcXEnA", cancellationToken: ct)
    .continueOnUi(userId -> {
        // User logged in, use user's ID here.
    })

Subsequently, userManager.users will yield a list of IDs of all logged-in users. In most applications, only one user will be logged in at a time.

val userId = userManager.users[0]
// Fetch information about the user using the ID

Note that a token refresh handler must be registered with the TKMServiceFactory prior to logging in any users. This can be done using the TKMServiceFactoryBuilder's setTokenRefreshHandler(tokenRefreshHandler: TKMTokenRefreshHandler) method.

// In the `willFinishLaunchingWithOptions` callback in the app delegate:

// Build service factory
self.tapkeyServiceFactory = TKMServiceFactoryBuilder()
    .setTokenRefreshHandler(SampleTokenRefreshHandler())
    .build()
// Sample token refresh handler implementation

import TapkeyMobileLib

class SampleTokenRefreshHandler: TKMTokenRefreshHandler {

    func refreshAuthenticationAsync(userId: String, cancellationToken: TKMCancellationToken) -> TKMPromise<String> {
        let promiseSource = TKMPromiseSource<String>()

        let accessToken = getNewTapkeyAccessTokenFromCustomApplicationLogic()

        // OK:
        promiseSource.setResult(value)

        // Or, in case no access token could be obtained:
        promiseSource.setError(TKMError(errorDescriptor: TKMErrorDescriptor(
                code: TKMAuthenticationHandlerErrorCodes.TokenRefreshFailed,
                message: "No new token can be obtained.",
                details: nil)))

        return promiseSource.promise
    }

    func onRefreshFailed(userId: String) {
        /*
         * Will be called if re-authentication failed and a manual call to
         * userManager.updateAccessToken() is required.
         */
    }
}

Searching for Nearby Locks

Searching for nearby locks can be done with the SDK's TKMBleLockScanner. For more details or custom implementation please read the BLE Scanning section of the Documentation.

Usage Description in Info.plist required

Depending on the iOS SDK version the app is built against, different entries in the Info.plist are required to scan for and communicate with BLE devices.

If case the reqired entry is missing, the app will crash using the bluetooth apis.

Read More

TKMBleLockScanner bleLockScanner = tapkeyServiceFactory.bleLockScanner

// Returns an observer registration to stop scanning once finished.
bleScanObserverRegistration = bleLockScanner.startForegroundScan()

To observe locks entering and leaving the range of this device, the TKMBleLockScanner's observable can be used.

// Listen for Tapkey locks coming into or leaving range.
nearbyLocksObserverRegistration = bleLockScanner.observable
    .addObserver(locks -> {
        // Handle nearby locks here, e.g. present them to the user.
    })

// Alternatively, the locks property can be used to get a list of nearby locks in this moment.
nearbyLocks = bleLockScanner.locks

Triggering a Lock

After a nearby lock was found with the TKMBleLockScanner, it can be opened using the trigger lock command, which is available from the TKMCommandExecutionFacade. Any command inside the command execution facade, requires a TLCP connection to a lock. The following example uses TKMBleLockCommunicator to establish a TCLP connection via BLE.

let ct = TKMCancellationTokens.fromTimeout(timeoutMs: 15000)

// Use the BLE lock communicator to send a command to the lock
return bleLockCommunicator.executeCommandAsync(
                peripheralId: peripheralId,
                physicalLockId: physicalLockId,
                commandFunc: { tlcpConnection -> TKMPromise<TKMCommandResult> in

                    // Pass the TLCP connection to the command execution facade
                    return self.commandExecutionFacade!.triggerLockAsync(
                        tlcpConnection,
                        cancellationToken: ct)
                },
                cancellationToken: ct)

    // Process the command's result
    .continueOnUi({ commandResult in
        let code: TKMCommandResult.TKMCommandResultCode = commandResult?.code ??
                TKMCommandResult.TKMCommandResultCode.technicalError

        switch code {
        case TKMCommandResult.TKMCommandResultCode.ok:
            return true
        default:
            return false
        }
    })
    .catchOnUi({ (e: TKMAsyncError) -> Bool in

        let syncError = e.syncSrcError

        // A TKMRuntimeError.bleInconsistencyError indicates that the bluetooth chip of the lock
        // did not response as expected and should not happen. But the iPhone is known to cache some
        // bluetooth connection specific cofigurations which might cause issues when the bluetooth
        // configuration of the lock was changed during an firmware upgrade.
        // In such very rare and unexpected cases a restart of the iphone should resolve the issue.
        if case TKMRuntimeError.bleInconsistencyError(_) = syncError {
            NSLog("Trigger lock failed because of a bleInconsistencyError")
            // Inform the user that an unexpected happend and a restart of the iphone might help.
            return false
        } 

        NSLog("Trigger lock failed")
        return false
    })

Logging

By default the Tapkey Mobile Lib writes its logs to the system logging system. To be able to collect logs methodical for debugging and investigations, a custom logger can be used instead.

Info

A custom logger replaces the Tapkey default logger, therefore logs won't be written to LogCat anymore in such cases.

Create a custom implementation of the TKMLogger.

class CustomLogger: TKMLogger {
    func println(priority: Int32, tag: String, msg: String, error: Error?) {
        // Custom logging implementation
    }
}

The priority is divided into following static priority levels:

TKMLogs.VERBOSE
TKMLogs.DEBUG
TKMLogs.INFO
TKMLogs.WARN
TKMLogs.ERROR

Add an instance of the custom implementation to TKMLogs. This should be done preferably before creating the TKMServiceFactoryBuilder to avoid lost logs during the bootrap.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

    TKMLogs.setLogger(CustomLogger())

        self.tapkeyServiceFactory = TKMServiceFactoryBuilder()
            ...
            .build()

        ...
  }