Skip to main content

iOS Platform Guide

Complete guide for integrating the MOSA SDK into iOS applications.

Requirements​

  • Minimum iOS Version: 13.0
  • Xcode: 12.0 or later
  • Swift: 5.0 or later
  • Device: iPhone 7 or later with NFC and BLE support

Installation​

  1. In Xcode, go to File → Add Packages...
  2. Enter the package URL (contact MOSA for the repository URL)
  3. Select the version or branch
  4. Add to your target

Alternatively, add to your Package.swift:

dependencies: [
.package(url: "https://github.com/mosa/ios-sdk.git", from: "1.0.0")
]

CocoaPods​

Add to your Podfile:

pod 'MosaDockSdk', '~> 1.0.0'

Then run:

pod install

Configuration​

Info.plist​

Add required usage descriptions:

<key>NSBluetoothAlwaysUsageDescription</key>
<string>This app uses Bluetooth to connect to and unlock MOSA docks.</string>

<key>NSBluetoothPeripheralUsageDescription</key>
<string>This app uses Bluetooth to connect to and unlock MOSA docks.</string>

<key>NFCReaderUsageDescription</key>
<string>This app uses NFC to scan MOSA dock tags for unlocking and parking operations.</string>

Note: NSBluetoothPeripheralUsageDescription is legacy but still recommended for compatibility.

Capabilities​

Enable the following capabilities in your app target:

  1. Background Modes (optional, for background BLE):

    • Uses Bluetooth LE accessories
  2. Near Field Communication Tag Reading: Enabled automatically when NFC usage description is present

Initialization​

Basic Initialization​

import MosaDockSdk

// Initialize SDK
MosaDockSDK.initialize(
with: self, // UIViewController
apiKey: "your-api-key",
userId: "user-123"
)

// Get shared instance
let sdk = MosaDockSDK.shared

Set Delegate (Optional)​

class ViewController: UIViewController, MosaDockDelegate {
override func viewDidLoad() {
super.viewDidLoad()

MosaDockSDK.initialize(
with: self,
apiKey: "your-api-key",
userId: "user-123"
)

let sdk = MosaDockSDK.shared
sdk.setDelegate(self)
}

// Implement delegate methods...
func onNFCScanCompleted(_ tag: MosaTag) {
// Handle NFC scan
}

func onBleConnected() {
// Handle BLE connection
}

// Implement other delegate methods...
}

Permissions​

Bluetooth Permissions​

Bluetooth permissions are requested automatically when needed. Ensure Info.plist has the required usage descriptions:

  • NSBluetoothAlwaysUsageDescription (required for iOS 13+)
  • NSBluetoothPeripheralUsageDescription (legacy, recommended for compatibility)

NFC Permissions​

NFC permissions are requested automatically when NFC scanning starts. Ensure Info.plist has:

  • NFCReaderUsageDescription

Location Permissions (Optional)​

Location permissions are only required if you use BLE in the background:

  • NSLocationWhenInUseUsageDescription
  • NSLocationAlwaysAndWhenInUseUsageDescription

Device Capabilities​

Check BLE Support​

if !sdk.isBLESupported() {
// Device doesn't support BLE
showError("Bluetooth LE is not supported on this device")
return
}

Check NFC Support​

if !sdk.isNFCSupported() {
// Device doesn't support NFC
showError("NFC is not supported on this device")
return
}

Basic Usage​

Request Unlock Key​

Task {
do {
let unlockKey = try await sdk.requestUnlockKey(
type: .bikeLock,
areaId: "area-123",
userId: "user-123"
)

// Use key for dock operation
try await performDockOperation(unlockKey)
} catch {
// Handle error
showError("Failed to request unlock key: \(error)")
}
}

Perform Dock Operation​

Task {
do {
try await sdk.unlockSmartLock(
with: unlockKey,
location: .parking,
delegate: self // Optional: provide delegate for this operation
)
} catch {
// Handle error
showError("Dock operation failed: \(error)")
}
}

Delegate Implementation​

Implement the MosaDockDelegate protocol to receive callbacks:

extension ViewController: MosaDockDelegate {
func onNFCScanCompleted(_ tag: MosaTag) {
DispatchQueue.main.async {
statusLabel.text = "Dock found: \(tag.macAddress)"
}
}

func onBleConnecting() {
DispatchQueue.main.async {
statusLabel.text = "Connecting..."
progressIndicator.startAnimating()
}
}

func onBleConnected() {
DispatchQueue.main.async {
statusLabel.text = "Connected"
}
}

func onBleDisconnected() {
DispatchQueue.main.async {
statusLabel.text = "Disconnected"
}
}

func onKeySent() {
DispatchQueue.main.async {
statusLabel.text = "Key sent, unlocking..."
}
}

func onDockUnlocked() {
DispatchQueue.main.async {
statusLabel.text = "Dock unlocked!"
}
}

func onDockChainRemoved() {
DispatchQueue.main.async {
statusLabel.text = "Chain removed - bike ready!"
}
}

func onDockChainInserted() {
DispatchQueue.main.async {
statusLabel.text = "Chain inserted, securing dock..."
}
}

func onDockSecured() {
DispatchQueue.main.async {
statusLabel.text = "Dock secured - operation complete!"
progressIndicator.stopAnimating()
}
}

func onError(_ error: MosaError) {
DispatchQueue.main.async {
statusLabel.text = "Error: \(error.message)"
progressIndicator.stopAnimating()
showErrorDialog(error)
}
}
}

Threading​

  • SDK methods use async/await and should be called from Task or async contexts
  • Callbacks are invoked on the main thread (via @MainActor)
  • BLE operations run on background queues

Lifecycle Management​

The SDK handles app lifecycle automatically:

  • Foreground: Full functionality available
  • Background: BLE operations can continue if background modes are enabled
  • Terminated: Sessions and events are persisted for sync when app restarts

Error Handling​

Errors are reported via the onError delegate method with structured MosaError objects:

func onError(_ error: MosaError) {
switch error.errorType {
case .nfcDisabled:
// Handle NFC disabled
showEnableNfcDialog()
case .bleDisabled:
// Handle BLE disabled
showEnableBleDialog()
case .dockTimeout:
// Handle timeout
showRetryDialog()
default:
// Handle other errors
showError("Error: \(error.message)")
}
}

Swift Patterns​

Using async/await​

func unlockBike() {
Task {
do {
let key = try await sdk.requestUnlockKey(
type: .bikeUnlock,
areaId: "area-123",
userId: "user-123"
)

try await sdk.unlockSmartLock(
with: key,
location: .parking,
delegate: self
)
} catch {
await MainActor.run {
showError("Failed: \(error)")
}
}
}
}

Using Combine (Optional)​

The SDK provides Combine publishers for reactive programming:

import Combine

var cancellables = Set<AnyCancellable>()

sdk.lastSessionPublisher
.sink { session in
// Handle session updates
}
.store(in: &cancellables)

Best Practices​

  1. Initialize Early: Initialize SDK in viewDidLoad() or application(_:didFinishLaunchingWithOptions:)
  2. Check Capabilities: Always check device capabilities before operations
  3. Set Delegate: Set delegate early to receive all callbacks
  4. Handle Errors: Always implement onError delegate method
  5. Main Thread: Use DispatchQueue.main.async or @MainActor for UI updates
  6. Lifecycle: Handle app lifecycle events appropriately

Next Steps​


For Android integration, see the Android Platform Guide.