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
Swift Package Manager (Recommended)
- In Xcode, go to File → Add Packages...
- Enter the package URL (contact MOSA for the repository URL)
- Select the version or branch
- 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:
-
Background Modes (optional, for background BLE):
- Uses Bluetooth LE accessories
-
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:
NSLocationWhenInUseUsageDescriptionNSLocationAlwaysAndWhenInUseUsageDescription
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/awaitand should be called fromTaskor 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
- Initialize Early: Initialize SDK in
viewDidLoad()orapplication(_:didFinishLaunchingWithOptions:) - Check Capabilities: Always check device capabilities before operations
- Set Delegate: Set delegate early to receive all callbacks
- Handle Errors: Always implement
onErrordelegate method - Main Thread: Use
DispatchQueue.main.asyncor@MainActorfor UI updates - Lifecycle: Handle app lifecycle events appropriately
Next Steps
- SDK Callbacks - Complete callback documentation
- Use Case Guides - Bike parking and sharing examples
- API Reference - Complete API documentation
For Android integration, see the Android Platform Guide.